github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/readerutil/opener.go (about) 1 /* 2 Copyright 2013 The Camlistore 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 readerutil 18 19 import ( 20 "os" 21 "sync" 22 23 "camlistore.org/pkg/singleflight" 24 "camlistore.org/pkg/types" 25 ) 26 27 var ( 28 openerGroup singleflight.Group 29 30 openFileMu sync.Mutex // guards openFiles 31 openFiles = make(map[string]*openFile) 32 ) 33 34 type openFile struct { 35 *os.File 36 path string // map key of openFiles 37 refCount int 38 } 39 40 type openFileHandle struct { 41 closed bool 42 *openFile 43 } 44 45 func (f *openFileHandle) Close() error { 46 openFileMu.Lock() 47 if f.closed { 48 openFileMu.Unlock() 49 return nil 50 } 51 f.closed = true 52 f.refCount-- 53 if f.refCount < 0 { 54 panic("unexpected negative refcount") 55 } 56 zero := f.refCount == 0 57 if zero { 58 delete(openFiles, f.path) 59 } 60 openFileMu.Unlock() 61 if !zero { 62 return nil 63 } 64 return f.openFile.File.Close() 65 } 66 67 type openingFile struct { 68 path string 69 mu sync.RWMutex // write-locked until Open is done 70 71 // Results, once mu is unlocked: 72 of *openFile 73 err error 74 } 75 76 // OpenSingle opens the given file path for reading, reusing existing file descriptors 77 // when possible. 78 func OpenSingle(path string) (types.ReaderAtCloser, error) { 79 openFileMu.Lock() 80 of := openFiles[path] 81 if of != nil { 82 of.refCount++ 83 openFileMu.Unlock() 84 return &openFileHandle{false, of}, nil 85 } 86 openFileMu.Unlock() // release the lock while we call os.Open 87 88 winner := false // this goroutine made it into Do's func 89 90 // Returns an *openFile 91 resi, err := openerGroup.Do(path, func() (interface{}, error) { 92 winner = true 93 f, err := os.Open(path) 94 if err != nil { 95 return nil, err 96 } 97 of := &openFile{ 98 File: f, 99 path: path, 100 refCount: 1, 101 } 102 openFileMu.Lock() 103 openFiles[path] = of 104 openFileMu.Unlock() 105 return of, nil 106 }) 107 if err != nil { 108 return nil, err 109 } 110 of = resi.(*openFile) 111 112 // If our os.Open was dup-suppressed, we have to increment our 113 // reference count. 114 if !winner { 115 openFileMu.Lock() 116 if of.refCount == 0 { 117 // Winner already closed it. Try again (rare). 118 openFileMu.Unlock() 119 return OpenSingle(path) 120 } 121 of.refCount++ 122 openFileMu.Unlock() 123 } 124 return &openFileHandle{false, of}, nil 125 }