forked from facebook/watchman
-
Notifications
You must be signed in to change notification settings - Fork 0
/
checksock.cpp
201 lines (179 loc) · 5.98 KB
/
checksock.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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* Copyright 2015-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */
#include "watchman.h"
namespace watchman {
namespace {
// Work-around decodeNext which implictly resets to non-blocking
json_ref
decodeNext(watchman_stream* client, w_jbuffer_t& buf, json_error_t& jerr) {
client->setNonBlock(false);
return buf.decodeNext(client, &jerr);
}
/* Periodically connect to our endpoint and verify that we're talking
* to ourselves. This is normally a sign of madness, but if we don't
* get an answer, or get a reply from someone else, we know things
* are bad; someone removed our socket file or there was some kind of
* race condition that resulted in multiple instances starting up.
*/
void check_my_sock(watchman_stream* client) {
auto cmd = json_array({typed_string_to_json("get-pid", W_STRING_UNICODE)});
w_jbuffer_t buf;
json_error_t jerr;
pid_t my_pid = ::getpid();
if (!buf.pduEncodeToStream(is_bser, 0, cmd, client)) {
log(watchman::FATAL, "Failed to send get-pid PDU: ", strerror(errno), "\n");
/* NOTREACHED */
}
buf.clear();
auto result = decodeNext(client, buf, jerr);
if (!result) {
log(watchman::FATAL,
"Failed to decode get-pid response: ",
jerr.text,
" ",
strerror(errno),
"\n");
/* NOTREACHED */
}
auto pid = result.get_default("pid");
if (!pid) {
log(watchman::FATAL,
"Failed to get pid from get-pid response: ",
jerr.text,
"\n");
/* NOTREACHED */
}
auto remote_pid = pid.asInt();
if (remote_pid != my_pid) {
log(watchman::FATAL,
"remote pid from get-pid ",
long(remote_pid),
" doesn't match my pid (",
(long)my_pid,
"\n");
/* NOTREACHED */
}
}
/**
* Run clock command for the specified root. Useful for getting time
* information.
*/
void check_clock_command(watchman_stream* client, json_ref& root) {
w_jbuffer_t buf;
json_error_t jerr;
auto cmd = json_array({typed_string_to_json("clock", W_STRING_UNICODE),
root,
json_object({{"sync_timeout", json_integer(20000)}})});
if (!buf.pduEncodeToStream(is_bser, 0, cmd, client)) {
throw std::runtime_error(
folly::to<std::string>("Failed to send clock PDU: ", strerror(errno)));
}
buf.clear();
auto result = decodeNext(client, buf, jerr);
if (!result) {
throw std::runtime_error(folly::to<std::string>(
"Failed to decode clock response: ", jerr.text, " ", strerror(errno)));
}
// Check for error in the response
auto error = result.get_default("error");
if (error) {
throw std::runtime_error(
folly::to<std::string>("Clock error : ", json_to_w_string(error)));
}
// We use presence of "clock" as success
auto clock = result.get_default("clock");
if (!clock) {
throw std::runtime_error("Failed to get clock in response");
}
}
/**
* Runs watch-list command and returns a json_ref that contains a list of roots.
*/
json_ref get_watch_list(watchman_stream* client) {
auto cmd = json_array({typed_string_to_json("watch-list", W_STRING_UNICODE)});
w_jbuffer_t buf;
json_error_t jerr;
if (!buf.pduEncodeToStream(is_bser, 0, cmd, client)) {
throw std::runtime_error(folly::to<std::string>(
"Failed to send watch-list PDU: ", strerror(errno)));
}
buf.clear();
auto result = decodeNext(client, buf, jerr);
if (!result) {
throw std::runtime_error(folly::to<std::string>(
"Failed to decode watch-list response: ",
jerr.text,
" error: ",
strerror(errno)));
}
return result.get_default("roots");
}
/**
* Run watch-list to get the list of watched roots. Then, run 'clock' on each
* watched root. We perf log the time taken to get the clock.
*/
void do_clock_check(watchman_stream* client) {
// We don't expect errors in these calls. However, we do want to make sure
// they are not fatal.
try {
auto roots = get_watch_list(client);
for (auto& r : roots.array()) {
w_perf_t sample("clock-test");
sample.add_meta("root", json_object({{"path", r}}));
try {
check_clock_command(client, r);
} catch (const std::exception& ex) {
log(watchman::ERR, "Failed do_clock_check : ", ex.what(), "\n");
sample.add_meta("error", w_string_to_json(ex.what()));
sample.force_log();
}
sample.finish();
sample.log();
}
} catch (const std::exception& ex) {
// Catch std::domain_error and std::runtime_error
log(watchman::ERR, "Failed get_watch_list : ", ex.what(), "\n");
}
}
void sanityCheckThread() noexcept {
w_set_thread_name("sanitychecks");
auto lastCheck = std::chrono::steady_clock::now();
auto interval = std::chrono::minutes(1);
log(ERR, "starting sanityCheckThread\n");
// we want to try the checks in here once per minute, but since
// this is mildly ghetto we don't have a way to directly signal
// this thread when we're shutting down. So we sleep for a second
// at a time and then check to see if a shutdown is in progress
// so that we can shutdown with a slightly lower latency than
// if we were to sleep for a minute at a time.
while (!w_is_stopping()) {
auto now = std::chrono::steady_clock::now();
if (now - lastCheck < interval) {
std::this_thread::sleep_for(std::chrono::seconds(1));
continue;
}
lastCheck = now;
log(DBG, "running sanity checks\n");
auto client = w_stm_connect(get_sock_name(), 6000);
if (!client) {
log(watchman::FATAL,
"Failed to connect to myself for sanity check: ",
strerror(errno),
"\n");
/* NOTREACHED */
}
check_my_sock(client.get());
do_clock_check(client.get());
}
log(ERR, "done with sanityCheckThread\n");
}
} // namespace
void startSanityCheckThread(void) {
// The blocking pipe reads we use on win32 can cause us to get blocked
// forever running the sanity checks, so skip this on win32
#ifndef _WIN32
std::thread thr(sanityCheckThread);
thr.detach();
#endif
}
} // namespace watchman