You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

flock_unix.go 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Copyright 2015 Tim Heckman. All rights reserved.
  2. // Use of this source code is governed by the BSD 3-Clause
  3. // license that can be found in the LICENSE file.
  4. // +build !aix,!windows
  5. package flock
  6. import (
  7. "os"
  8. "syscall"
  9. )
  10. // Lock is a blocking call to try and take an exclusive file lock. It will wait
  11. // until it is able to obtain the exclusive file lock. It's recommended that
  12. // TryLock() be used over this function. This function may block the ability to
  13. // query the current Locked() or RLocked() status due to a RW-mutex lock.
  14. //
  15. // If we are already exclusive-locked, this function short-circuits and returns
  16. // immediately assuming it can take the mutex lock.
  17. //
  18. // If the *Flock has a shared lock (RLock), this may transparently replace the
  19. // shared lock with an exclusive lock on some UNIX-like operating systems. Be
  20. // careful when using exclusive locks in conjunction with shared locks
  21. // (RLock()), because calling Unlock() may accidentally release the exclusive
  22. // lock that was once a shared lock.
  23. func (f *Flock) Lock() error {
  24. return f.lock(&f.l, syscall.LOCK_EX)
  25. }
  26. // RLock is a blocking call to try and take a shared file lock. It will wait
  27. // until it is able to obtain the shared file lock. It's recommended that
  28. // TryRLock() be used over this function. This function may block the ability to
  29. // query the current Locked() or RLocked() status due to a RW-mutex lock.
  30. //
  31. // If we are already shared-locked, this function short-circuits and returns
  32. // immediately assuming it can take the mutex lock.
  33. func (f *Flock) RLock() error {
  34. return f.lock(&f.r, syscall.LOCK_SH)
  35. }
  36. func (f *Flock) lock(locked *bool, flag int) error {
  37. f.m.Lock()
  38. defer f.m.Unlock()
  39. if *locked {
  40. return nil
  41. }
  42. if f.fh == nil {
  43. if err := f.setFh(); err != nil {
  44. return err
  45. }
  46. defer f.ensureFhState()
  47. }
  48. if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil {
  49. shouldRetry, reopenErr := f.reopenFDOnError(err)
  50. if reopenErr != nil {
  51. return reopenErr
  52. }
  53. if !shouldRetry {
  54. return err
  55. }
  56. if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil {
  57. return err
  58. }
  59. }
  60. *locked = true
  61. return nil
  62. }
  63. // Unlock is a function to unlock the file. This file takes a RW-mutex lock, so
  64. // while it is running the Locked() and RLocked() functions will be blocked.
  65. //
  66. // This function short-circuits if we are unlocked already. If not, it calls
  67. // syscall.LOCK_UN on the file and closes the file descriptor. It does not
  68. // remove the file from disk. It's up to your application to do.
  69. //
  70. // Please note, if your shared lock became an exclusive lock this may
  71. // unintentionally drop the exclusive lock if called by the consumer that
  72. // believes they have a shared lock. Please see Lock() for more details.
  73. func (f *Flock) Unlock() error {
  74. f.m.Lock()
  75. defer f.m.Unlock()
  76. // if we aren't locked or if the lockfile instance is nil
  77. // just return a nil error because we are unlocked
  78. if (!f.l && !f.r) || f.fh == nil {
  79. return nil
  80. }
  81. // mark the file as unlocked
  82. if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil {
  83. return err
  84. }
  85. f.fh.Close()
  86. f.l = false
  87. f.r = false
  88. f.fh = nil
  89. return nil
  90. }
  91. // TryLock is the preferred function for taking an exclusive file lock. This
  92. // function takes an RW-mutex lock before it tries to lock the file, so there is
  93. // the possibility that this function may block for a short time if another
  94. // goroutine is trying to take any action.
  95. //
  96. // The actual file lock is non-blocking. If we are unable to get the exclusive
  97. // file lock, the function will return false instead of waiting for the lock. If
  98. // we get the lock, we also set the *Flock instance as being exclusive-locked.
  99. func (f *Flock) TryLock() (bool, error) {
  100. return f.try(&f.l, syscall.LOCK_EX)
  101. }
  102. // TryRLock is the preferred function for taking a shared file lock. This
  103. // function takes an RW-mutex lock before it tries to lock the file, so there is
  104. // the possibility that this function may block for a short time if another
  105. // goroutine is trying to take any action.
  106. //
  107. // The actual file lock is non-blocking. If we are unable to get the shared file
  108. // lock, the function will return false instead of waiting for the lock. If we
  109. // get the lock, we also set the *Flock instance as being share-locked.
  110. func (f *Flock) TryRLock() (bool, error) {
  111. return f.try(&f.r, syscall.LOCK_SH)
  112. }
  113. func (f *Flock) try(locked *bool, flag int) (bool, error) {
  114. f.m.Lock()
  115. defer f.m.Unlock()
  116. if *locked {
  117. return true, nil
  118. }
  119. if f.fh == nil {
  120. if err := f.setFh(); err != nil {
  121. return false, err
  122. }
  123. defer f.ensureFhState()
  124. }
  125. var retried bool
  126. retry:
  127. err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB)
  128. switch err {
  129. case syscall.EWOULDBLOCK:
  130. return false, nil
  131. case nil:
  132. *locked = true
  133. return true, nil
  134. }
  135. if !retried {
  136. if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil {
  137. return false, reopenErr
  138. } else if shouldRetry {
  139. retried = true
  140. goto retry
  141. }
  142. }
  143. return false, err
  144. }
  145. // reopenFDOnError determines whether we should reopen the file handle
  146. // in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c:
  147. // Since Linux 3.4 (commit 55725513)
  148. // Probably NFSv4 where flock() is emulated by fcntl().
  149. func (f *Flock) reopenFDOnError(err error) (bool, error) {
  150. if err != syscall.EIO && err != syscall.EBADF {
  151. return false, nil
  152. }
  153. if st, err := f.fh.Stat(); err == nil {
  154. // if the file is able to be read and written
  155. if st.Mode()&0600 == 0600 {
  156. f.fh.Close()
  157. f.fh = nil
  158. // reopen in read-write mode and set the filehandle
  159. fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600))
  160. if err != nil {
  161. return false, err
  162. }
  163. f.fh = fh
  164. return true, nil
  165. }
  166. }
  167. return false, nil
  168. }