github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/flock/flock_unix.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package flock implements a simple POSIX file-based advisory lock.
     6  package flock
     7  
     8  import (
     9  	"context"
    10  	"sync"
    11  	"syscall"
    12  
    13  	"github.com/Schaudge/grailbase/log"
    14  )
    15  
    16  type T struct {
    17  	name string
    18  	fd   int
    19  	mu   sync.Mutex
    20  }
    21  
    22  // New creates an object that locks the given path.
    23  func New(path string) *T {
    24  	return &T{name: path}
    25  }
    26  
    27  // Lock locks the file. Iff Lock() returns nil, the caller must call Unlock()
    28  // later.
    29  func (f *T) Lock(ctx context.Context) (err error) {
    30  	reqCh := make(chan func() error, 2)
    31  	doneCh := make(chan error)
    32  	go func() {
    33  		var err error
    34  		for req := range reqCh {
    35  			if err == nil {
    36  				err = req()
    37  			}
    38  			doneCh <- err
    39  		}
    40  	}()
    41  	reqCh <- f.doLock
    42  	select {
    43  	case <-ctx.Done():
    44  		reqCh <- f.doUnlock
    45  		err = ctx.Err()
    46  	case err = <-doneCh:
    47  	}
    48  	close(reqCh)
    49  	return err
    50  }
    51  
    52  // Unlock unlocks the file.
    53  func (f *T) Unlock() error {
    54  	return f.doUnlock()
    55  }
    56  
    57  func (f *T) doLock() error {
    58  	f.mu.Lock() // Serialize the lock within one process.
    59  
    60  	var err error
    61  	f.fd, err = syscall.Open(f.name, syscall.O_CREAT|syscall.O_RDWR, 0777)
    62  	if err != nil {
    63  		f.mu.Unlock()
    64  		return err
    65  	}
    66  	err = syscall.Flock(f.fd, syscall.LOCK_EX|syscall.LOCK_NB)
    67  	for err == syscall.EWOULDBLOCK || err == syscall.EAGAIN {
    68  		log.Printf("waiting for lock %s", f.name)
    69  		err = syscall.Flock(f.fd, syscall.LOCK_EX)
    70  	}
    71  	if err != nil {
    72  		f.mu.Unlock()
    73  	}
    74  	return err
    75  }
    76  
    77  func (f *T) doUnlock() error {
    78  	err := syscall.Flock(f.fd, syscall.LOCK_UN)
    79  	if err := syscall.Close(f.fd); err != nil {
    80  		log.Error.Printf("close %s: %v", f.name, err)
    81  	}
    82  	f.mu.Unlock()
    83  	return err
    84  }