-
Notifications
You must be signed in to change notification settings - Fork 61
/
pidfile.go
143 lines (120 loc) · 3.63 KB
/
pidfile.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
//
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
package newrelic
import (
"errors"
"os"
"strconv"
"syscall"
"newrelic/limits"
"newrelic/log"
)
var (
ErrLocked = errors.New("pid file lock held by another process")
ErrRetryLimit = errors.New("max retries exceeded trying to create pid file")
)
// PidFile represents an open file descriptor to a pid file.
//
// A few words about pid files. The daemon requires a pid file to prevent
// a race condition where many agents attempt to spawn a daemon at the
// same time. To fully close the race condition the daemon that holds
// the pid file lock must retain it for its entire lifetime. For that
// reason we do not close the pid file and we do not delete the pid file
// on exit.
type PidFile struct {
file *os.File
}
// CreatePidFile opens the given pid file and acquires an exclusive
// write lock.
func CreatePidFile(name string) (*PidFile, error) {
for i := 0; i < limits.MaxPidfileRetries; i++ {
f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return nil, err
}
if err := setWriteLock(f); err != nil {
f.Close()
return nil, err
}
// There's a race condition between opening the file and acquiring
// the lock. Consider the following.
//
// 1) A opens the pid file and acquires the lock.
// 2) B opens the pid file.
// 3) A exits, cleaning up the pid file and releasing the lock.
// 4) B then acquires a lock on the deleted pid file.
// 5) C comes along and creates a new pid file...
//
// Attempt to detect and handle this and similar situations by retrying.
pathStat, err := os.Stat(f.Name())
if err != nil {
// File is probably gone or inaccessible. Try again.
log.Debugf("pidfile: %v - retrying", err)
f.Close()
continue
}
fdStat, err := f.Stat()
if err != nil {
// Fatal error (EIO?), give up.
f.Close()
return nil, err
}
if !os.SameFile(pathStat, fdStat) {
log.Debugf("pidfile: file has changed since it was opened - retrying")
f.Close()
continue
}
// Remove stale pid.
if err := f.Truncate(0); err != nil {
f.Close()
return nil, err
}
return &PidFile{file: f}, nil
}
return nil, ErrRetryLimit
}
// setWriteLock tries to acquire a POSIX exclusive write lock on f.
func setWriteLock(f *os.File) error {
lock := &syscall.Flock_t{
Type: syscall.F_WRLCK,
Whence: int16(os.SEEK_SET),
}
err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, lock)
if err != nil {
if err == syscall.EACCES || err == syscall.EAGAIN {
return ErrLocked
}
return err
}
return nil
}
// Name returns the name of the pid file as presented to CreatePidFile.
func (f *PidFile) Name() string {
return f.file.Name()
}
// Write writes the process id of the current process to f replacing its
// contents. It returns the number of bytes written and an error, if any.
func (f *PidFile) Write() (n int, err error) {
if err = f.file.Truncate(0); err != nil {
return 0, err
}
s := strconv.Itoa(os.Getpid()) + "\n"
return f.file.WriteString(s)
}
// Close closes the pid file, releasing its write lock and rendering it
// unusable for I/O. It returns an error, if any. Note, closing the pid
// file does not cause it to be removed.
func (f *PidFile) Close() error {
return f.file.Close()
}
// Remove attempts to remove the pid file. Removing a pid file releases its
// write lock and renders the PidFile unusable for I/O. For a daemon process,
// removing the pid file should be the very last step before exiting.
func (f *PidFile) Remove() error {
if err := os.Remove(f.file.Name()); err != nil {
return err
}
return f.file.Close()
}