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  }