Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/daemon/PhutilDaemonPool.php
12241 views
1
<?php
2
3
final class PhutilDaemonPool extends Phobject {
4
5
private $properties = array();
6
private $commandLineArguments;
7
8
private $overseer;
9
private $daemons = array();
10
private $argv;
11
12
private $lastAutoscaleUpdate;
13
private $inShutdown;
14
15
private function __construct() {
16
// <empty>
17
}
18
19
public static function newFromConfig(array $config) {
20
PhutilTypeSpec::checkMap(
21
$config,
22
array(
23
'class' => 'string',
24
'label' => 'string',
25
'argv' => 'optional list<string>',
26
'load' => 'optional list<string>',
27
'log' => 'optional string|null',
28
'pool' => 'optional int',
29
'up' => 'optional int',
30
'down' => 'optional int',
31
'reserve' => 'optional int|float',
32
));
33
34
$config = $config + array(
35
'argv' => array(),
36
'load' => array(),
37
'log' => null,
38
'pool' => 1,
39
'up' => 2,
40
'down' => 15,
41
'reserve' => 0,
42
);
43
44
$pool = new self();
45
$pool->properties = $config;
46
47
return $pool;
48
}
49
50
public function setOverseer(PhutilDaemonOverseer $overseer) {
51
$this->overseer = $overseer;
52
return $this;
53
}
54
55
public function getOverseer() {
56
return $this->overseer;
57
}
58
59
public function setCommandLineArguments(array $arguments) {
60
$this->commandLineArguments = $arguments;
61
return $this;
62
}
63
64
public function getCommandLineArguments() {
65
return $this->commandLineArguments;
66
}
67
68
private function shouldShutdown() {
69
return $this->inShutdown;
70
}
71
72
private function newDaemon() {
73
$config = $this->properties;
74
75
if (count($this->daemons)) {
76
$down_duration = $this->getPoolScaledownDuration();
77
} else {
78
// TODO: For now, never scale pools down to 0.
79
$down_duration = 0;
80
}
81
82
$forced_config = array(
83
'down' => $down_duration,
84
);
85
86
$config = $forced_config + $config;
87
88
$config = array_select_keys(
89
$config,
90
array(
91
'class',
92
'log',
93
'load',
94
'argv',
95
'down',
96
));
97
98
$daemon = PhutilDaemonHandle::newFromConfig($config)
99
->setDaemonPool($this)
100
->setCommandLineArguments($this->getCommandLineArguments());
101
102
$daemon_id = $daemon->getDaemonID();
103
$this->daemons[$daemon_id] = $daemon;
104
105
$daemon->didLaunch();
106
107
return $daemon;
108
}
109
110
public function getDaemons() {
111
return $this->daemons;
112
}
113
114
public function didReceiveSignal($signal, $signo) {
115
switch ($signal) {
116
case PhutilDaemonOverseer::SIGNAL_GRACEFUL:
117
case PhutilDaemonOverseer::SIGNAL_TERMINATE:
118
$this->inShutdown = true;
119
break;
120
}
121
122
foreach ($this->getDaemons() as $daemon) {
123
switch ($signal) {
124
case PhutilDaemonOverseer::SIGNAL_NOTIFY:
125
$daemon->didReceiveNotifySignal($signo);
126
break;
127
case PhutilDaemonOverseer::SIGNAL_RELOAD:
128
$daemon->didReceiveReloadSignal($signo);
129
break;
130
case PhutilDaemonOverseer::SIGNAL_GRACEFUL:
131
$daemon->didReceiveGracefulSignal($signo);
132
break;
133
case PhutilDaemonOverseer::SIGNAL_TERMINATE:
134
$daemon->didReceiveTerminateSignal($signo);
135
break;
136
default:
137
throw new Exception(
138
pht(
139
'Unknown signal "%s" ("%d").',
140
$signal,
141
$signo));
142
}
143
}
144
}
145
146
public function getPoolLabel() {
147
return $this->getPoolProperty('label');
148
}
149
150
public function getPoolMaximumSize() {
151
return $this->getPoolProperty('pool');
152
}
153
154
public function getPoolScaleupDuration() {
155
return $this->getPoolProperty('up');
156
}
157
158
public function getPoolScaledownDuration() {
159
return $this->getPoolProperty('down');
160
}
161
162
public function getPoolMemoryReserve() {
163
return $this->getPoolProperty('reserve');
164
}
165
166
public function getPoolDaemonClass() {
167
return $this->getPoolProperty('class');
168
}
169
170
private function getPoolProperty($key) {
171
return idx($this->properties, $key);
172
}
173
174
public function updatePool() {
175
$daemons = $this->getDaemons();
176
177
foreach ($daemons as $key => $daemon) {
178
$daemon->update();
179
180
if ($daemon->isDone()) {
181
$daemon->didExit();
182
183
unset($this->daemons[$key]);
184
185
if ($this->shouldShutdown()) {
186
$this->logMessage(
187
'DOWN',
188
pht(
189
'Pool "%s" is exiting, with %s daemon(s) remaining.',
190
$this->getPoolLabel(),
191
new PhutilNumber(count($this->daemons))));
192
} else {
193
$this->logMessage(
194
'POOL',
195
pht(
196
'Autoscale pool "%s" scaled down to %s daemon(s).',
197
$this->getPoolLabel(),
198
new PhutilNumber(count($this->daemons))));
199
}
200
}
201
}
202
203
$this->updateAutoscale();
204
}
205
206
public function isHibernating() {
207
foreach ($this->getDaemons() as $daemon) {
208
if (!$daemon->isHibernating()) {
209
return false;
210
}
211
}
212
213
return true;
214
}
215
216
public function wakeFromHibernation() {
217
if (!$this->isHibernating()) {
218
return $this;
219
}
220
221
$this->logMessage(
222
'WAKE',
223
pht(
224
'Autoscale pool "%s" is being awakened from hibernation.',
225
$this->getPoolLabel()));
226
227
$did_wake_daemons = false;
228
foreach ($this->getDaemons() as $daemon) {
229
if ($daemon->isHibernating()) {
230
$daemon->wakeFromHibernation();
231
$did_wake_daemons = true;
232
}
233
}
234
235
if (!$did_wake_daemons) {
236
// TODO: Pools currently can't scale down to 0 daemons, but we should
237
// scale up immediately here once they can.
238
}
239
240
$this->updatePool();
241
242
return $this;
243
}
244
245
private function updateAutoscale() {
246
if ($this->shouldShutdown()) {
247
return;
248
}
249
250
// Don't try to autoscale more than once per second. This mostly stops the
251
// logs from getting flooded in verbose mode.
252
$now = time();
253
if ($this->lastAutoscaleUpdate >= $now) {
254
return;
255
}
256
$this->lastAutoscaleUpdate = $now;
257
258
$daemons = $this->getDaemons();
259
260
// If this pool is already at the maximum size, we can't launch any new
261
// daemons.
262
$max_size = $this->getPoolMaximumSize();
263
if (count($daemons) >= $max_size) {
264
$this->logMessage(
265
'POOL',
266
pht(
267
'Autoscale pool "%s" already at maximum size (%s of %s).',
268
$this->getPoolLabel(),
269
new PhutilNumber(count($daemons)),
270
new PhutilNumber($max_size)));
271
return;
272
}
273
274
$scaleup_duration = $this->getPoolScaleupDuration();
275
276
foreach ($daemons as $daemon) {
277
$busy_epoch = $daemon->getBusyEpoch();
278
// If any daemons haven't started work yet, don't scale the pool up.
279
if (!$busy_epoch) {
280
$this->logMessage(
281
'POOL',
282
pht(
283
'Autoscale pool "%s" has an idle daemon, declining to scale.',
284
$this->getPoolLabel()));
285
return;
286
}
287
288
// If any daemons started work very recently, wait a little while
289
// to scale the pool up.
290
$busy_for = ($now - $busy_epoch);
291
if ($busy_for < $scaleup_duration) {
292
$this->logMessage(
293
'POOL',
294
pht(
295
'Autoscale pool "%s" has not been busy long enough to scale up '.
296
'(busy for %s of %s seconds).',
297
$this->getPoolLabel(),
298
new PhutilNumber($busy_for),
299
new PhutilNumber($scaleup_duration)));
300
return;
301
}
302
}
303
304
// If we have a configured memory reserve for this pool, it tells us that
305
// we should not scale up unless there's at least that much memory left
306
// on the system (for example, a reserve of 0.25 means that 25% of system
307
// memory must be free to autoscale).
308
309
// Note that the first daemon is exempt: we'll always launch at least one
310
// daemon, regardless of any memory reservation.
311
if (count($daemons)) {
312
$reserve = $this->getPoolMemoryReserve();
313
if ($reserve) {
314
// On some systems this may be slightly more expensive than other
315
// checks, so we only do it once we're prepared to scale up.
316
$memory = PhutilSystem::getSystemMemoryInformation();
317
$free_ratio = ($memory['free'] / $memory['total']);
318
319
// If we don't have enough free memory, don't scale.
320
if ($free_ratio <= $reserve) {
321
$this->logMessage(
322
'POOL',
323
pht(
324
'Autoscale pool "%s" does not have enough free memory to '.
325
'scale up (%s free of %s reserved).',
326
$this->getPoolLabel(),
327
new PhutilNumber($free_ratio, 3),
328
new PhutilNumber($reserve, 3)));
329
return;
330
}
331
}
332
}
333
334
$this->logMessage(
335
'AUTO',
336
pht(
337
'Scaling pool "%s" up to %s daemon(s).',
338
$this->getPoolLabel(),
339
new PhutilNumber(count($daemons) + 1)));
340
341
$this->newDaemon();
342
}
343
344
public function logMessage($type, $message, $context = null) {
345
return $this->getOverseer()->logMessage($type, $message, $context);
346
}
347
348
}
349
350