github.com/searKing/golang/go@v1.2.117/sync/filelock/filelock_fcntl.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build aix || (solaris && !illumos)
     6  
     7  // This code implements the filelock API using POSIX 'fcntl' locks, which attach
     8  // to an (inode, process) pair rather than a file descriptor. To avoid unlocking
     9  // files prematurely when the same file is opened through different descriptors,
    10  // we allow only one read-lock at a time.
    11  //
    12  // Most platforms provide some alternative API, such as an 'flock' system call
    13  // or an F_OFD_SETLK command for 'fcntl', that allows for better concurrency and
    14  // does not require per-inode bookkeeping in the application.
    15  
    16  // Copied from go/gc/src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go
    17  
    18  package filelock
    19  
    20  import (
    21  	"errors"
    22  	"io"
    23  	"io/fs"
    24  	"math/rand"
    25  	"sync"
    26  	"syscall"
    27  	"time"
    28  )
    29  
    30  type lockType int16
    31  
    32  const (
    33  	readLock  lockType = syscall.F_RDLCK
    34  	writeLock lockType = syscall.F_WRLCK
    35  )
    36  
    37  type inode = uint64 // type of syscall.Stat_t.Ino
    38  
    39  type inodeLock struct {
    40  	owner File
    41  	queue []<-chan File
    42  }
    43  
    44  var (
    45  	mu     sync.Mutex
    46  	inodes = map[File]inode{}
    47  	locks  = map[inode]inodeLock{}
    48  )
    49  
    50  func lock(f File, lt lockType, try bool) (err error) {
    51  	// POSIX locks apply per inode and process, and the lock for an inode is
    52  	// released when *any* descriptor for that inode is closed. So we need to
    53  	// synchronize access to each inode internally, and must serialize lock and
    54  	// unlock calls that refer to the same inode through different descriptors.
    55  	fi, err := f.Stat()
    56  	if err != nil {
    57  		return err
    58  	}
    59  	ino := fi.Sys().(*syscall.Stat_t).Ino
    60  
    61  	mu.Lock()
    62  	if i, dup := inodes[f]; dup && i != ino {
    63  		mu.Unlock()
    64  		return &fs.PathError{
    65  			Op:   lt.String(),
    66  			Path: f.Name(),
    67  			Err:  errors.New("inode for file changed since last Lock or RLock"),
    68  		}
    69  	}
    70  	inodes[f] = ino
    71  
    72  	var wait chan File
    73  	l := locks[ino]
    74  	if l.owner == f {
    75  		// This file already owns the lock, but the call may change its lock type.
    76  	} else if l.owner == nil {
    77  		// No owner: it's ours now.
    78  		l.owner = f
    79  	} else {
    80  		// Already owned: add a channel to wait on.
    81  		wait = make(chan File)
    82  		l.queue = append(l.queue, wait)
    83  	}
    84  	locks[ino] = l
    85  	mu.Unlock()
    86  
    87  	if wait != nil {
    88  		wait <- f
    89  	}
    90  
    91  	// Spurious EDEADLK errors arise on platforms that compute deadlock graphs at
    92  	// the process, rather than thread, level. Consider processes P and Q, with
    93  	// threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be
    94  	// reported as a deadlock on systems that consider only process granularity:
    95  	//
    96  	// 	P.1 locks file A.
    97  	// 	Q.3 locks file B.
    98  	// 	Q.3 blocks on file A.
    99  	// 	P.2 blocks on file B. (This is erroneously reported as a deadlock.)
   100  	// 	P.1 unlocks file A.
   101  	// 	Q.3 unblocks and locks file A.
   102  	// 	Q.3 unlocks files A and B.
   103  	// 	P.2 unblocks and locks file B.
   104  	// 	P.2 unlocks file B.
   105  	//
   106  	// These spurious errors were observed in practice on AIX and Solaris in
   107  	// cmd/go: see https://golang.org/issue/32817.
   108  	//
   109  	// We work around this bug by treating EDEADLK as always spurious. If there
   110  	// really is a lock-ordering bug between the interacting processes, it will
   111  	// become a livelock instead, but that's not appreciably worse than if we had
   112  	// a proper flock implementation (which generally does not even attempt to
   113  	// diagnose deadlocks).
   114  	//
   115  	// In the above example, that changes the trace to:
   116  	//
   117  	// 	P.1 locks file A.
   118  	// 	Q.3 locks file B.
   119  	// 	Q.3 blocks on file A.
   120  	// 	P.2 spuriously fails to lock file B and goes to sleep.
   121  	// 	P.1 unlocks file A.
   122  	// 	Q.3 unblocks and locks file A.
   123  	// 	Q.3 unlocks files A and B.
   124  	// 	P.2 wakes up and locks file B.
   125  	// 	P.2 unlocks file B.
   126  	//
   127  	// We know that the retry loop will not introduce a *spurious* livelock
   128  	// because, according to the POSIX specification, EDEADLK is only to be
   129  	// returned when “the lock is blocked by a lock from another process”.
   130  	// If that process is blocked on some lock that we are holding, then the
   131  	// resulting livelock is due to a real deadlock (and would manifest as such
   132  	// when using, for example, the flock implementation of this package).
   133  	// If the other process is *not* blocked on some other lock that we are
   134  	// holding, then it will eventually release the requested lock.
   135  
   136  	nextSleep := 1 * time.Millisecond
   137  	const maxSleep = 500 * time.Millisecond
   138  	for {
   139  		err = setlkw(f.Fd(), lt)
   140  		if err != syscall.EDEADLK {
   141  			break
   142  		}
   143  		if try {
   144  			break
   145  		}
   146  		time.Sleep(nextSleep)
   147  
   148  		nextSleep += nextSleep
   149  		if nextSleep > maxSleep {
   150  			nextSleep = maxSleep
   151  		}
   152  		// Apply 10% jitter to avoid synchronizing collisions when we finally unblock.
   153  		nextSleep += time.Duration((0.1*rand.Float64() - 0.05) * float64(nextSleep))
   154  	}
   155  
   156  	if err != nil && err != syscall.EDEADLK {
   157  		unlock(f)
   158  		return &fs.PathError{
   159  			Op:   lt.String(),
   160  			Path: f.Name(),
   161  			Err:  err,
   162  		}
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func unlock(f File) error {
   169  	var owner File
   170  
   171  	mu.Lock()
   172  	ino, ok := inodes[f]
   173  	if ok {
   174  		owner = locks[ino].owner
   175  	}
   176  	mu.Unlock()
   177  
   178  	if owner != f {
   179  		panic("unlock called on a file that is not locked")
   180  	}
   181  
   182  	err := setlkw(f.Fd(), syscall.F_UNLCK)
   183  
   184  	mu.Lock()
   185  	l := locks[ino]
   186  	if len(l.queue) == 0 {
   187  		// No waiters: remove the map entry.
   188  		delete(locks, ino)
   189  	} else {
   190  		// The first waiter is sending us their file now.
   191  		// Receive it and update the queue.
   192  		l.owner = <-l.queue[0]
   193  		l.queue = l.queue[1:]
   194  		locks[ino] = l
   195  	}
   196  	delete(inodes, f)
   197  	mu.Unlock()
   198  
   199  	return err
   200  }
   201  
   202  // setlkw calls FcntlFlock with F_SETLKW for the entire file indicated by fd.
   203  func setlkw(fd uintptr, lt lockType) error {
   204  	for {
   205  		err := syscall.FcntlFlock(fd, syscall.F_SETLKW, &syscall.Flock_t{
   206  			Type:   int16(lt),
   207  			Whence: io.SeekStart,
   208  			Start:  0,
   209  			Len:    0, // All bytes.
   210  		})
   211  		if err != syscall.EINTR {
   212  			return err
   213  		}
   214  	}
   215  }