forked from facebook/watchman
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProcessLock.cpp
159 lines (138 loc) · 4.98 KB
/
ProcessLock.cpp
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
// Copyright 2004-present Facebook. All Rights Reserved.
#include "ProcessLock.h"
#include <folly/String.h>
#include "Logging.h"
#ifndef _WIN32
#include <sys/file.h>
#endif
namespace watchman {
ProcessLock ProcessLock::acquire(const std::string& pid_file) {
auto result = tryAcquire(pid_file);
if (auto* error = std::get_if<std::string>(&result)) {
log(ERR, *error, "\n");
exit(1);
}
return std::move(std::get<ProcessLock>(result));
}
std::variant<ProcessLock, ProcessLock::LockError> ProcessLock::tryAcquire(
const std::string& pid_file) {
#ifndef _WIN32
FileDescriptor fd(
open(pid_file.c_str(), O_RDWR | O_CREAT, 0644),
FileDescriptor::FDType::Generic);
if (!fd) {
return folly::to<std::string>(
"Failed to open pidfile ",
pid_file,
" for write: ",
folly::errnoStr(errno));
}
// Ensure that no children inherit the locked pidfile descriptor
fd.setCloExec();
// Watchman only starts its server when it's considered not running. But it's
// possible the old Watchman server has just shut down, and the lock isn't
// released yet. If we fail to acquire the lock, return, and let the caller
// decide whether to retry.
// Use flock because it transfers the lock to child processes through
// fork().
int result = ::flock(fd.fd(), LOCK_EX | LOCK_NB);
const int errno_copy = errno;
if (result != 0) {
char pidstr[32];
int len = read(fd.fd(), pidstr, sizeof(pidstr) - 1);
pidstr[len] = '\0';
return fmt::format(
"Failed to lock pidfile {}: process {} owns it: {}, and my pid = {}",
pid_file,
pidstr,
folly::errnoStr(errno_copy),
getpid());
}
return ProcessLock{std::move(fd)};
#else
// One does not simply, and without risk of races, write a pidfile
// on win32. Instead we're using a named mutex in the global namespace.
// This gives us a very simple way to exclusively claim ownership of
// the lock for this user. To make things a little more complicated,
// since we scope our locks based on the state dir location and require
// this to work for our integration tests, we need to create a unique
// name per state dir. This is made even more interesting because
// we are forbidden from using windows directory separator characters
// in the name, so we cannot simply concatenate the state dir path
// with a watchman specific prefix. Instead we iterate the path
// and rewrite any backslashes with forward slashes and use that
// for the name.
// Using a mutex for this does make it more awkward to discover
// the process id of the exclusive owner, but that's not critically
// important; it is possible to connect to the instance and issue
// a get-pid command if that is needed.
// We use the global namespace so that we ensure that we have one
// watchman process per user per state dir location. If we didn't
// use the Global namespace we'd end using a local namespace scoped
// to the user session and that might cause confusion/insanity if
// they are doing something elaborate like being logged in via
// ssh in multiple sessions and expecting to share state.
std::string name("Global\\Watchman-");
for (const auto& it : pid_file) {
if (it == '\\') {
// We're not allowed to use backslash in the name, so normalize
// to forward slashes.
name.append("/");
} else {
name.push_back(it);
}
}
HANDLE mutex = CreateMutexA(nullptr, true, name.c_str());
if (!mutex) {
// Treat unexpected errors as fatal.
log(ERR,
"Failed to create mutex named: ",
name,
": ",
GetLastError(),
"\n");
exit(1);
}
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// Allow retrying failure to acquire the lock.
log(ERR,
"Failed to acquire mutex named: ",
name,
"; watchman is already running for this context\n");
exit(1);
}
/* We are intentionally not closing the mutex and intentionally not storing
* a reference to it anywhere: the intention is that it remain locked
* for the rest of the lifetime of our process.
* CloseHandle(mutex); // NOPE!
*/
return ProcessLock{};
#endif
}
ProcessLock::Handle ProcessLock::writePid(const std::string& pid_file) {
#ifndef _WIN32
CHECK(fd_) << "writePid may only be called after acquire";
// Replace contents of the pidfile with our pid string
if (0 == ftruncate(fd_.fd(), 0)) {
pid_t mypid = getpid();
auto pidString = folly::to<std::string>(mypid);
ignore_result(write(fd_.fd(), pidString.data(), pidString.size()));
fsync(fd_.fd());
} else {
log(ERR,
"Failed to truncate pidfile ",
pid_file,
": ",
folly::errnoStr(errno),
"\n");
}
/* We are intentionally not closing the fd and intentionally not storing
* a reference to it anywhere: the intention is that it remain locked
* for the rest of the lifetime of our process.
* close(fd); // NOPE!
*/
fd_.release();
#endif
return Handle{};
}
} // namespace watchman