@@ -26,6 +26,7 @@ import (
26
26
"encoding/binary"
27
27
"errors"
28
28
"fmt"
29
+ "hash/crc32"
29
30
"io"
30
31
"log"
31
32
"os"
@@ -35,6 +36,7 @@ import (
35
36
"sync/atomic"
36
37
"time"
37
38
39
+ "golang.org/x/tools/internal/bug"
38
40
"golang.org/x/tools/internal/lockedfile"
39
41
)
40
42
@@ -62,13 +64,20 @@ func Get(kind string, key [32]byte) ([]byte, error) {
62
64
63
65
// Verify that the Write was complete
64
66
// by checking the recorded length.
65
- if len (data ) < 8 {
67
+ if len (data ) < 8 + 4 {
66
68
return nil , ErrNotFound // cache entry is incomplete
67
69
}
68
- if length := binary .LittleEndian .Uint64 (data ); int (length ) != len (data )- 8 {
70
+ length , value , checksum := data [:8 ], data [8 :len (data )- 4 ], data [len (data )- 4 :]
71
+ if binary .LittleEndian .Uint64 (length ) != uint64 (len (value )) {
69
72
return nil , ErrNotFound // cache entry is incomplete (or too long!)
70
73
}
71
- data = data [8 :]
74
+
75
+ // Check for corruption and print the entire file content as
76
+ // this may help us observe the pattern. See issue #59289.
77
+ if binary .LittleEndian .Uint32 (checksum ) != crc32 .ChecksumIEEE (value ) {
78
+ return nil , bug .Errorf ("internal error in filecache.Get(%q, %x): invalid checksum at end of %d-byte file %s:\n %q" ,
79
+ kind , key , len (data ), name , data )
80
+ }
72
81
73
82
// Update file time for use by LRU eviction.
74
83
// (This turns every read into a write operation.
@@ -84,7 +93,7 @@ func Get(kind string, key [32]byte) ([]byte, error) {
84
93
return nil , fmt .Errorf ("failed to update access time: %w" , err )
85
94
}
86
95
87
- return data , nil
96
+ return value , nil
88
97
}
89
98
90
99
// ErrNotFound is the distinguished error
@@ -104,15 +113,28 @@ func Set(kind string, key [32]byte, value []byte) error {
104
113
// the expected length first and verify it in Get.
105
114
var length [8 ]byte
106
115
binary .LittleEndian .PutUint64 (length [:], uint64 (len (value )))
107
- header := bytes .NewReader (length [:])
108
- payload := bytes .NewReader (value )
116
+
117
+ // Occasional file corruption (presence of zero bytes in JSON
118
+ // files) has been reported on macOS (see issue #59289),
119
+ // assumed due to a nonatomicity problem in the file system.
120
+ // Ideally the macOS kernel would be fixed, or lockedfile
121
+ // would implement a workaround (since its job is to provide
122
+ // reliable atomic file replacement atop kernels that don't),
123
+ // but for now we add an extra integrity check: a 32-bit
124
+ // checksum at the end.
125
+ var checksum [4 ]byte
126
+ binary .LittleEndian .PutUint32 (checksum [:], crc32 .ChecksumIEEE (value ))
109
127
110
128
// Windows doesn't support atomic rename--we tried MoveFile,
111
129
// MoveFileEx, ReplaceFileEx, and SetFileInformationByHandle
112
130
// of RenameFileInfo, all to no avail--so instead we use
113
131
// advisory file locking, which is only about 2x slower even
114
132
// on POSIX platforms with atomic rename.
115
- return lockedfile .Write (name , io .MultiReader (header , payload ), 0600 )
133
+ return lockedfile .Write (name , io .MultiReader (
134
+ bytes .NewReader (length [:]),
135
+ bytes .NewReader (value ),
136
+ bytes .NewReader (checksum [:])),
137
+ 0600 )
116
138
}
117
139
118
140
var budget int64 = 1e9 // 1GB
0 commit comments