New upstream release.
Debian Janitor
2 years ago
0 | 0 | language: go |
1 | 1 | go: |
2 | - 1.4.3 | |
3 | - 1.6.2 | |
2 | - 1.13 | |
3 | - 1.14 | |
4 | 4 | - tip |
5 | 5 | |
6 | 6 | # Only test commits to production branch and all pull requests |
0 | golang-github-nightlyone-lockfile (0.0~git20170804.6a197d5-2) UNRELEASED; urgency=medium | |
0 | golang-github-nightlyone-lockfile (1.0.0-1) UNRELEASED; urgency=medium | |
1 | 1 | |
2 | 2 | [ Alexandre Viau ] |
3 | 3 | * Point Vcs-* urls to salsa.debian.org. |
5 | 5 | [ Debian Janitor ] |
6 | 6 | * Remove constraints unnecessary since stretch: |
7 | 7 | + Build-Depends: Drop versioned constraint on debhelper. |
8 | * New upstream release. | |
8 | 9 | |
9 | -- Alexandre Viau <aviau@debian.org> Mon, 02 Apr 2018 19:38:45 -0400 | |
10 | -- Alexandre Viau <aviau@debian.org> Fri, 19 Nov 2021 20:26:03 -0000 | |
10 | 11 | |
11 | 12 | golang-github-nightlyone-lockfile (0.0~git20170804.6a197d5-1) unstable; urgency=medium |
12 | 13 |
25 | 25 | // Temporary returns always true. |
26 | 26 | // It exists, so you can detect it via |
27 | 27 | // if te, ok := err.(interface{ Temporary() bool }); ok { |
28 | // fmt.Println("I am a temporay error situation, so wait and retry") | |
28 | // fmt.Println("I am a temporary error situation, so wait and retry") | |
29 | 29 | // } |
30 | 30 | func (t TemporaryError) Temporary() bool { return true } |
31 | 31 | |
44 | 44 | if !filepath.IsAbs(path) { |
45 | 45 | return Lockfile(""), ErrNeedAbsPath |
46 | 46 | } |
47 | ||
47 | 48 | return Lockfile(path), nil |
48 | 49 | } |
49 | 50 | |
62 | 63 | if err != nil { |
63 | 64 | return nil, err |
64 | 65 | } |
66 | ||
65 | 67 | running, err := isRunning(pid) |
66 | 68 | if err != nil { |
67 | 69 | return nil, err |
72 | 74 | if err != nil { |
73 | 75 | return nil, err |
74 | 76 | } |
77 | ||
75 | 78 | return proc, nil |
76 | 79 | } |
80 | ||
77 | 81 | return nil, ErrDeadOwner |
78 | ||
79 | 82 | } |
80 | 83 | |
81 | 84 | // TryLock tries to own the lock. |
92 | 95 | panic(ErrNeedAbsPath) |
93 | 96 | } |
94 | 97 | |
95 | tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") | |
96 | if err != nil { | |
97 | return err | |
98 | } | |
99 | ||
100 | cleanup := func() { | |
101 | _ = tmplock.Close() | |
102 | _ = os.Remove(tmplock.Name()) | |
103 | } | |
98 | tmplock, cleanup, err := makePidFile(name, os.Getpid()) | |
99 | if err != nil { | |
100 | return err | |
101 | } | |
102 | ||
104 | 103 | defer cleanup() |
105 | 104 | |
106 | if err := writePidLine(tmplock, os.Getpid()); err != nil { | |
107 | return err | |
108 | } | |
109 | ||
110 | // EEXIST and similiar error codes, caught by os.IsExist, are intentionally ignored, | |
105 | // EEXIST and similar error codes, caught by os.IsExist, are intentionally ignored, | |
111 | 106 | // as it means that someone was faster creating this link |
112 | 107 | // and ignoring this kind of error is part of the algorithm. |
113 | // The we will probably fail the pid owner check later, if this process is still alive. | |
108 | // Then we will probably fail the pid owner check later, if this process is still alive. | |
114 | 109 | // We cannot ignore ALL errors, since failure to support hard links, disk full |
115 | 110 | // as well as many other errors can happen to a filesystem operation |
116 | 111 | // and we really want to abort on those. |
117 | if err := os.Link(tmplock.Name(), name); err != nil { | |
112 | if err := os.Link(tmplock, name); err != nil { | |
118 | 113 | if !os.IsExist(err) { |
119 | 114 | return err |
120 | 115 | } |
121 | 116 | } |
122 | 117 | |
123 | fiTmp, err := os.Lstat(tmplock.Name()) | |
124 | if err != nil { | |
125 | return err | |
126 | } | |
118 | fiTmp, err := os.Lstat(tmplock) | |
119 | if err != nil { | |
120 | return err | |
121 | } | |
122 | ||
127 | 123 | fiLock, err := os.Lstat(name) |
128 | 124 | if err != nil { |
129 | 125 | // tell user that a retry would be a good idea |
130 | 126 | if os.IsNotExist(err) { |
131 | 127 | return ErrNotExist |
132 | 128 | } |
129 | ||
133 | 130 | return err |
134 | 131 | } |
135 | 132 | |
147 | 144 | if proc.Pid != os.Getpid() { |
148 | 145 | return ErrBusy |
149 | 146 | } |
150 | case ErrDeadOwner, ErrInvalidPid: | |
151 | // cases we can fix below | |
147 | case ErrDeadOwner, ErrInvalidPid: // cases we can fix below | |
152 | 148 | } |
153 | 149 | |
154 | 150 | // clean stale/invalid lockfile |
164 | 160 | return l.TryLock() |
165 | 161 | } |
166 | 162 | |
167 | // Unlock a lock again, if we owned it. Returns any error that happend during release of lock. | |
163 | // Unlock a lock again, if we owned it. Returns any error that happened during release of lock. | |
168 | 164 | func (l Lockfile) Unlock() error { |
169 | 165 | proc, err := l.GetOwner() |
170 | 166 | switch err { |
188 | 184 | } |
189 | 185 | } |
190 | 186 | |
191 | func writePidLine(w io.Writer, pid int) error { | |
192 | _, err := io.WriteString(w, fmt.Sprintf("%d\n", pid)) | |
193 | return err | |
194 | } | |
195 | ||
196 | 187 | func scanPidLine(content []byte) (int, error) { |
197 | 188 | if len(content) == 0 { |
198 | 189 | return 0, ErrInvalidPid |
206 | 197 | if pid <= 0 { |
207 | 198 | return 0, ErrInvalidPid |
208 | 199 | } |
200 | ||
209 | 201 | return pid, nil |
210 | 202 | } |
203 | ||
204 | func makePidFile(name string, pid int) (tmpname string, cleanup func(), err error) { | |
205 | tmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+".") | |
206 | if err != nil { | |
207 | return "", nil, err | |
208 | } | |
209 | ||
210 | cleanup = func() { | |
211 | _ = tmplock.Close() | |
212 | _ = os.Remove(tmplock.Name()) | |
213 | } | |
214 | ||
215 | if _, err := io.WriteString(tmplock, fmt.Sprintf("%d\n", pid)); err != nil { | |
216 | cleanup() // Do cleanup here, so call doesn't have to. | |
217 | return "", nil, err | |
218 | } | |
219 | ||
220 | return tmplock.Name(), cleanup, nil | |
221 | } |
15 | 15 | fmt.Printf("Cannot init lock. reason: %v", err) |
16 | 16 | panic(err) // handle properly please! |
17 | 17 | } |
18 | err = lock.TryLock() | |
19 | 18 | |
20 | 19 | // Error handling is essential, as we only try to get the lock. |
21 | if err != nil { | |
20 | if err = lock.TryLock(); err != nil { | |
22 | 21 | fmt.Printf("Cannot lock %q, reason: %v", lock, err) |
23 | 22 | panic(err) // handle properly please! |
24 | 23 | } |
25 | 24 | |
26 | defer lock.Unlock() | |
25 | defer func() { | |
26 | if err := lock.Unlock(); err != nil { | |
27 | fmt.Printf("Cannot unlock %q, reason: %v", lock, err) | |
28 | panic(err) // handle properly please! | |
29 | } | |
30 | }() | |
27 | 31 | |
28 | 32 | fmt.Println("Do stuff under lock") |
29 | 33 | // Output: Do stuff under lock |
60 | 64 | func GetDeadPID() int { |
61 | 65 | // I have no idea how windows handles large PIDs, or if they even exist. |
62 | 66 | // So limit it to be less or equal to 4096 to be safe. |
63 | ||
64 | 67 | const maxPid = 4095 |
65 | 68 | |
66 | 69 | // limited iteration, so we finish one day |
70 | 73 | if seen[pid] { |
71 | 74 | continue |
72 | 75 | } |
76 | ||
73 | 77 | seen[pid] = true |
78 | ||
74 | 79 | running, err := isRunning(pid) |
75 | 80 | if err != nil { |
76 | 81 | fmt.Println("Error checking PID: ", err) |
163 | 168 | t.Fatal(err) |
164 | 169 | return |
165 | 170 | } |
171 | ||
166 | 172 | defer os.Remove(path) |
167 | 173 | |
168 | 174 | err = lf.Unlock() |
171 | 177 | return |
172 | 178 | } |
173 | 179 | |
174 | if _, err := os.Stat(path); os.IsNotExist(err) { | |
175 | t.Fatal("lockfile should not be deleted by us, if we didn't create it") | |
176 | } else { | |
177 | if err != nil { | |
178 | t.Fatalf("unexpected error %v", err) | |
179 | } | |
180 | if _, err := os.Stat(path); err != nil { | |
181 | if os.IsNotExist(err) { | |
182 | content, _ := ioutil.ReadFile(path) | |
183 | t.Fatalf("lockfile %q (%q) should not be deleted by us, if we didn't create it", path, content) | |
184 | } | |
185 | t.Fatalf("unexpected error %v", err) | |
180 | 186 | } |
181 | 187 | } |
182 | 188 | |
214 | 220 | t.Fatal(err) |
215 | 221 | return |
216 | 222 | } |
223 | ||
217 | 224 | if err := ioutil.WriteFile(path, []byte("\n"), 0666); err != nil { |
218 | 225 | t.Fatal(err) |
219 | 226 | return |
220 | 227 | } |
228 | ||
221 | 229 | defer os.Remove(path) |
222 | 230 | |
223 | 231 | lf, err := New(path) |
249 | 257 | pid int |
250 | 258 | xfail error |
251 | 259 | }{ |
252 | { | |
253 | xfail: ErrInvalidPid, | |
254 | }, | |
255 | { | |
256 | input: []byte(""), | |
257 | xfail: ErrInvalidPid, | |
258 | }, | |
259 | { | |
260 | input: []byte("\n"), | |
261 | xfail: ErrInvalidPid, | |
262 | }, | |
263 | { | |
264 | input: []byte("-1\n"), | |
265 | xfail: ErrInvalidPid, | |
266 | }, | |
267 | { | |
268 | input: []byte("0\n"), | |
269 | xfail: ErrInvalidPid, | |
270 | }, | |
271 | { | |
272 | input: []byte("a\n"), | |
273 | xfail: ErrInvalidPid, | |
274 | }, | |
275 | { | |
276 | input: []byte("1\n"), | |
277 | pid: 1, | |
278 | }, | |
260 | {xfail: ErrInvalidPid}, | |
261 | {input: []byte(""), xfail: ErrInvalidPid}, | |
262 | {input: []byte("\n"), xfail: ErrInvalidPid}, | |
263 | {input: []byte("-1\n"), xfail: ErrInvalidPid}, | |
264 | {input: []byte("0\n"), xfail: ErrInvalidPid}, | |
265 | {input: []byte("a\n"), xfail: ErrInvalidPid}, | |
266 | {input: []byte("1\n"), pid: 1}, | |
279 | 267 | } |
280 | 268 | |
281 | 269 | // test positive cases first |
283 | 271 | if tc.xfail != nil { |
284 | 272 | continue |
285 | 273 | } |
286 | want := tc.pid | |
274 | ||
287 | 275 | got, err := scanPidLine(tc.input) |
288 | 276 | if err != nil { |
289 | 277 | t.Fatalf("%d: unexpected error %v", step, err) |
290 | 278 | } |
291 | if got != want { | |
279 | ||
280 | if want := tc.pid; got != want { | |
292 | 281 | t.Errorf("%d: expected pid %d, got %d", step, want, got) |
293 | 282 | } |
294 | 283 | } |
298 | 287 | if tc.xfail == nil { |
299 | 288 | continue |
300 | 289 | } |
301 | want := tc.xfail | |
290 | ||
302 | 291 | _, got := scanPidLine(tc.input) |
303 | if got != want { | |
292 | if want := tc.xfail; got != want { | |
304 | 293 | t.Errorf("%d: expected error %v, got %v", step, want, got) |
305 | 294 | } |
306 | 295 | } |