This repository has been archived by the owner on Apr 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 35
/
fslock_windows.go
165 lines (145 loc) · 3.85 KB
/
fslock_windows.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright 2016 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.
package fslock
import (
"log"
"syscall"
"time"
"unsafe"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procLockFileEx = modkernel32.NewProc("LockFileEx")
procCreateEventW = modkernel32.NewProc("CreateEventW")
)
const (
lockfileExclusiveLock = 2
fileFlagNormal = 0x00000080
)
func init() {
log.SetFlags(log.Lmicroseconds | log.Ldate)
}
// Lock implements cross-process locks using syscalls.
// This implementation is based on LockFileEx syscall.
type Lock struct {
filename string
handle syscall.Handle
}
// New returns a new lock around the given file.
func New(filename string) *Lock {
return &Lock{filename: filename}
}
// TryLock attempts to lock the lock. This method will return ErrLocked
// immediately if the lock cannot be acquired.
func (l *Lock) TryLock() error {
err := l.LockWithTimeout(0)
if err == ErrTimeout {
// in our case, timing out immediately just means it was already locked.
return ErrLocked
}
return err
}
// Lock locks the lock. This call will block until the lock is available.
func (l *Lock) Lock() error {
return l.LockWithTimeout(-1)
}
// Unlock unlocks the lock.
func (l *Lock) Unlock() error {
return syscall.Close(l.handle)
}
// LockWithTimeout tries to lock the lock until the timeout expires. If the
// timeout expires, this method will return ErrTimeout.
func (l *Lock) LockWithTimeout(timeout time.Duration) (err error) {
name, err := syscall.UTF16PtrFromString(l.filename)
if err != nil {
return err
}
// Open for asynchronous I/O so that we can timeout waiting for the lock.
// Also open shared so that other processes can open the file (but will
// still need to lock it).
handle, err := syscall.CreateFile(
name,
syscall.GENERIC_READ,
syscall.FILE_SHARE_READ,
nil,
syscall.OPEN_ALWAYS,
syscall.FILE_FLAG_OVERLAPPED|fileFlagNormal,
0)
if err != nil {
return err
}
l.handle = handle
defer func() {
if err != nil {
syscall.Close(handle)
}
}()
millis := uint32(syscall.INFINITE)
if timeout >= 0 {
millis = uint32(timeout.Nanoseconds() / 1000000)
}
ol, err := newOverlapped()
if err != nil {
return err
}
defer syscall.CloseHandle(ol.HEvent)
err = lockFileEx(handle, lockfileExclusiveLock, 0, 1, 0, ol)
if err == nil {
return nil
}
// ERROR_IO_PENDING is expected when we're waiting on an asychronous event
// to occur.
if err != syscall.ERROR_IO_PENDING {
return err
}
s, err := syscall.WaitForSingleObject(ol.HEvent, millis)
switch s {
case syscall.WAIT_OBJECT_0:
// success!
return nil
case syscall.WAIT_TIMEOUT:
return ErrTimeout
default:
return err
}
}
// newOverlapped creates a structure used to track asynchronous
// I/O requests that have been issued.
func newOverlapped() (*syscall.Overlapped, error) {
event, err := createEvent(nil, true, false, nil)
if err != nil {
return nil, err
}
return &syscall.Overlapped{HEvent: event}, nil
}
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createEvent(sa *syscall.SecurityAttributes, manualReset bool, initialState bool, name *uint16) (handle syscall.Handle, err error) {
var _p0 uint32
if manualReset {
_p0 = 1
}
var _p1 uint32
if initialState {
_p1 = 1
}
r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4, uintptr(unsafe.Pointer(sa)), uintptr(_p0), uintptr(_p1), uintptr(unsafe.Pointer(name)), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}