github.com/anacrolix/torrent@v1.61.0/storage/file-handle-cache.go (about) 1 package storage 2 3 import ( 4 "cmp" 5 "expvar" 6 "fmt" 7 "io" 8 "maps" 9 "net/http" 10 "os" 11 "slices" 12 "sync" 13 "sync/atomic" 14 ) 15 16 var ( 17 sharedFiles sharedFilesInterface = regularFsSharedFiles{} 18 19 wipSharedFilesPool = sharedFilesType{m: make(map[string]*sharedFile)} 20 ) 21 22 type regularFsSharedFiles struct{} 23 24 func (me regularFsSharedFiles) CloseAll(name string) error { 25 // We don't track regular file handles, we will assume the client is synchronizing things 26 // correctly so that an open handle won't be around. 27 return nil 28 } 29 30 func (me regularFsSharedFiles) Open(name string) (sharedFileIf, error) { 31 return os.Open(name) 32 } 33 34 type sharedFileIf interface { 35 io.ReaderAt 36 io.Closer 37 } 38 39 type sharedFilesInterface interface { 40 Open(name string) (sharedFileIf, error) 41 CloseAll(name string) error 42 } 43 44 func init() { 45 http.HandleFunc("/debug/shared-files", func(w http.ResponseWriter, r *http.Request) { 46 wipSharedFilesPool.WriteDebug(w) 47 }) 48 } 49 50 type sharedFilesType struct { 51 mu sync.Mutex 52 m map[string]*sharedFile 53 } 54 55 func (sharedFiles *sharedFilesType) WriteDebug(w io.Writer) { 56 sharedFiles.mu.Lock() 57 defer sharedFiles.mu.Unlock() 58 byRefs := slices.SortedFunc(maps.Keys(sharedFiles.m), func(a, b string) int { 59 return cmp.Or( 60 sharedFiles.m[b].refs-sharedFiles.m[a].refs, 61 cmp.Compare(a, b)) 62 }) 63 for _, key := range byRefs { 64 sf := sharedFiles.m[key] 65 fmt.Fprintf(w, "%v: refs=%v, name=%v\n", key, sf.refs, sf.f.Name()) 66 } 67 } 68 69 // How many opens wouldn't have been needed with singleflight. 70 var sharedFilesWastedOpens = expvar.NewInt("sharedFilesWastedOpens") 71 72 func (me *sharedFilesType) Open(name string) (ret *sharedFileRef, err error) { 73 me.mu.Lock() 74 sf, ok := me.m[name] 75 if !ok { 76 me.mu.Unlock() 77 // Can singleflight here... 78 var f *os.File 79 f, err = os.Open(name) 80 if err != nil { 81 return 82 } 83 me.mu.Lock() 84 sf, ok = me.m[name] 85 if ok { 86 sharedFilesWastedOpens.Add(1) 87 f.Close() 88 } else { 89 sf = &sharedFile{pool: me, f: f} 90 me.m[name] = sf 91 } 92 } 93 ret = sf.newRef() 94 me.mu.Unlock() 95 return 96 } 97 98 type sharedFile struct { 99 pool *sharedFilesType 100 f *os.File 101 // Could do this with weakrefs... Wonder if it works well with OS resources like that. 102 refs int 103 } 104 105 func (me *sharedFile) newRef() *sharedFileRef { 106 me.refs++ 107 return &sharedFileRef{ 108 sf: me, 109 inherit: me.f, 110 } 111 } 112 113 type inherit interface { 114 io.ReaderAt 115 } 116 117 type sharedFileRef struct { 118 // Only methods that are safe for concurrent use. 119 inherit 120 sf *sharedFile 121 closed atomic.Bool 122 } 123 124 func (me *sharedFileRef) Close() (err error) { 125 if !me.closed.CompareAndSwap(false, true) { 126 return 127 } 128 me.inherit = nil 129 me.sf.pool.mu.Lock() 130 me.sf.refs-- 131 me.sf.pool.mu.Unlock() 132 me.sf = nil 133 return 134 }