lockfile.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. /*
  2. * Public Domain Software
  3. *
  4. * I (Matthias Ladkau) am the author of the source code in this file.
  5. * I have placed the source code in this file in the public domain.
  6. *
  7. * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. /*
  10. Package lockutil contains a file based lock which can be used to lock file resources
  11. across different processes. The lock file is monitored by a Go routine. Invalidating
  12. the lock file (e.g. just writing a single character to it) causes the Go routine
  13. to exit. A client can check if the lockfile is still being monitored by calling
  14. WatcherRunning().
  15. */
  16. package lockutil
  17. import (
  18. "errors"
  19. "fmt"
  20. "os"
  21. "time"
  22. )
  23. /*
  24. LockFile data structure
  25. */
  26. type LockFile struct {
  27. filename string // Filename for LockFile
  28. timestamp int64 // Timestamp to uniquely indentify the lockfile
  29. interval time.Duration // Interval with which the file should be watched
  30. errorChan chan error // Error communication channel with watcher goroutine
  31. running bool // Flag to indicate that a lockfile is being watched
  32. }
  33. /*
  34. NewLockFile creates a new LockFile which and watch it in given intervals.
  35. */
  36. func NewLockFile(filename string, interval time.Duration) *LockFile {
  37. return &LockFile{filename, time.Now().UnixNano(), interval, nil, false}
  38. }
  39. /*
  40. watch is the internal watcher goroutine function.
  41. */
  42. func (lf *LockFile) watch() {
  43. // Attempt to read the lockfile - no error checking since the next write
  44. // lockfile call will catch any file related errors
  45. res, _ := lf.checkLockfile()
  46. if err := lf.writeLockfile(); err != nil {
  47. lf.errorChan <- err
  48. return
  49. }
  50. if res != 0 {
  51. time.Sleep(lf.interval * 10)
  52. // If we have overwritten an existing timestamp then check
  53. // if it was overwritten again by another process after some time
  54. res, err := lf.checkLockfile()
  55. if res != lf.timestamp || err != nil {
  56. lf.errorChan <- errors.New(fmt.Sprint(
  57. "Could not write lockfile - read result after writing: ", res,
  58. "(expected: ", lf.timestamp, ")", err))
  59. return
  60. }
  61. }
  62. // Signal that all is well
  63. lf.running = true
  64. lf.errorChan <- nil
  65. for lf.running {
  66. // Wakeup every interval and read the file
  67. time.Sleep(lf.interval)
  68. res, err := lf.checkLockfile()
  69. if err != nil {
  70. // Shut down if we get an error back
  71. lf.running = false
  72. lf.errorChan <- err
  73. return
  74. }
  75. if res != lf.timestamp {
  76. // Attempt to write the timestamp again - no error checking
  77. // if it fails we'll try again next time
  78. lf.writeLockfile()
  79. }
  80. }
  81. // At this point lf.running is false - remove lockfile and return
  82. lf.errorChan <- os.Remove(lf.filename)
  83. }
  84. /*
  85. Write a timestamp to the lockfile
  86. */
  87. func (lf *LockFile) writeLockfile() error {
  88. file, err := os.OpenFile(lf.filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
  89. if err != nil {
  90. return err
  91. }
  92. defer file.Close()
  93. data := make([]byte, 8)
  94. data[0] = byte(lf.timestamp >> 56)
  95. data[1] = byte(lf.timestamp >> 48)
  96. data[2] = byte(lf.timestamp >> 40)
  97. data[3] = byte(lf.timestamp >> 32)
  98. data[4] = byte(lf.timestamp >> 24)
  99. data[5] = byte(lf.timestamp >> 16)
  100. data[6] = byte(lf.timestamp >> 8)
  101. data[7] = byte(lf.timestamp >> 0)
  102. _, err = file.Write(data)
  103. return err
  104. }
  105. /*
  106. Try to read a timestamp from a lockfile
  107. */
  108. func (lf *LockFile) checkLockfile() (int64, error) {
  109. file, err := os.OpenFile(lf.filename, os.O_RDONLY, 0660)
  110. if err != nil {
  111. if os.IsNotExist(err) {
  112. return 0, nil
  113. }
  114. return 0, err
  115. }
  116. defer file.Close()
  117. // Read timestamp
  118. timestamp := make([]byte, 8)
  119. i, err := file.Read(timestamp)
  120. if i != 8 {
  121. return 0, errors.New(fmt.Sprint("Unexpected timestamp value found in lockfile:", timestamp))
  122. }
  123. return (int64(timestamp[0]) << 56) |
  124. (int64(timestamp[1]) << 48) |
  125. (int64(timestamp[2]) << 40) |
  126. (int64(timestamp[3]) << 32) |
  127. (int64(timestamp[4]) << 24) |
  128. (int64(timestamp[5]) << 16) |
  129. (int64(timestamp[6]) << 8) |
  130. (int64(timestamp[7]) << 0), err
  131. }
  132. /*
  133. Start creates the lockfile and starts watching it.
  134. */
  135. func (lf *LockFile) Start() error {
  136. // Do nothing if the lockfile is already being watched
  137. if lf.running {
  138. return nil
  139. }
  140. // Set the running flag and kick off the watcher goroutine
  141. lf.errorChan = make(chan error)
  142. go lf.watch()
  143. return <-lf.errorChan
  144. }
  145. /*
  146. WatcherRunning returns if the watcher goroutine is running.
  147. */
  148. func (lf *LockFile) WatcherRunning() bool {
  149. return lf.running
  150. }
  151. /*
  152. Finish watching a lockfile and return once the watcher goroutine has finished.
  153. */
  154. func (lf *LockFile) Finish() error {
  155. var err error
  156. // Do nothing if the lockfile is not being watched
  157. if !lf.running {
  158. // Clean up if there is a channel still open
  159. if lf.errorChan != nil {
  160. err = <-lf.errorChan
  161. lf.errorChan = nil
  162. }
  163. return err
  164. }
  165. // Signale the watcher goroutine to stop
  166. lf.running = false
  167. // Wait for the goroutine to finish
  168. err = <-lf.errorChan
  169. lf.errorChan = nil
  170. return err
  171. }