@@ -164,7 +164,7 @@ func Set(kind string, key [32]byte, value []byte) error {
164
164
}
165
165
// Avoiding O_TRUNC here is merely an optimization to avoid
166
166
// cache misses when two threads race to write the same file.
167
- if err := writeFileNoTrunc (casName , value , 0666 ); err != nil {
167
+ if err := writeFileNoTrunc (casName , value , 0600 ); err != nil {
168
168
os .Remove (casName ) // ignore error
169
169
return err // e.g. disk full
170
170
}
@@ -178,7 +178,7 @@ func Set(kind string, key [32]byte, value []byte) error {
178
178
if err := os .MkdirAll (filepath .Dir (indexName ), 0700 ); err != nil {
179
179
return err
180
180
}
181
- if err := writeFileNoTrunc (indexName , hash [:], 0666 ); err != nil {
181
+ if err := writeFileNoTrunc (indexName , hash [:], 0600 ); err != nil {
182
182
os .Remove (indexName ) // ignore error
183
183
return err // e.g. disk full
184
184
}
@@ -203,19 +203,23 @@ func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error {
203
203
return err
204
204
}
205
205
206
- const casKind = "cas"
206
+ const casKind = "cas" // kind for CAS (content-addressable store) files
207
207
208
208
var iolimit = make (chan struct {}, 128 ) // counting semaphore to limit I/O concurrency in Set.
209
209
210
210
var budget int64 = 1e9 // 1GB
211
211
212
- // SetBudget sets a soft limit on disk usage of the cache (in bytes)
213
- // and returns the previous value. Supplying a negative value queries
214
- // the current value without changing it.
212
+ // SetBudget sets a soft limit on disk usage of files in the cache (in
213
+ // bytes) and returns the previous value. Supplying a negative value
214
+ // queries the current value without changing it.
215
215
//
216
216
// If two gopls processes have different budgets, the one with the
217
217
// lower budget will collect garbage more actively, but both will
218
218
// observe the effect.
219
+ //
220
+ // Even in the steady state, the storage usage reported by the 'du'
221
+ // command may exceed the budget by as much as 50-70% due to the
222
+ // overheads of directories and the effects of block quantization.
219
223
func SetBudget (new int64 ) (old int64 ) {
220
224
if new < 0 {
221
225
return atomic .LoadInt64 (& budget )
@@ -250,6 +254,15 @@ func SetBudget(new int64) (old int64) {
250
254
// practice atomic (all or nothing) on all platforms.
251
255
// (See GOROOT/src/cmd/go/internal/cache/cache.go.)
252
256
//
257
+ // Russ Cox notes: "all file systems use an rwlock around every file
258
+ // system block, including data blocks, so any writes or reads within
259
+ // the same block are going to be handled atomically by the FS
260
+ // implementation without any need to request file locking explicitly.
261
+ // And since the files are so small, there's only one block. (A block
262
+ // is at minimum 512 bytes, usually much more.)" And: "all modern file
263
+ // systems protect against [partial writes due to power loss] with
264
+ // journals."
265
+ //
253
266
// We use a two-level scheme consisting of an index and a
254
267
// content-addressable store (CAS). A single cache entry consists of
255
268
// two files. The value of a cache entry is written into the file at
@@ -262,10 +275,12 @@ func SetBudget(new int64) (old int64) {
262
275
// Once the CAS file has been written, we write a small fixed-size
263
276
// index file at filename(kind, key), using the values supplied by the
264
277
// caller. The index file contains the hash that identifies the value
265
- // file in the CAS. (We could add a small amount of extra metadata to
266
- // this file if later desired.) Because the index file is small,
278
+ // file in the CAS. (We could add extra metadata to this file, up to
279
+ // 512B, the minimum size of a disk block, if later desired, so long
280
+ // as the total size remains fixed.) Because the index file is small,
267
281
// concurrent writes to it are atomic in practice, even though this is
268
- // not guaranteed by any OS.
282
+ // not guaranteed by any OS. The fixed size ensures that readers can't
283
+ // see a palimpsest when a short new file overwrites a longer old one.
269
284
//
270
285
// New versions of gopls are free to reorganize the contents of the
271
286
// version directory as needs evolve. But all versions of gopls must
@@ -275,7 +290,7 @@ func SetBudget(new int64) (old int64) {
275
290
// the entire gopls directory so that newer binaries can clean up
276
291
// after older ones: in the development cycle especially, new
277
292
// new versions may be created frequently.
278
-
293
+ //
279
294
// TODO(adonovan): opt: use "VVVVVVVV / KK / KKKK...KKKK-kind" to
280
295
// avoid creating 256 directories per distinct kind (+ cas).
281
296
func filename (kind string , key [32 ]byte ) (string , error ) {
0 commit comments