123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- /*
- * Public Domain Software
- *
- * I (Matthias Ladkau) am the author of the source code in this file.
- * I have placed the source code in this file in the public domain.
- *
- * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
- */
- /*
- Package lockutil contains a file based lock which can be used to lock file resources
- across different processes. The lock file is monitored by a Go routine. Invalidating
- the lock file (e.g. just writing a single character to it) causes the Go routine
- to exit. A client can check if the lockfile is still being monitored by calling
- WatcherRunning().
- */
- package lockutil
- import (
- "errors"
- "fmt"
- "os"
- "sync"
- "time"
- )
- /*
- LockFile data structure
- */
- type LockFile struct {
- filename string // Filename for LockFile
- timestamp int64 // Timestamp to uniquely indentify the lockfile
- interval time.Duration // Interval with which the file should be watched
- errorChan chan error // Error communication channel with watcher goroutine
- running bool // Flag to indicate that a lockfile is being watched
- mutex *sync.Mutex
- }
- /*
- NewLockFile creates a new LockFile which and watch it in given intervals.
- */
- func NewLockFile(filename string, interval time.Duration) *LockFile {
- return &LockFile{filename, time.Now().UnixNano(), interval, nil, false, &sync.Mutex{}}
- }
- /*
- watch is the internal watcher goroutine function.
- */
- func (lf *LockFile) watch() {
- // Attempt to read the lockfile - no error checking since the next write
- // lockfile call will catch any file related errors
- res, _ := lf.checkLockfile()
- if err := lf.writeLockfile(); err != nil {
- lf.errorChan <- err
- return
- }
- if res != 0 {
- time.Sleep(lf.interval * 10)
- // If we have overwritten an existing timestamp then check
- // if it was overwritten again by another process after some time
- res, err := lf.checkLockfile()
- if res != lf.timestamp || err != nil {
- lf.errorChan <- errors.New(fmt.Sprint(
- "Could not write lockfile - read result after writing: ", res,
- "(expected: ", lf.timestamp, ")", err))
- return
- }
- }
- // Signal that all is well
- lf.SetWatcherRunning(true)
- lf.errorChan <- nil
- for lf.WatcherRunning() {
- // Wakeup every interval and read the file
- time.Sleep(lf.interval)
- res, err := lf.checkLockfile()
- if err != nil {
- // Shut down if we get an error back
- lf.SetWatcherRunning(false)
- lf.errorChan <- err
- return
- }
- if res != lf.timestamp {
- // Attempt to write the timestamp again - no error checking
- // if it fails we'll try again next time
- lf.writeLockfile()
- }
- }
- // At this point lf.running is false - remove lockfile and return
- lf.errorChan <- os.Remove(lf.filename)
- }
- /*
- Write a timestamp to the lockfile
- */
- func (lf *LockFile) writeLockfile() error {
- lf.mutex.Lock()
- defer lf.mutex.Unlock()
- file, err := os.OpenFile(lf.filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
- if err != nil {
- return err
- }
- defer file.Close()
- data := make([]byte, 8)
- data[0] = byte(lf.timestamp >> 56)
- data[1] = byte(lf.timestamp >> 48)
- data[2] = byte(lf.timestamp >> 40)
- data[3] = byte(lf.timestamp >> 32)
- data[4] = byte(lf.timestamp >> 24)
- data[5] = byte(lf.timestamp >> 16)
- data[6] = byte(lf.timestamp >> 8)
- data[7] = byte(lf.timestamp >> 0)
- _, err = file.Write(data)
- return err
- }
- /*
- Try to read a timestamp from a lockfile
- */
- func (lf *LockFile) checkLockfile() (int64, error) {
- lf.mutex.Lock()
- defer lf.mutex.Unlock()
- file, err := os.OpenFile(lf.filename, os.O_RDONLY, 0660)
- if err != nil {
- if os.IsNotExist(err) {
- return 0, nil
- }
- return 0, err
- }
- defer file.Close()
- // Read timestamp
- timestamp := make([]byte, 8)
- i, err := file.Read(timestamp)
- if i != 8 {
- return 0, errors.New(fmt.Sprint("Unexpected timestamp value found in lockfile:", timestamp))
- }
- return (int64(timestamp[0]) << 56) |
- (int64(timestamp[1]) << 48) |
- (int64(timestamp[2]) << 40) |
- (int64(timestamp[3]) << 32) |
- (int64(timestamp[4]) << 24) |
- (int64(timestamp[5]) << 16) |
- (int64(timestamp[6]) << 8) |
- (int64(timestamp[7]) << 0), err
- }
- /*
- Start creates the lockfile and starts watching it.
- */
- func (lf *LockFile) Start() error {
- // Do nothing if the lockfile is already being watched
- if lf.WatcherRunning() {
- return nil
- }
- // Set the running flag and kick off the watcher goroutine
- lf.errorChan = make(chan error)
- go lf.watch()
- return <-lf.errorChan
- }
- /*
- WatcherRunning returns if the watcher goroutine is running.
- */
- func (lf *LockFile) WatcherRunning() bool {
- lf.mutex.Lock()
- defer lf.mutex.Unlock()
- return lf.running
- }
- /*
- SetWatcherRunning sets if the watcher goroutine is running.
- */
- func (lf *LockFile) SetWatcherRunning(state bool) {
- lf.mutex.Lock()
- defer lf.mutex.Unlock()
- lf.running = state
- }
- /*
- Finish watching a lockfile and return once the watcher goroutine has finished.
- */
- func (lf *LockFile) Finish() error {
- var err error
- // Do nothing if the lockfile is not being watched
- if !lf.WatcherRunning() {
- // Clean up if there is a channel still open
- if lf.errorChan != nil {
- err = <-lf.errorChan
- lf.errorChan = nil
- }
- return err
- }
- // Signale the watcher goroutine to stop
- lf.SetWatcherRunning(false)
- // Wait for the goroutine to finish
- err = <-lf.errorChan
- lf.errorChan = nil
- return err
- }
|