storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/fs-v1-rwpool.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016 MinIO, Inc. 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 cmd 18 19 import ( 20 "os" 21 pathutil "path" 22 "sync" 23 24 "storj.io/minio/cmd/logger" 25 "storj.io/minio/pkg/lock" 26 ) 27 28 // fsIOPool represents a protected list to keep track of all 29 // the concurrent readers at a given path. 30 type fsIOPool struct { 31 sync.Mutex 32 readersMap map[string]*lock.RLockedFile 33 } 34 35 // lookupToRead - looks up an fd from readers map and 36 // returns read locked fd for caller to read from, if 37 // fd found increments the reference count. If the fd 38 // is found to be closed then purges it from the 39 // readersMap and returns nil instead. 40 // 41 // NOTE: this function is not protected and it is callers 42 // responsibility to lock this call to be thread safe. For 43 // implementation ideas look at the usage inside Open() call. 44 func (fsi *fsIOPool) lookupToRead(path string) (*lock.RLockedFile, bool) { 45 rlkFile, ok := fsi.readersMap[path] 46 // File reference exists on map, validate if its 47 // really closed and we are safe to purge it. 48 if ok && rlkFile != nil { 49 // If the file is closed and not removed from map is a bug. 50 if rlkFile.IsClosed() { 51 // Log this as an error. 52 reqInfo := (&logger.ReqInfo{}).AppendTags("path", path) 53 ctx := logger.SetReqInfo(GlobalContext, reqInfo) 54 logger.LogIf(ctx, errUnexpected) 55 56 // Purge the cached lock path from map. 57 delete(fsi.readersMap, path) 58 59 // Indicate that we can populate the new fd. 60 ok = false 61 } else { 62 // Increment the lock ref, since the file is not closed yet 63 // and caller requested to read the file again. 64 rlkFile.IncLockRef() 65 } 66 } 67 return rlkFile, ok 68 } 69 70 // Open is a wrapper call to read locked file which 71 // returns a ReadAtCloser. 72 // 73 // ReaderAt is provided so that the fd is non seekable, since 74 // we are sharing fd's with concurrent threads, we don't want 75 // all readers to change offsets on each other during such 76 // concurrent operations. Using ReadAt allows us to read from 77 // any offsets. 78 // 79 // Closer is implemented to track total readers and to close 80 // only when there no more readers, the fd is purged if the lock 81 // count has reached zero. 82 func (fsi *fsIOPool) Open(path string) (*lock.RLockedFile, error) { 83 if err := checkPathLength(path); err != nil { 84 return nil, err 85 } 86 87 fsi.Lock() 88 rlkFile, ok := fsi.lookupToRead(path) 89 fsi.Unlock() 90 // Locked path reference doesn't exist, acquire a read lock again on the file. 91 if !ok { 92 // Open file for reading with read lock. 93 newRlkFile, err := lock.RLockedOpenFile(path) 94 if err != nil { 95 switch { 96 case osIsNotExist(err): 97 return nil, errFileNotFound 98 case osIsPermission(err): 99 return nil, errFileAccessDenied 100 case isSysErrIsDir(err): 101 return nil, errIsNotRegular 102 case isSysErrNotDir(err): 103 return nil, errFileAccessDenied 104 case isSysErrPathNotFound(err): 105 return nil, errFileNotFound 106 default: 107 return nil, err 108 } 109 } 110 111 /// Save new reader on the map. 112 113 // It is possible by this time due to concurrent 114 // i/o we might have another lock present. Lookup 115 // again to check for such a possibility. If no such 116 // file exists save the newly opened fd, if not 117 // reuse the existing fd and close the newly opened 118 // file 119 fsi.Lock() 120 rlkFile, ok = fsi.lookupToRead(path) 121 if ok { 122 // Close the new fd, since we already seem to have 123 // an active reference. 124 newRlkFile.Close() 125 } else { 126 // Save the new rlk file. 127 rlkFile = newRlkFile 128 } 129 130 // Save the new fd on the map. 131 fsi.readersMap[path] = rlkFile 132 fsi.Unlock() 133 134 } 135 136 // Success. 137 return rlkFile, nil 138 } 139 140 // Write - Attempt to lock the file if it exists, 141 // - if the file exists. Then we try to get a write lock this 142 // will block if we can't get a lock perhaps another write 143 // or read is in progress. Concurrent calls are protected 144 // by the global namspace lock within the same process. 145 func (fsi *fsIOPool) Write(path string) (wlk *lock.LockedFile, err error) { 146 if err = checkPathLength(path); err != nil { 147 return nil, err 148 } 149 150 wlk, err = lock.LockedOpenFile(path, os.O_RDWR, 0666) 151 if err != nil { 152 switch { 153 case osIsNotExist(err): 154 return nil, errFileNotFound 155 case osIsPermission(err): 156 return nil, errFileAccessDenied 157 case isSysErrIsDir(err): 158 return nil, errIsNotRegular 159 default: 160 if isSysErrPathNotFound(err) { 161 return nil, errFileNotFound 162 } 163 return nil, err 164 } 165 } 166 return wlk, nil 167 } 168 169 // Create - creates a new write locked file instance. 170 // - if the file doesn't exist. We create the file and hold lock. 171 func (fsi *fsIOPool) Create(path string) (wlk *lock.LockedFile, err error) { 172 if err = checkPathLength(path); err != nil { 173 return nil, err 174 } 175 176 // Creates parent if missing. 177 if err = mkdirAll(pathutil.Dir(path), 0777); err != nil { 178 return nil, err 179 } 180 181 // Attempt to create the file. 182 wlk, err = lock.LockedOpenFile(path, os.O_RDWR|os.O_CREATE, 0666) 183 if err != nil { 184 switch { 185 case osIsPermission(err): 186 return nil, errFileAccessDenied 187 case isSysErrIsDir(err): 188 return nil, errIsNotRegular 189 case isSysErrPathNotFound(err): 190 return nil, errFileAccessDenied 191 default: 192 return nil, err 193 } 194 } 195 196 // Success. 197 return wlk, nil 198 } 199 200 // Close implements closing the path referenced by the reader in such 201 // a way that it makes sure to remove entry from the map immediately 202 // if no active readers are present. 203 func (fsi *fsIOPool) Close(path string) error { 204 fsi.Lock() 205 defer fsi.Unlock() 206 207 if err := checkPathLength(path); err != nil { 208 return err 209 } 210 211 // Pop readers from path. 212 rlkFile, ok := fsi.readersMap[path] 213 if !ok { 214 return nil 215 } 216 217 // Close the reader. 218 rlkFile.Close() 219 220 // If the file is closed, remove it from the reader pool map. 221 if rlkFile.IsClosed() { 222 223 // Purge the cached lock path from map. 224 delete(fsi.readersMap, path) 225 } 226 227 // Success. 228 return nil 229 }