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  }