├── .gitignore
├── LICENSE
├── README.md
├── fslock.go
├── fslock_nix.go
├── fslock_test.go
└── fslock_windows.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | All files in this repository are licensed as follows. If you contribute
2 | to this repository, it is assumed that you license your contribution
3 | under the same license unless you state otherwise.
4 |
5 | All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
6 |
7 | This software is licensed under the LGPLv3, included below.
8 |
9 | As a special exception to the GNU Lesser General Public License version 3
10 | ("LGPL3"), the copyright holders of this Library give you permission to
11 | convey to a third party a Combined Work that links statically or dynamically
12 | to this Library without providing any Minimal Corresponding Source or
13 | Minimal Application Code as set out in 4d or providing the installation
14 | information set out in section 4e, provided that you comply with the other
15 | provisions of LGPL3 and provided that you meet, for the Application the
16 | terms and conditions of the license(s) which apply to the Application.
17 |
18 | Except as stated in this special exception, the provisions of LGPL3 will
19 | continue to comply in full to this Library. If you modify this Library, you
20 | may apply this exception to your version of this Library, but you are not
21 | obliged to do so. If you do not wish to do so, delete this exception
22 | statement from your version. This exception does not (and cannot) modify any
23 | license terms which apply to the Application, with which you must still
24 | comply.
25 |
26 |
27 | GNU LESSER GENERAL PUBLIC LICENSE
28 | Version 3, 29 June 2007
29 |
30 | Copyright (C) 2007 Free Software Foundation, Inc.
31 | Everyone is permitted to copy and distribute verbatim copies
32 | of this license document, but changing it is not allowed.
33 |
34 |
35 | This version of the GNU Lesser General Public License incorporates
36 | the terms and conditions of version 3 of the GNU General Public
37 | License, supplemented by the additional permissions listed below.
38 |
39 | 0. Additional Definitions.
40 |
41 | As used herein, "this License" refers to version 3 of the GNU Lesser
42 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
43 | General Public License.
44 |
45 | "The Library" refers to a covered work governed by this License,
46 | other than an Application or a Combined Work as defined below.
47 |
48 | An "Application" is any work that makes use of an interface provided
49 | by the Library, but which is not otherwise based on the Library.
50 | Defining a subclass of a class defined by the Library is deemed a mode
51 | of using an interface provided by the Library.
52 |
53 | A "Combined Work" is a work produced by combining or linking an
54 | Application with the Library. The particular version of the Library
55 | with which the Combined Work was made is also called the "Linked
56 | Version".
57 |
58 | The "Minimal Corresponding Source" for a Combined Work means the
59 | Corresponding Source for the Combined Work, excluding any source code
60 | for portions of the Combined Work that, considered in isolation, are
61 | based on the Application, and not on the Linked Version.
62 |
63 | The "Corresponding Application Code" for a Combined Work means the
64 | object code and/or source code for the Application, including any data
65 | and utility programs needed for reproducing the Combined Work from the
66 | Application, but excluding the System Libraries of the Combined Work.
67 |
68 | 1. Exception to Section 3 of the GNU GPL.
69 |
70 | You may convey a covered work under sections 3 and 4 of this License
71 | without being bound by section 3 of the GNU GPL.
72 |
73 | 2. Conveying Modified Versions.
74 |
75 | If you modify a copy of the Library, and, in your modifications, a
76 | facility refers to a function or data to be supplied by an Application
77 | that uses the facility (other than as an argument passed when the
78 | facility is invoked), then you may convey a copy of the modified
79 | version:
80 |
81 | a) under this License, provided that you make a good faith effort to
82 | ensure that, in the event an Application does not supply the
83 | function or data, the facility still operates, and performs
84 | whatever part of its purpose remains meaningful, or
85 |
86 | b) under the GNU GPL, with none of the additional permissions of
87 | this License applicable to that copy.
88 |
89 | 3. Object Code Incorporating Material from Library Header Files.
90 |
91 | The object code form of an Application may incorporate material from
92 | a header file that is part of the Library. You may convey such object
93 | code under terms of your choice, provided that, if the incorporated
94 | material is not limited to numerical parameters, data structure
95 | layouts and accessors, or small macros, inline functions and templates
96 | (ten or fewer lines in length), you do both of the following:
97 |
98 | a) Give prominent notice with each copy of the object code that the
99 | Library is used in it and that the Library and its use are
100 | covered by this License.
101 |
102 | b) Accompany the object code with a copy of the GNU GPL and this license
103 | document.
104 |
105 | 4. Combined Works.
106 |
107 | You may convey a Combined Work under terms of your choice that,
108 | taken together, effectively do not restrict modification of the
109 | portions of the Library contained in the Combined Work and reverse
110 | engineering for debugging such modifications, if you also do each of
111 | the following:
112 |
113 | a) Give prominent notice with each copy of the Combined Work that
114 | the Library is used in it and that the Library and its use are
115 | covered by this License.
116 |
117 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
118 | document.
119 |
120 | c) For a Combined Work that displays copyright notices during
121 | execution, include the copyright notice for the Library among
122 | these notices, as well as a reference directing the user to the
123 | copies of the GNU GPL and this license document.
124 |
125 | d) Do one of the following:
126 |
127 | 0) Convey the Minimal Corresponding Source under the terms of this
128 | License, and the Corresponding Application Code in a form
129 | suitable for, and under terms that permit, the user to
130 | recombine or relink the Application with a modified version of
131 | the Linked Version to produce a modified Combined Work, in the
132 | manner specified by section 6 of the GNU GPL for conveying
133 | Corresponding Source.
134 |
135 | 1) Use a suitable shared library mechanism for linking with the
136 | Library. A suitable mechanism is one that (a) uses at run time
137 | a copy of the Library already present on the user's computer
138 | system, and (b) will operate properly with a modified version
139 | of the Library that is interface-compatible with the Linked
140 | Version.
141 |
142 | e) Provide Installation Information, but only if you would otherwise
143 | be required to provide such information under section 6 of the
144 | GNU GPL, and only to the extent that such information is
145 | necessary to install and execute a modified version of the
146 | Combined Work produced by recombining or relinking the
147 | Application with a modified version of the Linked Version. (If
148 | you use option 4d0, the Installation Information must accompany
149 | the Minimal Corresponding Source and Corresponding Application
150 | Code. If you use option 4d1, you must provide the Installation
151 | Information in the manner specified by section 6 of the GNU GPL
152 | for conveying Corresponding Source.)
153 |
154 | 5. Combined Libraries.
155 |
156 | You may place library facilities that are a work based on the
157 | Library side by side in a single library together with other library
158 | facilities that are not Applications and are not covered by this
159 | License, and convey such a combined library under terms of your
160 | choice, if you do both of the following:
161 |
162 | a) Accompany the combined library with a copy of the same work based
163 | on the Library, uncombined with any other library facilities,
164 | conveyed under the terms of this License.
165 |
166 | b) Give prominent notice with the combined library that part of it
167 | is a work based on the Library, and explaining where to find the
168 | accompanying uncombined form of the same work.
169 |
170 | 6. Revised Versions of the GNU Lesser General Public License.
171 |
172 | The Free Software Foundation may publish revised and/or new versions
173 | of the GNU Lesser General Public License from time to time. Such new
174 | versions will be similar in spirit to the present version, but may
175 | differ in detail to address new problems or concerns.
176 |
177 | Each version is given a distinguishing version number. If the
178 | Library as you received it specifies that a certain numbered version
179 | of the GNU Lesser General Public License "or any later version"
180 | applies to it, you have the option of following the terms and
181 | conditions either of that published version or of any later version
182 | published by the Free Software Foundation. If the Library as you
183 | received it does not specify a version number of the GNU Lesser
184 | General Public License, you may choose any version of the GNU Lesser
185 | General Public License ever published by the Free Software Foundation.
186 |
187 | If the Library as you received it specifies that a proxy can decide
188 | whether future versions of the GNU Lesser General Public License shall
189 | apply, that proxy's public statement of acceptance of any version is
190 | permanent authorization for you to choose that version for the
191 | Library.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # fslock [](https://godoc.org/github.com/juju/fslock)
3 | fslock provides a cross-process mutex based on file locks that works on windows and *nix platforms.
4 |
5 |
6 | 
7 |
8 | image: [public domain](https://pixabay.com/en/encrypted-privacy-policy-445155/)
9 | (don't ask)
10 |
11 |
12 | fslock relies on LockFileEx on Windows and flock on \*nix systems. The timeout
13 | feature uses overlapped IO on Windows, but on \*nix platforms, timing out
14 | requires the use of a goroutine that will run until the lock is acquired,
15 | regardless of timeout. If you need to avoid this use of goroutines, poll
16 | TryLock in a loop.
17 |
18 |
19 |
20 | ## Variables
21 | ``` go
22 | var ErrLocked error = trylockError("fslock is already locked")
23 | ```
24 | ErrLocked indicates TryLock failed because the lock was already locked.
25 |
26 | ``` go
27 | var ErrTimeout error = timeoutError("lock timeout exceeded")
28 | ```
29 | ErrTimeout indicates that the lock attempt timed out.
30 |
31 |
32 | ## type Lock
33 | ``` go
34 | type Lock struct {
35 | // contains filtered or unexported fields
36 | }
37 | ```
38 | Lock implements cross-process locks using syscalls.
39 |
40 |
41 | ### func New
42 | ``` go
43 | func New(filename string) *Lock
44 | ```
45 | New returns a new lock around the given file.
46 |
47 |
48 | ### func (\*Lock) Lock
49 | ``` go
50 | func (l *Lock) Lock() error
51 | ```
52 | Lock locks the lock. This call will block until the lock is available.
53 |
54 | ### func (\*Lock) LockWithTimeout
55 | ``` go
56 | func (l *Lock) LockWithTimeout(timeout time.Duration) error
57 | ```
58 | LockWithTimeout tries to lock the lock until the timeout expires. If the
59 | timeout expires, this method will return ErrTimeout.
60 |
61 | ### func (\*Lock) TryLock
62 | ``` go
63 | func (l *Lock) TryLock() error
64 | ```
65 | TryLock attempts to lock the lock. This method will return ErrLocked
66 | immediately if the lock cannot be acquired.
67 |
68 | ### func (\*Lock) Unlock
69 | ``` go
70 | func (l *Lock) Unlock() error
71 | ```
72 | Unlock unlocks the lock.
73 |
74 |
75 |
--------------------------------------------------------------------------------
/fslock.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Canonical Ltd.
2 | // Licensed under the LGPLv3, see LICENCE file for details.
3 |
4 | // Package fslock provides a cross-process mutex based on file locks.
5 | //
6 | // It is built on top of flock for linux and darwin, and LockFileEx on Windows.
7 | package fslock
8 |
9 | // ErrTimeout indicates that the lock attempt timed out.
10 | var ErrTimeout error = timeoutError("lock timeout exceeded")
11 |
12 | type timeoutError string
13 |
14 | func (t timeoutError) Error() string {
15 | return string(t)
16 | }
17 | func (timeoutError) Timeout() bool {
18 | return true
19 | }
20 |
21 | // ErrLocked indicates TryLock failed because the lock was already locked.
22 | var ErrLocked error = trylockError("fslock is already locked")
23 |
24 | type trylockError string
25 |
26 | func (t trylockError) Error() string {
27 | return string(t)
28 | }
29 |
30 | func (trylockError) Temporary() bool {
31 | return true
32 | }
33 |
--------------------------------------------------------------------------------
/fslock_nix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Canonical Ltd.
2 | // Licensed under the LGPLv3, see LICENCE file for details.
3 |
4 | // +build darwin dragonfly freebsd linux netbsd openbsd
5 |
6 | package fslock
7 |
8 | import (
9 | "syscall"
10 | "time"
11 | )
12 |
13 | // Lock implements cross-process locks using syscalls.
14 | // This implementation is based on flock syscall.
15 | type Lock struct {
16 | filename string
17 | fd int
18 | }
19 |
20 | // New returns a new lock around the given file.
21 | func New(filename string) *Lock {
22 | return &Lock{filename: filename}
23 | }
24 |
25 | // Lock locks the lock. This call will block until the lock is available.
26 | func (l *Lock) Lock() error {
27 | if err := l.open(); err != nil {
28 | return err
29 | }
30 | return syscall.Flock(l.fd, syscall.LOCK_EX)
31 | }
32 |
33 | // TryLock attempts to lock the lock. This method will return ErrLocked
34 | // immediately if the lock cannot be acquired.
35 | func (l *Lock) TryLock() error {
36 | if err := l.open(); err != nil {
37 | return err
38 | }
39 | err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
40 | if err != nil {
41 | syscall.Close(l.fd)
42 | }
43 | if err == syscall.EWOULDBLOCK {
44 | return ErrLocked
45 | }
46 | return err
47 | }
48 |
49 | func (l *Lock) open() error {
50 | fd, err := syscall.Open(l.filename, syscall.O_CREAT|syscall.O_RDONLY, 0600)
51 | if err != nil {
52 | return err
53 | }
54 | l.fd = fd
55 | return nil
56 | }
57 |
58 | // Unlock unlocks the lock.
59 | func (l *Lock) Unlock() error {
60 | return syscall.Close(l.fd)
61 | }
62 |
63 | // LockWithTimeout tries to lock the lock until the timeout expires. If the
64 | // timeout expires, this method will return ErrTimeout.
65 | func (l *Lock) LockWithTimeout(timeout time.Duration) error {
66 | if err := l.open(); err != nil {
67 | return err
68 | }
69 | result := make(chan error)
70 | cancel := make(chan struct{})
71 | go func() {
72 | err := syscall.Flock(l.fd, syscall.LOCK_EX)
73 | select {
74 | case <-cancel:
75 | // Timed out, cleanup if necessary.
76 | syscall.Flock(l.fd, syscall.LOCK_UN)
77 | syscall.Close(l.fd)
78 | case result <- err:
79 | }
80 | }()
81 | select {
82 | case err := <-result:
83 | return err
84 | case <-time.After(timeout):
85 | close(cancel)
86 | return ErrTimeout
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/fslock_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Canonical Ltd.
2 | // Licensed under the LGPLv3, see LICENCE file for details.
3 |
4 | package fslock_test
5 |
6 | import (
7 | "fmt"
8 | "os"
9 | "os/exec"
10 | "path/filepath"
11 | "runtime"
12 | "sync"
13 | "sync/atomic"
14 | "testing"
15 | "time"
16 |
17 | gc "gopkg.in/check.v1"
18 |
19 | "github.com/juju/fslock"
20 | )
21 |
22 | func Test(t *testing.T) {
23 | gc.TestingT(t)
24 | }
25 |
26 | const (
27 | shortWait = 10 * time.Millisecond
28 | longWait = 10 * shortWait
29 | )
30 |
31 | type fslockSuite struct{}
32 |
33 | var _ = gc.Suite(&fslockSuite{})
34 |
35 | func (s *fslockSuite) TestLockNoContention(c *gc.C) {
36 | path := filepath.Join(c.MkDir(), "testing")
37 | lock := fslock.New(path)
38 |
39 | started := make(chan struct{})
40 | acquired := make(chan struct{})
41 | go func() {
42 | close(started)
43 | err := lock.Lock()
44 | close(acquired)
45 | c.Assert(err, gc.IsNil)
46 | }()
47 |
48 | select {
49 | case <-started:
50 | // good, goroutine started.
51 | case <-time.After(shortWait * 2):
52 | c.Fatalf("timeout waiting for goroutine to start")
53 | }
54 |
55 | select {
56 | case <-acquired:
57 | // got the lock. good.
58 | case <-time.After(shortWait * 2):
59 | c.Fatalf("Timed out waiting for lock acquisition.")
60 | }
61 |
62 | err := lock.Unlock()
63 | c.Assert(err, gc.IsNil)
64 | }
65 |
66 | func (s *fslockSuite) TestLockBlocks(c *gc.C) {
67 | path := filepath.Join(c.MkDir(), "testing")
68 | lock := fslock.New(path)
69 |
70 | kill := make(chan struct{})
71 |
72 | // this will block until the other process has the lock.
73 | procDone := LockFromAnotherProc(c, path, kill)
74 |
75 | defer func() {
76 | close(kill)
77 | // now wait for the other process to exit so the file will be unlocked.
78 | select {
79 | case <-procDone:
80 | case <-time.After(time.Second):
81 | }
82 | }()
83 |
84 | started := make(chan struct{})
85 | acquired := make(chan struct{})
86 | go func() {
87 | close(started)
88 | err := lock.Lock()
89 | close(acquired)
90 | lock.Unlock()
91 | c.Assert(err, gc.IsNil)
92 | }()
93 |
94 | select {
95 | case <-started:
96 | // good, goroutine started.
97 | case <-time.After(shortWait * 2):
98 | c.Fatalf("timeout waiting for goroutine to start")
99 | }
100 |
101 | // Waiting for something not to happen is inherently hard...
102 | select {
103 | case <-acquired:
104 | c.Fatalf("Unexpected lock acquisition")
105 | case <-time.After(shortWait * 2):
106 | // all good.
107 | }
108 | }
109 |
110 | func (s *fslockSuite) TestTryLock(c *gc.C) {
111 | lock := fslock.New(filepath.Join(c.MkDir(), "testing"))
112 |
113 | err := lock.TryLock()
114 | c.Assert(err, gc.IsNil)
115 | lock.Unlock()
116 | }
117 |
118 | func (s *fslockSuite) TestTryLockNoBlock(c *gc.C) {
119 | path := filepath.Join(c.MkDir(), "testing")
120 | lock := fslock.New(path)
121 |
122 | kill := make(chan struct{})
123 |
124 | // this will block until the other process has the lock.
125 | procDone := LockFromAnotherProc(c, path, kill)
126 |
127 | defer func() {
128 | close(kill)
129 | // now wait for the other process to exit so the file will be unlocked.
130 | select {
131 | case <-procDone:
132 | case <-time.After(time.Second):
133 | }
134 | }()
135 |
136 | started := make(chan struct{})
137 | result := make(chan error)
138 | go func() {
139 | close(started)
140 | result <- lock.TryLock()
141 | }()
142 |
143 | select {
144 | case <-started:
145 | // good, goroutine started.
146 | case <-time.After(shortWait):
147 | c.Fatalf("timeout waiting for goroutine to start")
148 | }
149 |
150 | // Wait for trylock to fail.
151 | select {
152 | case err := <-result:
153 | // yes, I know this is redundant with the assert below, but it makes the
154 | // failed test message clearer.
155 | if err == nil {
156 | c.Fatalf("lock succeeded, but should have errored out")
157 | }
158 | // This should be the error from trylock failing.
159 | c.Assert(err, gc.Equals, fslock.ErrLocked)
160 | case <-time.After(shortWait):
161 | c.Fatalf("took too long to fail trylock")
162 | }
163 | }
164 |
165 | func (s *fslockSuite) TestUnlockedWithTimeout(c *gc.C) {
166 | lock := fslock.New(filepath.Join(c.MkDir(), "testing"))
167 |
168 | err := lock.LockWithTimeout(shortWait)
169 | c.Assert(err, gc.IsNil)
170 | lock.Unlock()
171 | }
172 |
173 | func (s *fslockSuite) TestLockWithTimeout(c *gc.C) {
174 | path := filepath.Join(c.MkDir(), "testing")
175 | lock := fslock.New(path)
176 | defer lock.Unlock()
177 |
178 | kill := make(chan struct{})
179 |
180 | // this will block until the other process has the lock.
181 | procDone := LockFromAnotherProc(c, path, kill)
182 |
183 | defer func() {
184 | close(kill)
185 | // now wait for the other process to exit so the file will be unlocked.
186 | select {
187 | case <-procDone:
188 | case <-time.After(time.Second):
189 | }
190 | }()
191 |
192 | started := make(chan struct{})
193 | result := make(chan error)
194 | go func() {
195 | close(started)
196 | result <- lock.LockWithTimeout(shortWait)
197 | }()
198 |
199 | select {
200 | case <-started:
201 | // good, goroutine started.
202 | case <-time.After(shortWait * 2):
203 | c.Fatalf("timeout waiting for goroutine to start")
204 | }
205 |
206 | // Wait for timeout.
207 | select {
208 | case err := <-result:
209 | // yes, I know this is redundant with the assert below, but it makes the
210 | // failed test message clearer.
211 | if err == nil {
212 | c.Fatalf("lock succeeded, but should have timed out")
213 | }
214 | // This should be the error from the lock timing out.
215 | c.Assert(err, gc.Equals, fslock.ErrTimeout)
216 | case <-time.After(shortWait * 2):
217 | c.Fatalf("lock took too long to timeout")
218 | }
219 | }
220 |
221 | func (s *fslockSuite) TestStress(c *gc.C) {
222 | const lockAttempts = 200
223 | const concurrentLocks = 10
224 |
225 | var counter = new(int64)
226 | // Use atomics to update lockState to make sure the lock isn't held by
227 | // someone else. A value of 1 means locked, 0 means unlocked.
228 | var lockState = new(int32)
229 |
230 | var wg sync.WaitGroup
231 |
232 | dir := c.MkDir()
233 |
234 | var stress = func(name string) {
235 | defer wg.Done()
236 | lock := fslock.New(filepath.Join(dir, "testing"))
237 | for i := 0; i < lockAttempts; i++ {
238 | err := lock.Lock()
239 | c.Assert(err, gc.IsNil)
240 | state := atomic.AddInt32(lockState, 1)
241 | c.Assert(state, gc.Equals, int32(1))
242 | // Tell the go routine scheduler to give a slice to someone else
243 | // while we have this locked.
244 | runtime.Gosched()
245 | // need to decrement prior to unlock to avoid the race of someone
246 | // else grabbing the lock before we decrement the state.
247 | atomic.AddInt32(lockState, -1)
248 | err = lock.Unlock()
249 | c.Assert(err, gc.IsNil)
250 | // increment the general counter
251 | atomic.AddInt64(counter, 1)
252 | }
253 | }
254 |
255 | for i := 0; i < concurrentLocks; i++ {
256 | wg.Add(1)
257 | go stress(fmt.Sprintf("Lock %d", i))
258 | }
259 | wg.Wait()
260 | c.Assert(*counter, gc.Equals, int64(lockAttempts*concurrentLocks))
261 | }
262 |
263 | // LockFromAnotherProc will launch a process and block until that process has
264 | // created the lock file. If we time out waiting for the other process to take
265 | // the lock, this function will fail the current test.
266 | func LockFromAnotherProc(c *gc.C, path string, kill chan struct{}) (done chan struct{}) {
267 | cmd := exec.Command(os.Args[0], "-test.run", "TestLockFromOtherProcess")
268 | cmd.Env = append(
269 | // We must preserve os.Environ() on Windows,
270 | // or the subprocess will fail in weird and
271 | // wonderful ways.
272 | os.Environ(),
273 | "FSLOCK_TEST_HELPER_WANTED=1",
274 | "FSLOCK_TEST_HELPER_PATH="+path,
275 | )
276 |
277 | if err := cmd.Start(); err != nil {
278 | c.Fatalf("error starting other proc: %v", err)
279 | }
280 |
281 | done = make(chan struct{})
282 |
283 | go func() {
284 | cmd.Wait()
285 | close(done)
286 | }()
287 |
288 | go func() {
289 | select {
290 | case <-kill:
291 | // this may fail, but there's not much we can do about it
292 | _ = cmd.Process.Kill()
293 | case <-done:
294 | }
295 | }()
296 |
297 | for x := 0; x < 10; x++ {
298 | time.Sleep(shortWait)
299 | if _, err := os.Stat(path); err == nil {
300 | // file created by other process, let's continue
301 | break
302 | }
303 | if x == 9 {
304 | c.Fatalf("timed out waiting for other process to start")
305 | }
306 | }
307 | return done
308 | }
309 |
310 | func TestLockFromOtherProcess(t *testing.T) {
311 | if os.Getenv("FSLOCK_TEST_HELPER_WANTED") == "" {
312 | return
313 | }
314 | filename := os.Getenv("FSLOCK_TEST_HELPER_PATH")
315 | lock := fslock.New(filename)
316 | err := lock.Lock()
317 | if err != nil {
318 | fmt.Fprintf(os.Stderr, "error locking %q: %v", filename, err)
319 | os.Exit(1)
320 | }
321 | time.Sleep(longWait)
322 | err = lock.Unlock()
323 | if err != nil {
324 | fmt.Fprintf(os.Stderr, "error unlocking %q: %v", filename, err)
325 | os.Exit(1)
326 | }
327 | os.Exit(0)
328 | }
329 |
--------------------------------------------------------------------------------
/fslock_windows.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Canonical Ltd.
2 | // Licensed under the LGPLv3, see LICENCE file for details.
3 |
4 | package fslock
5 |
6 | import (
7 | "log"
8 | "syscall"
9 | "time"
10 | "unsafe"
11 | )
12 |
13 | var (
14 | modkernel32 = syscall.NewLazyDLL("kernel32.dll")
15 | procLockFileEx = modkernel32.NewProc("LockFileEx")
16 | procCreateEventW = modkernel32.NewProc("CreateEventW")
17 | )
18 |
19 | const (
20 | lockfileExclusiveLock = 2
21 | fileFlagNormal = 0x00000080
22 | )
23 |
24 | func init() {
25 | log.SetFlags(log.Lmicroseconds | log.Ldate)
26 | }
27 |
28 | // Lock implements cross-process locks using syscalls.
29 | // This implementation is based on LockFileEx syscall.
30 | type Lock struct {
31 | filename string
32 | handle syscall.Handle
33 | }
34 |
35 | // New returns a new lock around the given file.
36 | func New(filename string) *Lock {
37 | return &Lock{filename: filename}
38 | }
39 |
40 | // TryLock attempts to lock the lock. This method will return ErrLocked
41 | // immediately if the lock cannot be acquired.
42 | func (l *Lock) TryLock() error {
43 | err := l.LockWithTimeout(0)
44 | if err == ErrTimeout {
45 | // in our case, timing out immediately just means it was already locked.
46 | return ErrLocked
47 | }
48 | return err
49 | }
50 |
51 | // Lock locks the lock. This call will block until the lock is available.
52 | func (l *Lock) Lock() error {
53 | return l.LockWithTimeout(-1)
54 | }
55 |
56 | // Unlock unlocks the lock.
57 | func (l *Lock) Unlock() error {
58 | return syscall.Close(l.handle)
59 | }
60 |
61 | // LockWithTimeout tries to lock the lock until the timeout expires. If the
62 | // timeout expires, this method will return ErrTimeout.
63 | func (l *Lock) LockWithTimeout(timeout time.Duration) (err error) {
64 | name, err := syscall.UTF16PtrFromString(l.filename)
65 | if err != nil {
66 | return err
67 | }
68 |
69 | // Open for asynchronous I/O so that we can timeout waiting for the lock.
70 | // Also open shared so that other processes can open the file (but will
71 | // still need to lock it).
72 | handle, err := syscall.CreateFile(
73 | name,
74 | syscall.GENERIC_READ,
75 | syscall.FILE_SHARE_READ,
76 | nil,
77 | syscall.OPEN_ALWAYS,
78 | syscall.FILE_FLAG_OVERLAPPED|fileFlagNormal,
79 | 0)
80 | if err != nil {
81 | return err
82 | }
83 | l.handle = handle
84 | defer func() {
85 | if err != nil {
86 | syscall.Close(handle)
87 | }
88 | }()
89 |
90 | millis := uint32(syscall.INFINITE)
91 | if timeout >= 0 {
92 | millis = uint32(timeout.Nanoseconds() / 1000000)
93 | }
94 |
95 | ol, err := newOverlapped()
96 | if err != nil {
97 | return err
98 | }
99 | defer syscall.CloseHandle(ol.HEvent)
100 | err = lockFileEx(handle, lockfileExclusiveLock, 0, 1, 0, ol)
101 | if err == nil {
102 | return nil
103 | }
104 |
105 | // ERROR_IO_PENDING is expected when we're waiting on an asychronous event
106 | // to occur.
107 | if err != syscall.ERROR_IO_PENDING {
108 | return err
109 | }
110 | s, err := syscall.WaitForSingleObject(ol.HEvent, millis)
111 |
112 | switch s {
113 | case syscall.WAIT_OBJECT_0:
114 | // success!
115 | return nil
116 | case syscall.WAIT_TIMEOUT:
117 | return ErrTimeout
118 | default:
119 | return err
120 | }
121 | }
122 |
123 | // newOverlapped creates a structure used to track asynchronous
124 | // I/O requests that have been issued.
125 | func newOverlapped() (*syscall.Overlapped, error) {
126 | event, err := createEvent(nil, true, false, nil)
127 | if err != nil {
128 | return nil, err
129 | }
130 | return &syscall.Overlapped{HEvent: event}, nil
131 | }
132 |
133 | func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
134 | r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
135 | if r1 == 0 {
136 | if e1 != 0 {
137 | err = error(e1)
138 | } else {
139 | err = syscall.EINVAL
140 | }
141 | }
142 | return
143 | }
144 |
145 | func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
146 | var _p0 uint32
147 | if manualReset {
148 | _p0 = 1
149 | }
150 | var _p1 uint32
151 | if initialState {
152 | _p1 = 1
153 | }
154 |
155 | r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(sa)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
156 | handle = syscall.Handle(r0)
157 | if handle == syscall.InvalidHandle {
158 | if e1 != 0 {
159 | err = error(e1)
160 | } else {
161 | err = syscall.EINVAL
162 | }
163 | }
164 | return
165 | }
166 |
--------------------------------------------------------------------------------