Skip to content

Commit 38860c3

Browse files
committed
Modified AttributeCachingFileSystemTests.kt cached acl attributes do not get modified by concurrent operation test to use only readAttributes and setAttribute
Added AttributeCachingFileSystemTests.kt `acl attributes accessed from Files getFileAttributeView() are not cached` test to demonstrate we cannot cache attributes via FileAttributeViews Added README.md note to use Files.readAttributes() and Files.setAttribute() to access proper caching functionality
1 parent 5efc5a1 commit 38860c3

2 files changed

Lines changed: 87 additions & 6 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ attributes.
77
It reduces `OTHER_IOPS` calls as listed in the "other" column when executing [Procmon] and looking at
88
operations performed on a given file where file attributes are read and/or set.
99

10+
**NOTE**: This filesystem does not support caching attributes from `FileAttributeView`(s) directly (ie: via
11+
setting and/or getting attributes from views returned from `Files.getFileAttributeView()`). Please use
12+
`Files.readAttributes()` and `Files.setAttribute()` to access proper attribute caching functionality.
13+
1014
## Installation and Usage Instructions
1115
For installation with gradle:
1216
* Dependency to add to the top level `build.gradle.kts` file:

file-attribute-caching/src/test/kotlin/com/pkware/filesystem/attributecaching/AttributeCachingFileSystemTests.kt

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,11 @@ class AttributeCachingFileSystemTests {
530530
// Test with original file owner on default filesystem because it's a large amount of work to create our
531531
// own test user there.
532532

533-
val originalAttributeView = Files.getFileAttributeView(cachingPath, AclFileAttributeView::class.java)
534-
val originalOwner = originalAttributeView.owner
535-
val originalAclEntries = originalAttributeView.acl
533+
val originalAttributeMap = Files.readAttributes(cachingPath, "acl:*")
534+
val originalOwner = originalAttributeMap["owner"] as? UserPrincipal
535+
536+
@Suppress("UNCHECKED_CAST")
537+
val originalAclEntries = originalAttributeMap["acl"] as? List<AclEntry>
536538

537539
// simulate concurrent modification on default filesystem
538540
val concurrentPath = defaultFileSystem.getPath(
@@ -555,13 +557,12 @@ class AttributeCachingFileSystemTests {
555557
AclEntryPermission.DELETE,
556558
)
557559
.build()
558-
val aclEntries = listOf<AclEntry>(acl)
559-
Files.setAttribute(concurrentPath, "acl:acl", aclEntries)
560+
Files.setAttribute(concurrentPath, "acl:acl", listOf<AclEntry>(acl))
560561

561562
val newAttributesMap = Files.readAttributes(cachingPath, "acl:*")
562563

563564
assertThat(newAttributesMap).isInstanceOf(MutableMap::class.java)
564-
assertThat(originalOwner).isEqualTo(newAttributesMap["owner"])
565+
assertThat(originalOwner).isEqualTo(newAttributesMap["owner"] as? UserPrincipal)
565566
@Suppress("UNCHECKED_CAST")
566567
assertThat(originalAclEntries).containsExactlyElementsIn(
567568
newAttributesMap["acl"] as? List<AclEntry>,
@@ -573,6 +574,82 @@ class AttributeCachingFileSystemTests {
573574
}
574575
}
575576

577+
// This test demonstrates that we cannot cache attributes via the AclFileAttributeView itself and by extension we
578+
// cannot cache attributes via any other view directly (BasicFileAttributeView, DosFileAttributeView,
579+
// PosixFileAttributeView) because accessing those views' properties performs filesystem/disk io.
580+
@Test
581+
@EnabledOnOs(OS.WINDOWS)
582+
fun `acl attributes accessed from Files getFileAttributeView() are not cached`() {
583+
val defaultFileSystem = FileSystems.getDefault()
584+
val uniqueID = UUID.randomUUID()
585+
val tempDirPath = defaultFileSystem.getPath(System.getProperty("java.io.tmpdir")) / "TEST-ACL-$uniqueID"
586+
587+
AttributeCachingFileSystem.wrapping(defaultFileSystem).use {
588+
// get filesystem attribute caching path
589+
val javaTmpDir = it.getPath(System.getProperty("java.io.tmpdir"))
590+
val testDir = javaTmpDir / "TEST-ACL-$uniqueID"
591+
Files.createDirectories(testDir)
592+
var cachingPath = testDir / "testfile-$uniqueID.txt"
593+
594+
Files.createFile(cachingPath)
595+
Files.newOutputStream(cachingPath).use { outputStream ->
596+
outputStream.write("hello".toByteArray(Charsets.UTF_8))
597+
}
598+
599+
assertThat(cachingPath).isInstanceOf(AttributeCachingPath::class.java)
600+
assertThat((cachingPath as AttributeCachingPath).isCachedInitialized()).isFalse()
601+
// Force cachingPath initialization after the file is created with getPath on the path's string representation.
602+
// This test requires that the cache of the file under test be initialized.
603+
cachingPath = it.getPath(cachingPath.toString())
604+
assertThat((cachingPath as AttributeCachingPath).isCachedInitialized()).isTrue()
605+
606+
try {
607+
// Test with original file owner on default filesystem because it's a large amount of work to create our
608+
// own test user there.
609+
610+
val originalAttributeView = Files.getFileAttributeView(cachingPath, AclFileAttributeView::class.java)
611+
val originalOwner = originalAttributeView.owner
612+
val originalAclEntries = originalAttributeView.acl
613+
614+
// simulate concurrent modification on default filesystem
615+
val concurrentPath = defaultFileSystem.getPath(
616+
"$tempDirPath${defaultFileSystem.separator}testfile-$uniqueID.txt",
617+
)
618+
619+
val acl = AclEntry.newBuilder()
620+
.setType(AclEntryType.ALLOW)
621+
.setPrincipal(originalOwner)
622+
.setFlags(AclEntryFlag.FILE_INHERIT)
623+
.setPermissions(
624+
AclEntryPermission.WRITE_NAMED_ATTRS,
625+
AclEntryPermission.WRITE_ATTRIBUTES,
626+
AclEntryPermission.WRITE_DATA,
627+
AclEntryPermission.READ_ACL,
628+
AclEntryPermission.APPEND_DATA,
629+
AclEntryPermission.READ_ATTRIBUTES,
630+
AclEntryPermission.READ_DATA,
631+
AclEntryPermission.READ_NAMED_ATTRS,
632+
AclEntryPermission.DELETE,
633+
)
634+
.build()
635+
Files.setAttribute(concurrentPath, "acl:acl", listOf<AclEntry>(acl))
636+
637+
val newAttributeView = Files.getFileAttributeView(cachingPath, AclFileAttributeView::class.java)
638+
val newOwner = newAttributeView.owner
639+
val newAclEntries = newAttributeView.acl
640+
641+
assertThat(newAttributeView).isInstanceOf(AclFileAttributeView::class.java)
642+
// owner doesn't change because we didn't modify the owner in the concurrent file path
643+
assertThat(originalOwner).isEqualTo(newOwner)
644+
// acl entries do change because we modified the acl entries in the concurrent file path
645+
assertThat(originalAclEntries).doesNotContain(newAclEntries)
646+
} finally {
647+
Files.deleteIfExists(cachingPath)
648+
Files.deleteIfExists(testDir)
649+
}
650+
}
651+
}
652+
576653
@ParameterizedTest
577654
@EnabledOnOs(OS.WINDOWS)
578655
@ValueSource(strings = ["dos:readonly", "dos:hidden", "dos:archive", "dos:system"])

0 commit comments

Comments
 (0)