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  }