github.com/go4org/go4@v0.0.0-20200104003542-c7e774b10ea0/readerutil/singlereader/opener.go (about) 1 /* 2 Copyright 2013 The Go4 Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // package singlereader provides Open and Close operations, reusing existing 18 // file descriptors when possible. 19 package singlereader // import "go4.org/readerutil/singlereader" 20 21 import ( 22 "sync" 23 24 "go4.org/readerutil" 25 "go4.org/syncutil/singleflight" 26 "go4.org/wkfs" 27 ) 28 29 var ( 30 openerGroup singleflight.Group 31 32 openFileMu sync.Mutex // guards openFiles 33 openFiles = make(map[string]*openFile) 34 ) 35 36 type openFile struct { 37 wkfs.File 38 path string // map key of openFiles 39 refCount int 40 } 41 42 type openFileHandle struct { 43 closed bool 44 *openFile 45 } 46 47 func (f *openFileHandle) Close() error { 48 openFileMu.Lock() 49 if f.closed { 50 openFileMu.Unlock() 51 return nil 52 } 53 f.closed = true 54 f.refCount-- 55 if f.refCount < 0 { 56 panic("unexpected negative refcount") 57 } 58 zero := f.refCount == 0 59 if zero { 60 delete(openFiles, f.path) 61 } 62 openFileMu.Unlock() 63 if !zero { 64 return nil 65 } 66 return f.openFile.File.Close() 67 } 68 69 // Open opens the given file path for reading, reusing existing file descriptors 70 // when possible. 71 func Open(path string) (readerutil.ReaderAtCloser, error) { 72 openFileMu.Lock() 73 of := openFiles[path] 74 if of != nil { 75 of.refCount++ 76 openFileMu.Unlock() 77 return &openFileHandle{false, of}, nil 78 } 79 openFileMu.Unlock() // release the lock while we call os.Open 80 81 winner := false // this goroutine made it into Do's func 82 83 // Returns an *openFile 84 resi, err := openerGroup.Do(path, func() (interface{}, error) { 85 winner = true 86 f, err := wkfs.Open(path) 87 if err != nil { 88 return nil, err 89 } 90 of := &openFile{ 91 File: f, 92 path: path, 93 refCount: 1, 94 } 95 openFileMu.Lock() 96 openFiles[path] = of 97 openFileMu.Unlock() 98 return of, nil 99 }) 100 if err != nil { 101 return nil, err 102 } 103 of = resi.(*openFile) 104 105 // If our os.Open was dup-suppressed, we have to increment our 106 // reference count. 107 if !winner { 108 openFileMu.Lock() 109 if of.refCount == 0 { 110 // Winner already closed it. Try again (rare). 111 openFileMu.Unlock() 112 return Open(path) 113 } 114 of.refCount++ 115 openFileMu.Unlock() 116 } 117 return &openFileHandle{false, of}, nil 118 }