github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/mem_fs.go (about) 1 // Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package vfs // import "github.com/cockroachdb/pebble/vfs" 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "path" 13 "sort" 14 "strings" 15 "sync" 16 "sync/atomic" 17 "syscall" 18 "time" 19 20 "github.com/cockroachdb/errors" 21 "github.com/cockroachdb/errors/oserror" 22 "github.com/cockroachdb/pebble/internal/invariants" 23 ) 24 25 const sep = "/" 26 27 // NewMem returns a new memory-backed FS implementation. 28 func NewMem() *MemFS { 29 return &MemFS{ 30 root: newRootMemNode(), 31 } 32 } 33 34 // NewStrictMem returns a "strict" memory-backed FS implementation. The behaviour is strict wrt 35 // needing a Sync() call on files or directories for the state changes to be finalized. Any 36 // changes that are not finalized are visible to reads until MemFS.ResetToSyncedState() is called, 37 // at which point they are discarded and no longer visible. 38 // 39 // Expected usage: 40 // 41 // strictFS := NewStrictMem() 42 // db := Open(..., &Options{FS: strictFS}) 43 // // Do and commit various operations. 44 // ... 45 // // Prevent any more changes to finalized state. 46 // strictFS.SetIgnoreSyncs(true) 47 // // This will finish any ongoing background flushes, compactions but none of these writes will 48 // // be finalized since syncs are being ignored. 49 // db.Close() 50 // // Discard unsynced state. 51 // strictFS.ResetToSyncedState() 52 // // Allow changes to finalized state. 53 // strictFS.SetIgnoreSyncs(false) 54 // // Open the DB. This DB should have the same state as if the earlier strictFS operations and 55 // // db.Close() were not called. 56 // db := Open(..., &Options{FS: strictFS}) 57 func NewStrictMem() *MemFS { 58 return &MemFS{ 59 root: newRootMemNode(), 60 strict: true, 61 } 62 } 63 64 // NewMemFile returns a memory-backed File implementation. The memory-backed 65 // file takes ownership of data. 66 func NewMemFile(data []byte) File { 67 n := &memNode{} 68 n.refs.Store(1) 69 n.mu.data = data 70 n.mu.modTime = time.Now() 71 return &memFile{ 72 n: n, 73 read: true, 74 } 75 } 76 77 // MemFS implements FS. 78 type MemFS struct { 79 mu sync.Mutex 80 root *memNode 81 82 // lockFiles holds a map of open file locks. Presence in this map indicates 83 // a file lock is currently held. Keys are strings holding the path of the 84 // locked file. The stored value is untyped and unused; only presence of 85 // the key within the map is significant. 86 lockedFiles sync.Map 87 strict bool 88 ignoreSyncs bool 89 // Windows has peculiar semantics with respect to hard links and deleting 90 // open files. In tests meant to exercise this behavior, this flag can be 91 // set to error if removing an open file. 92 windowsSemantics bool 93 } 94 95 var _ FS = &MemFS{} 96 97 // UseWindowsSemantics configures whether the MemFS implements Windows-style 98 // semantics, in particular with respect to whether any of an open file's links 99 // may be removed. Windows semantics default to off. 100 func (y *MemFS) UseWindowsSemantics(windowsSemantics bool) { 101 y.mu.Lock() 102 defer y.mu.Unlock() 103 y.windowsSemantics = windowsSemantics 104 } 105 106 // String dumps the contents of the MemFS. 107 func (y *MemFS) String() string { 108 y.mu.Lock() 109 defer y.mu.Unlock() 110 111 s := new(bytes.Buffer) 112 y.root.dump(s, 0) 113 return s.String() 114 } 115 116 // SetIgnoreSyncs sets the MemFS.ignoreSyncs field. See the usage comment with NewStrictMem() for 117 // details. 118 func (y *MemFS) SetIgnoreSyncs(ignoreSyncs bool) { 119 y.mu.Lock() 120 if !y.strict { 121 // noop 122 return 123 } 124 y.ignoreSyncs = ignoreSyncs 125 y.mu.Unlock() 126 } 127 128 // ResetToSyncedState discards state in the FS that is not synced. See the usage comment with 129 // NewStrictMem() for details. 130 func (y *MemFS) ResetToSyncedState() { 131 if !y.strict { 132 // noop 133 return 134 } 135 y.mu.Lock() 136 y.root.resetToSyncedState() 137 y.mu.Unlock() 138 } 139 140 // walk walks the directory tree for the fullname, calling f at each step. If 141 // f returns an error, the walk will be aborted and return that same error. 142 // 143 // Each walk is atomic: y's mutex is held for the entire operation, including 144 // all calls to f. 145 // 146 // dir is the directory at that step, frag is the name fragment, and final is 147 // whether it is the final step. For example, walking "/foo/bar/x" will result 148 // in 3 calls to f: 149 // - "/", "foo", false 150 // - "/foo/", "bar", false 151 // - "/foo/bar/", "x", true 152 // 153 // Similarly, walking "/y/z/", with a trailing slash, will result in 3 calls to f: 154 // - "/", "y", false 155 // - "/y/", "z", false 156 // - "/y/z/", "", true 157 func (y *MemFS) walk(fullname string, f func(dir *memNode, frag string, final bool) error) error { 158 y.mu.Lock() 159 defer y.mu.Unlock() 160 161 // For memfs, the current working directory is the same as the root directory, 162 // so we strip off any leading "/"s to make fullname a relative path, and 163 // the walk starts at y.root. 164 for len(fullname) > 0 && fullname[0] == sep[0] { 165 fullname = fullname[1:] 166 } 167 dir := y.root 168 169 for { 170 frag, remaining := fullname, "" 171 i := strings.IndexRune(fullname, rune(sep[0])) 172 final := i < 0 173 if !final { 174 frag, remaining = fullname[:i], fullname[i+1:] 175 for len(remaining) > 0 && remaining[0] == sep[0] { 176 remaining = remaining[1:] 177 } 178 } 179 if err := f(dir, frag, final); err != nil { 180 return err 181 } 182 if final { 183 break 184 } 185 child := dir.children[frag] 186 if child == nil { 187 return &os.PathError{ 188 Op: "open", 189 Path: fullname, 190 Err: oserror.ErrNotExist, 191 } 192 } 193 if !child.isDir { 194 return &os.PathError{ 195 Op: "open", 196 Path: fullname, 197 Err: errors.New("not a directory"), 198 } 199 } 200 dir, fullname = child, remaining 201 } 202 return nil 203 } 204 205 // Create implements FS.Create. 206 func (y *MemFS) Create(fullname string) (File, error) { 207 var ret *memFile 208 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 209 if final { 210 if frag == "" { 211 return errors.New("pebble/vfs: empty file name") 212 } 213 n := &memNode{name: frag} 214 dir.children[frag] = n 215 ret = &memFile{ 216 n: n, 217 fs: y, 218 read: true, 219 write: true, 220 } 221 } 222 return nil 223 }) 224 if err != nil { 225 return nil, err 226 } 227 ret.n.refs.Add(1) 228 return ret, nil 229 } 230 231 // Link implements FS.Link. 232 func (y *MemFS) Link(oldname, newname string) error { 233 var n *memNode 234 err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { 235 if final { 236 if frag == "" { 237 return errors.New("pebble/vfs: empty file name") 238 } 239 n = dir.children[frag] 240 } 241 return nil 242 }) 243 if err != nil { 244 return err 245 } 246 if n == nil { 247 return &os.LinkError{ 248 Op: "link", 249 Old: oldname, 250 New: newname, 251 Err: oserror.ErrNotExist, 252 } 253 } 254 return y.walk(newname, func(dir *memNode, frag string, final bool) error { 255 if final { 256 if frag == "" { 257 return errors.New("pebble/vfs: empty file name") 258 } 259 if _, ok := dir.children[frag]; ok { 260 return &os.LinkError{ 261 Op: "link", 262 Old: oldname, 263 New: newname, 264 Err: oserror.ErrExist, 265 } 266 } 267 dir.children[frag] = n 268 } 269 return nil 270 }) 271 } 272 273 func (y *MemFS) open(fullname string, openForWrite bool) (File, error) { 274 var ret *memFile 275 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 276 if final { 277 if frag == "" { 278 ret = &memFile{ 279 n: dir, 280 fs: y, 281 } 282 return nil 283 } 284 if n := dir.children[frag]; n != nil { 285 ret = &memFile{ 286 n: n, 287 fs: y, 288 read: true, 289 write: openForWrite, 290 } 291 } 292 } 293 return nil 294 }) 295 if err != nil { 296 return nil, err 297 } 298 if ret == nil { 299 return nil, &os.PathError{ 300 Op: "open", 301 Path: fullname, 302 Err: oserror.ErrNotExist, 303 } 304 } 305 ret.n.refs.Add(1) 306 return ret, nil 307 } 308 309 // Open implements FS.Open. 310 func (y *MemFS) Open(fullname string, opts ...OpenOption) (File, error) { 311 return y.open(fullname, false /* openForWrite */) 312 } 313 314 // OpenReadWrite implements FS.OpenReadWrite. 315 func (y *MemFS) OpenReadWrite(fullname string, opts ...OpenOption) (File, error) { 316 f, err := y.open(fullname, true /* openForWrite */) 317 pathErr, ok := err.(*os.PathError) 318 if ok && pathErr.Err == oserror.ErrNotExist { 319 return y.Create(fullname) 320 } 321 return f, err 322 } 323 324 // OpenDir implements FS.OpenDir. 325 func (y *MemFS) OpenDir(fullname string) (File, error) { 326 return y.open(fullname, false /* openForWrite */) 327 } 328 329 // Remove implements FS.Remove. 330 func (y *MemFS) Remove(fullname string) error { 331 return y.walk(fullname, func(dir *memNode, frag string, final bool) error { 332 if final { 333 if frag == "" { 334 return errors.New("pebble/vfs: empty file name") 335 } 336 child, ok := dir.children[frag] 337 if !ok { 338 return oserror.ErrNotExist 339 } 340 if y.windowsSemantics { 341 // Disallow removal of open files/directories which implements 342 // Windows semantics. This ensures that we don't regress in the 343 // ordering of operations and try to remove a file while it is 344 // still open. 345 if n := child.refs.Load(); n > 0 { 346 return oserror.ErrInvalid 347 } 348 } 349 if len(child.children) > 0 { 350 return errNotEmpty 351 } 352 delete(dir.children, frag) 353 } 354 return nil 355 }) 356 } 357 358 // RemoveAll implements FS.RemoveAll. 359 func (y *MemFS) RemoveAll(fullname string) error { 360 err := y.walk(fullname, func(dir *memNode, frag string, final bool) error { 361 if final { 362 if frag == "" { 363 return errors.New("pebble/vfs: empty file name") 364 } 365 _, ok := dir.children[frag] 366 if !ok { 367 return nil 368 } 369 delete(dir.children, frag) 370 } 371 return nil 372 }) 373 // Match os.RemoveAll which returns a nil error even if the parent 374 // directories don't exist. 375 if oserror.IsNotExist(err) { 376 err = nil 377 } 378 return err 379 } 380 381 // Rename implements FS.Rename. 382 func (y *MemFS) Rename(oldname, newname string) error { 383 var n *memNode 384 err := y.walk(oldname, func(dir *memNode, frag string, final bool) error { 385 if final { 386 if frag == "" { 387 return errors.New("pebble/vfs: empty file name") 388 } 389 n = dir.children[frag] 390 delete(dir.children, frag) 391 } 392 return nil 393 }) 394 if err != nil { 395 return err 396 } 397 if n == nil { 398 return &os.PathError{ 399 Op: "open", 400 Path: oldname, 401 Err: oserror.ErrNotExist, 402 } 403 } 404 return y.walk(newname, func(dir *memNode, frag string, final bool) error { 405 if final { 406 if frag == "" { 407 return errors.New("pebble/vfs: empty file name") 408 } 409 dir.children[frag] = n 410 n.name = frag 411 } 412 return nil 413 }) 414 } 415 416 // ReuseForWrite implements FS.ReuseForWrite. 417 func (y *MemFS) ReuseForWrite(oldname, newname string) (File, error) { 418 if err := y.Rename(oldname, newname); err != nil { 419 return nil, err 420 } 421 f, err := y.Open(newname) 422 if err != nil { 423 return nil, err 424 } 425 y.mu.Lock() 426 defer y.mu.Unlock() 427 428 mf := f.(*memFile) 429 mf.read = false 430 mf.write = true 431 return f, nil 432 } 433 434 // MkdirAll implements FS.MkdirAll. 435 func (y *MemFS) MkdirAll(dirname string, perm os.FileMode) error { 436 return y.walk(dirname, func(dir *memNode, frag string, final bool) error { 437 if frag == "" { 438 if final { 439 return nil 440 } 441 return errors.New("pebble/vfs: empty file name") 442 } 443 child := dir.children[frag] 444 if child == nil { 445 dir.children[frag] = &memNode{ 446 name: frag, 447 children: make(map[string]*memNode), 448 isDir: true, 449 } 450 return nil 451 } 452 if !child.isDir { 453 return &os.PathError{ 454 Op: "open", 455 Path: dirname, 456 Err: errors.New("not a directory"), 457 } 458 } 459 return nil 460 }) 461 } 462 463 // Lock implements FS.Lock. 464 func (y *MemFS) Lock(fullname string) (io.Closer, error) { 465 // FS.Lock excludes other processes, but other processes cannot see this 466 // process' memory. However some uses (eg, Cockroach tests) may open and 467 // close the same MemFS-backed database multiple times. We want mutual 468 // exclusion in this case too. See cockroachdb/cockroach#110645. 469 _, loaded := y.lockedFiles.Swap(fullname, nil /* the value itself is insignificant */) 470 if loaded { 471 // This file lock has already been acquired. On unix, this results in 472 // either EACCES or EAGAIN so we mimic. 473 return nil, syscall.EAGAIN 474 } 475 // Otherwise, we successfully acquired the lock. Locks are visible in the 476 // parent directory listing, and they also must be created under an existent 477 // directory. Create the path so that we have the normal detection of 478 // non-existent directory paths, and make the lock visible when listing 479 // directory entries. 480 f, err := y.Create(fullname) 481 if err != nil { 482 // "Release" the lock since we failed. 483 y.lockedFiles.Delete(fullname) 484 return nil, err 485 } 486 return &memFileLock{ 487 y: y, 488 f: f, 489 fullname: fullname, 490 }, nil 491 } 492 493 // List implements FS.List. 494 func (y *MemFS) List(dirname string) ([]string, error) { 495 if !strings.HasSuffix(dirname, sep) { 496 dirname += sep 497 } 498 var ret []string 499 err := y.walk(dirname, func(dir *memNode, frag string, final bool) error { 500 if final { 501 if frag != "" { 502 panic("unreachable") 503 } 504 ret = make([]string, 0, len(dir.children)) 505 for s := range dir.children { 506 ret = append(ret, s) 507 } 508 } 509 return nil 510 }) 511 return ret, err 512 } 513 514 // Stat implements FS.Stat. 515 func (y *MemFS) Stat(name string) (os.FileInfo, error) { 516 f, err := y.Open(name) 517 if err != nil { 518 if pe, ok := err.(*os.PathError); ok { 519 pe.Op = "stat" 520 } 521 return nil, err 522 } 523 defer f.Close() 524 return f.Stat() 525 } 526 527 // PathBase implements FS.PathBase. 528 func (*MemFS) PathBase(p string) string { 529 // Note that MemFS uses forward slashes for its separator, hence the use of 530 // path.Base, not filepath.Base. 531 return path.Base(p) 532 } 533 534 // PathJoin implements FS.PathJoin. 535 func (*MemFS) PathJoin(elem ...string) string { 536 // Note that MemFS uses forward slashes for its separator, hence the use of 537 // path.Join, not filepath.Join. 538 return path.Join(elem...) 539 } 540 541 // PathDir implements FS.PathDir. 542 func (*MemFS) PathDir(p string) string { 543 // Note that MemFS uses forward slashes for its separator, hence the use of 544 // path.Dir, not filepath.Dir. 545 return path.Dir(p) 546 } 547 548 // GetDiskUsage implements FS.GetDiskUsage. 549 func (*MemFS) GetDiskUsage(string) (DiskUsage, error) { 550 return DiskUsage{}, ErrUnsupported 551 } 552 553 // memNode holds a file's data or a directory's children, and implements os.FileInfo. 554 type memNode struct { 555 name string 556 isDir bool 557 refs atomic.Int32 558 559 // Mutable state. 560 // - For a file: data, syncedDate, modTime: A file is only being mutated by a single goroutine, 561 // but there can be concurrent readers e.g. DB.Checkpoint() which can read WAL or MANIFEST 562 // files that are being written to. Additionally Sync() calls can be concurrent with writing. 563 // - For a directory: children and syncedChildren. Concurrent writes are possible, and 564 // these are protected using MemFS.mu. 565 mu struct { 566 sync.Mutex 567 data []byte 568 syncedData []byte 569 modTime time.Time 570 } 571 572 children map[string]*memNode 573 syncedChildren map[string]*memNode 574 } 575 576 func newRootMemNode() *memNode { 577 return &memNode{ 578 name: "/", // set the name to match what file systems do 579 children: make(map[string]*memNode), 580 isDir: true, 581 } 582 } 583 584 func (f *memNode) IsDir() bool { 585 return f.isDir 586 } 587 588 func (f *memNode) ModTime() time.Time { 589 f.mu.Lock() 590 defer f.mu.Unlock() 591 return f.mu.modTime 592 } 593 594 func (f *memNode) Mode() os.FileMode { 595 if f.isDir { 596 return os.ModeDir | 0755 597 } 598 return 0755 599 } 600 601 func (f *memNode) Name() string { 602 return f.name 603 } 604 605 func (f *memNode) Size() int64 { 606 f.mu.Lock() 607 defer f.mu.Unlock() 608 return int64(len(f.mu.data)) 609 } 610 611 func (f *memNode) Sys() interface{} { 612 return nil 613 } 614 615 func (f *memNode) dump(w *bytes.Buffer, level int) { 616 if f.isDir { 617 w.WriteString(" ") 618 } else { 619 f.mu.Lock() 620 fmt.Fprintf(w, "%8d ", len(f.mu.data)) 621 f.mu.Unlock() 622 } 623 for i := 0; i < level; i++ { 624 w.WriteString(" ") 625 } 626 w.WriteString(f.name) 627 if !f.isDir { 628 w.WriteByte('\n') 629 return 630 } 631 if level > 0 { // deal with the fact that the root's name is already "/" 632 w.WriteByte(sep[0]) 633 } 634 w.WriteByte('\n') 635 names := make([]string, 0, len(f.children)) 636 for name := range f.children { 637 names = append(names, name) 638 } 639 sort.Strings(names) 640 for _, name := range names { 641 f.children[name].dump(w, level+1) 642 } 643 } 644 645 func (f *memNode) resetToSyncedState() { 646 if f.isDir { 647 f.children = make(map[string]*memNode) 648 for k, v := range f.syncedChildren { 649 f.children[k] = v 650 } 651 for _, v := range f.children { 652 v.resetToSyncedState() 653 } 654 } else { 655 f.mu.Lock() 656 f.mu.data = append([]byte(nil), f.mu.syncedData...) 657 f.mu.Unlock() 658 } 659 } 660 661 // memFile is a reader or writer of a node's data, and implements File. 662 type memFile struct { 663 n *memNode 664 fs *MemFS // nil for a standalone memFile 665 rpos int 666 wpos int 667 read, write bool 668 } 669 670 var _ File = (*memFile)(nil) 671 672 func (f *memFile) Close() error { 673 if n := f.n.refs.Add(-1); n < 0 { 674 panic(fmt.Sprintf("pebble: close of unopened file: %d", n)) 675 } 676 f.n = nil 677 return nil 678 } 679 680 func (f *memFile) Read(p []byte) (int, error) { 681 if !f.read { 682 return 0, errors.New("pebble/vfs: file was not opened for reading") 683 } 684 if f.n.isDir { 685 return 0, errors.New("pebble/vfs: cannot read a directory") 686 } 687 f.n.mu.Lock() 688 defer f.n.mu.Unlock() 689 if f.rpos >= len(f.n.mu.data) { 690 return 0, io.EOF 691 } 692 n := copy(p, f.n.mu.data[f.rpos:]) 693 f.rpos += n 694 return n, nil 695 } 696 697 func (f *memFile) ReadAt(p []byte, off int64) (int, error) { 698 if !f.read { 699 return 0, errors.New("pebble/vfs: file was not opened for reading") 700 } 701 if f.n.isDir { 702 return 0, errors.New("pebble/vfs: cannot read a directory") 703 } 704 f.n.mu.Lock() 705 defer f.n.mu.Unlock() 706 if off >= int64(len(f.n.mu.data)) { 707 return 0, io.EOF 708 } 709 n := copy(p, f.n.mu.data[off:]) 710 if n < len(p) { 711 return n, io.EOF 712 } 713 return n, nil 714 } 715 716 func (f *memFile) Write(p []byte) (int, error) { 717 if !f.write { 718 return 0, errors.New("pebble/vfs: file was not created for writing") 719 } 720 if f.n.isDir { 721 return 0, errors.New("pebble/vfs: cannot write a directory") 722 } 723 f.n.mu.Lock() 724 defer f.n.mu.Unlock() 725 f.n.mu.modTime = time.Now() 726 if f.wpos+len(p) <= len(f.n.mu.data) { 727 n := copy(f.n.mu.data[f.wpos:f.wpos+len(p)], p) 728 if n != len(p) { 729 panic("stuff") 730 } 731 } else { 732 f.n.mu.data = append(f.n.mu.data[:f.wpos], p...) 733 } 734 f.wpos += len(p) 735 736 if invariants.Enabled { 737 // Mutate the input buffer to flush out bugs in Pebble which expect the 738 // input buffer to be unmodified. 739 for i := range p { 740 p[i] ^= 0xff 741 } 742 } 743 return len(p), nil 744 } 745 746 func (f *memFile) WriteAt(p []byte, ofs int64) (int, error) { 747 if !f.write { 748 return 0, errors.New("pebble/vfs: file was not created for writing") 749 } 750 if f.n.isDir { 751 return 0, errors.New("pebble/vfs: cannot write a directory") 752 } 753 f.n.mu.Lock() 754 defer f.n.mu.Unlock() 755 f.n.mu.modTime = time.Now() 756 757 for len(f.n.mu.data) < int(ofs)+len(p) { 758 f.n.mu.data = append(f.n.mu.data, 0) 759 } 760 761 n := copy(f.n.mu.data[int(ofs):int(ofs)+len(p)], p) 762 if n != len(p) { 763 panic("stuff") 764 } 765 766 return len(p), nil 767 } 768 769 func (f *memFile) Prefetch(offset int64, length int64) error { return nil } 770 func (f *memFile) Preallocate(offset, length int64) error { return nil } 771 772 func (f *memFile) Stat() (os.FileInfo, error) { 773 return f.n, nil 774 } 775 776 func (f *memFile) Sync() error { 777 if f.fs != nil && f.fs.strict { 778 f.fs.mu.Lock() 779 defer f.fs.mu.Unlock() 780 if f.fs.ignoreSyncs { 781 return nil 782 } 783 if f.n.isDir { 784 f.n.syncedChildren = make(map[string]*memNode) 785 for k, v := range f.n.children { 786 f.n.syncedChildren[k] = v 787 } 788 } else { 789 f.n.mu.Lock() 790 f.n.mu.syncedData = append([]byte(nil), f.n.mu.data...) 791 f.n.mu.Unlock() 792 } 793 } 794 return nil 795 } 796 797 func (f *memFile) SyncData() error { 798 return f.Sync() 799 } 800 801 func (f *memFile) SyncTo(length int64) (fullSync bool, err error) { 802 // NB: This SyncTo implementation lies, with its return values claiming it 803 // synced the data up to `length`. When fullSync=false, SyncTo provides no 804 // durability guarantees, so this can help surface bugs where we improperly 805 // rely on SyncTo providing durability. 806 return false, nil 807 } 808 809 func (f *memFile) Fd() uintptr { 810 return InvalidFd 811 } 812 813 // Flush is a no-op and present only to prevent buffering at higher levels 814 // (e.g. it prevents sstable.Writer from using a bufio.Writer). 815 func (f *memFile) Flush() error { 816 return nil 817 } 818 819 type memFileLock struct { 820 y *MemFS 821 f File 822 fullname string 823 } 824 825 func (l *memFileLock) Close() error { 826 if l.y == nil { 827 return nil 828 } 829 l.y.lockedFiles.Delete(l.fullname) 830 l.y = nil 831 return l.f.Close() 832 }