Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f373b7d9e4 | ||
|
|
d8b58bf281 |
@@ -407,9 +407,9 @@ operands:
|
||||
is what makes a daily rescan cheap.
|
||||
- When such a file's size on disk differs from its recorded `disk_size`, `scan`
|
||||
updates `disk_size` alone. It still does not read the file, and counts it as
|
||||
unchanged. ZFS, for example, counts a new file's blocks up to seconds after
|
||||
the write. A block is the unit in which a filesystem allocates disk space. A
|
||||
file scanned within those seconds gets its real `disk_size` at the next scan.
|
||||
unchanged. ZFS, for example, counts a new file's blocks only seconds after the
|
||||
write. A block is the unit in which a filesystem allocates disk space. A file
|
||||
scanned within those seconds gets its real `disk_size` at the next scan.
|
||||
- `scan` hashes a file again when its size differs or its mtime is newer. The
|
||||
mtime is compared to the nanosecond, as finely as the filesystem records it. A
|
||||
same-size rewrite counts as a change whenever the filesystem gives it a later
|
||||
|
||||
@@ -554,7 +554,7 @@ func (s *scanState) walkPhase(events <-chan walkEvent, roots []string) {
|
||||
|
||||
// The space a file takes on disk can change while its size and
|
||||
// mtime stay the same. ZFS, for one, counts a new file's blocks
|
||||
// up to seconds after the write.
|
||||
// only seconds after the write.
|
||||
if old.diskSize != ev.rec.diskSize {
|
||||
s.diskSizes = append(s.diskSizes, ev.rec)
|
||||
}
|
||||
|
||||
+25
-74
@@ -43,16 +43,10 @@ func writeFile(t *testing.T, dir, name string, data []byte) string {
|
||||
}
|
||||
|
||||
// diskSizeOf returns the bytes the file at path takes on disk, from its
|
||||
// own lstat: st_blocks counts 512-byte units. ZFS counts a write's
|
||||
// blocks only when it commits the write, up to seconds later, and one
|
||||
// commit can take the file through several growing sizes. So this value
|
||||
// can change at any moment. Neither fsync nor unix.Sync hastens that,
|
||||
// but a unix.Sync call often takes long enough that the blocks are
|
||||
// counted when it returns. A test that needs the value to change waits
|
||||
// for the change. A test that compares it with a scan's disk_size reads
|
||||
// it before and after the scan. The rewrite test rescans until the two
|
||||
// reads match, then compares exactly. The other two tests fail only on
|
||||
// a disk_size outside the two reads.
|
||||
// own lstat: st_blocks counts 512-byte units. ZFS counts a new file's
|
||||
// blocks only seconds after the write, and fsync does not hasten that.
|
||||
// A test therefore calls unix.Sync, which waits for it, before the scan
|
||||
// whose disk_size it compares with this.
|
||||
func diskSizeOf(t *testing.T, path string) int64 {
|
||||
t.Helper()
|
||||
|
||||
@@ -1454,26 +1448,20 @@ func TestScanStoresDiskSize(t *testing.T) {
|
||||
|
||||
unix.Sync() // see diskSizeOf
|
||||
|
||||
size := diskSizeOf(t, p)
|
||||
if size == 0 || size >= length {
|
||||
want := diskSizeOf(t, p)
|
||||
if want == 0 || want >= length {
|
||||
t.Fatalf("the file takes %d bytes on disk, want more than none "+
|
||||
"and fewer than its length %d", size, length)
|
||||
"and fewer than its length %d", want, length)
|
||||
}
|
||||
|
||||
for _, operand := range []string{filepath.Dir(p), p} {
|
||||
db := openTestDB(t)
|
||||
|
||||
// ZFS may count the blocks during the scan; see diskSizeOf.
|
||||
before := diskSizeOf(t, p)
|
||||
|
||||
syncTree(t, db, operand)
|
||||
|
||||
after := diskSizeOf(t, p)
|
||||
|
||||
got := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
if got < before || got > after {
|
||||
t.Errorf("scan of %s: disk_size = %d, want %d to %d",
|
||||
operand, got, before, after)
|
||||
if got != want {
|
||||
t.Errorf("scan of %s: disk_size = %d, want %d",
|
||||
operand, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1491,7 +1479,7 @@ func TestSyncScanRewriteUpdatesDiskSize(t *testing.T) {
|
||||
|
||||
syncTree(t, db, dir)
|
||||
|
||||
recorded := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
before := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
|
||||
pokeAt(t, p, 512*1024, pattern(7, 64*1024))
|
||||
|
||||
@@ -1504,53 +1492,22 @@ func TestSyncScanRewriteUpdatesDiskSize(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Wait for ZFS to count the write; see diskSizeOf.
|
||||
deadline := time.Now().Add(30 * time.Second)
|
||||
for diskSizeOf(t, p) == recorded {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the file still takes %d bytes on disk 30 seconds "+
|
||||
"after the write into its hole", recorded)
|
||||
}
|
||||
unix.Sync() // see diskSizeOf
|
||||
|
||||
unix.Sync()
|
||||
want := diskSizeOf(t, p)
|
||||
if want == before {
|
||||
t.Fatalf("the file still takes %d bytes on disk after the write "+
|
||||
"into its hole", want)
|
||||
}
|
||||
|
||||
// ZFS may still be counting, so the size may change during a rescan.
|
||||
// Only a rescan with the same size on both sides of it can be compared
|
||||
// exactly. A rescan that keeps the old size still fails, because the
|
||||
// wait made before differ from it.
|
||||
for {
|
||||
before := diskSizeOf(t, p)
|
||||
st := syncTree(t, db, dir)
|
||||
if st != (scanStats{walked: 1, updated: 1}) {
|
||||
t.Fatalf("rescan stats = %+v, want 1 updated", st)
|
||||
}
|
||||
|
||||
st := syncTree(t, db, dir)
|
||||
|
||||
after := diskSizeOf(t, p)
|
||||
|
||||
if st != (scanStats{walked: 1, updated: 1}) {
|
||||
t.Fatalf("rescan stats = %+v, want 1 updated", st)
|
||||
}
|
||||
|
||||
if before == after {
|
||||
got := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
if got != after {
|
||||
t.Errorf("disk_size = %d, want %d", got, after)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("the size on disk was still changing 30 seconds after "+
|
||||
"the write into its hole: %d, then %d", before, after)
|
||||
}
|
||||
|
||||
// A later mtime makes the next rescan read the file again.
|
||||
later = later.Add(time.Second)
|
||||
|
||||
err = os.Chtimes(p, later, later)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
if got != want {
|
||||
t.Errorf("disk_size = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,21 +1533,15 @@ func TestSyncScanUnchangedUpdatesDiskSize(t *testing.T) {
|
||||
|
||||
unix.Sync() // see diskSizeOf
|
||||
|
||||
// ZFS may count the blocks during the rescan; see diskSizeOf.
|
||||
before := diskSizeOf(t, p)
|
||||
|
||||
// A file read again would count as updated.
|
||||
st := syncTree(t, db, dir)
|
||||
|
||||
after := diskSizeOf(t, p)
|
||||
|
||||
if st != (scanStats{walked: 1, unchanged: 1}) {
|
||||
t.Fatalf("rescan stats = %+v, want 1 unchanged", st)
|
||||
}
|
||||
|
||||
got := recordByPath(t, dbRecords(t, db), p).diskSize
|
||||
if got < before || got > after {
|
||||
t.Errorf("disk_size = %d, want %d to %d", got, before, after)
|
||||
if want := diskSizeOf(t, p); got != want {
|
||||
t.Errorf("disk_size = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user