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  }