Something went wrong. Try again.
@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
Something went wrong. Try again.
9.0 kB · 348 lines
PHP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349<?php
final class PhutilDaemonPool extends Phobject {
private $properties = array(); private $commandLineArguments;
private $overseer; private $daemons = array(); private $argv;
private $lastAutoscaleUpdate; private $inShutdown;
private function __construct() { // <empty> }
public static function newFromConfig(array $config) { PhutilTypeSpec::checkMap( $config, array( 'class' => 'string', 'label' => 'string', 'argv' => 'optional list<string>', 'load' => 'optional list<string>', 'log' => 'optional string|null', 'pool' => 'optional int', 'up' => 'optional int', 'down' => 'optional int', 'reserve' => 'optional int|float', ));
$config = $config + array( 'argv' => array(), 'load' => array(), 'log' => null, 'pool' => 1, 'up' => 2, 'down' => 15, 'reserve' => 0, );
$pool = new self(); $pool->properties = $config;
return $pool; }
public function setOverseer(PhutilDaemonOverseer $overseer) { $this->overseer = $overseer; return $this; }
public function getOverseer() { return $this->overseer; }
public function setCommandLineArguments(array $arguments) { $this->commandLineArguments = $arguments; return $this; }
public function getCommandLineArguments() { return $this->commandLineArguments; }
private function shouldShutdown() { return $this->inShutdown; }
private function newDaemon() { $config = $this->properties;
if (count($this->daemons)) { $down_duration = $this->getPoolScaledownDuration(); } else { // TODO: For now, never scale pools down to 0. $down_duration = 0; }
$forced_config = array( 'down' => $down_duration, );
$config = $forced_config + $config;
$config = array_select_keys( $config, array( 'class', 'log', 'load', 'argv', 'down', ));
$daemon = PhutilDaemonHandle::newFromConfig($config) ->setDaemonPool($this) ->setCommandLineArguments($this->getCommandLineArguments());
$daemon_id = $daemon->getDaemonID(); $this->daemons[$daemon_id] = $daemon;
$daemon->didLaunch();
return $daemon; }
public function getDaemons() { return $this->daemons; }
public function didReceiveSignal($signal, $signo) { switch ($signal) { case PhutilDaemonOverseer::SIGNAL_GRACEFUL: case PhutilDaemonOverseer::SIGNAL_TERMINATE: $this->inShutdown = true; break; }
foreach ($this->getDaemons() as $daemon) { switch ($signal) { case PhutilDaemonOverseer::SIGNAL_NOTIFY: $daemon->didReceiveNotifySignal($signo); break; case PhutilDaemonOverseer::SIGNAL_RELOAD: $daemon->didReceiveReloadSignal($signo); break; case PhutilDaemonOverseer::SIGNAL_GRACEFUL: $daemon->didReceiveGracefulSignal($signo); break; case PhutilDaemonOverseer::SIGNAL_TERMINATE: $daemon->didReceiveTerminateSignal($signo); break; default: throw new Exception( pht( 'Unknown signal "%s" ("%d").', $signal, $signo)); } } }
public function getPoolLabel() { return $this->getPoolProperty('label'); }
public function getPoolMaximumSize() { return $this->getPoolProperty('pool'); }
public function getPoolScaleupDuration() { return $this->getPoolProperty('up'); }
public function getPoolScaledownDuration() { return $this->getPoolProperty('down'); }
public function getPoolMemoryReserve() { return $this->getPoolProperty('reserve'); }
public function getPoolDaemonClass() { return $this->getPoolProperty('class'); }
private function getPoolProperty($key) { return idx($this->properties, $key); }
public function updatePool() { $daemons = $this->getDaemons();
foreach ($daemons as $key => $daemon) { $daemon->update();
if ($daemon->isDone()) { $daemon->didExit();
unset($this->daemons[$key]);
if ($this->shouldShutdown()) { $this->logMessage( 'DOWN', pht( 'Pool "%s" is exiting, with %s daemon(s) remaining.', $this->getPoolLabel(), new PhutilNumber(count($this->daemons)))); } else { $this->logMessage( 'POOL', pht( 'Autoscale pool "%s" scaled down to %s daemon(s).', $this->getPoolLabel(), new PhutilNumber(count($this->daemons)))); } } }
$this->updateAutoscale(); }
public function isHibernating() { foreach ($this->getDaemons() as $daemon) { if (!$daemon->isHibernating()) { return false; } }
return true; }
public function wakeFromHibernation() { if (!$this->isHibernating()) { return $this; }
$this->logMessage( 'WAKE', pht( 'Autoscale pool "%s" is being awakened from hibernation.', $this->getPoolLabel()));
$did_wake_daemons = false; foreach ($this->getDaemons() as $daemon) { if ($daemon->isHibernating()) { $daemon->wakeFromHibernation(); $did_wake_daemons = true; } }
if (!$did_wake_daemons) { // TODO: Pools currently can't scale down to 0 daemons, but we should // scale up immediately here once they can. }
$this->updatePool();
return $this; }
private function updateAutoscale() { if ($this->shouldShutdown()) { return; }
// Don't try to autoscale more than once per second. This mostly stops the // logs from getting flooded in verbose mode. $now = time(); if ($this->lastAutoscaleUpdate >= $now) { return; } $this->lastAutoscaleUpdate = $now;
$daemons = $this->getDaemons();
// If this pool is already at the maximum size, we can't launch any new // daemons. $max_size = $this->getPoolMaximumSize(); if (count($daemons) >= $max_size) { $this->logMessage( 'POOL', pht( 'Autoscale pool "%s" already at maximum size (%s of %s).', $this->getPoolLabel(), new PhutilNumber(count($daemons)), new PhutilNumber($max_size))); return; }
$scaleup_duration = $this->getPoolScaleupDuration();
foreach ($daemons as $daemon) { $busy_epoch = $daemon->getBusyEpoch(); // If any daemons haven't started work yet, don't scale the pool up. if (!$busy_epoch) { $this->logMessage( 'POOL', pht( 'Autoscale pool "%s" has an idle daemon, declining to scale.', $this->getPoolLabel())); return; }
// If any daemons started work very recently, wait a little while // to scale the pool up. $busy_for = ($now - $busy_epoch); if ($busy_for < $scaleup_duration) { $this->logMessage( 'POOL', pht( 'Autoscale pool "%s" has not been busy long enough to scale up '. '(busy for %s of %s seconds).', $this->getPoolLabel(), new PhutilNumber($busy_for), new PhutilNumber($scaleup_duration))); return; } }
// If we have a configured memory reserve for this pool, it tells us that // we should not scale up unless there's at least that much memory left // on the system (for example, a reserve of 0.25 means that 25% of system // memory must be free to autoscale).
// Note that the first daemon is exempt: we'll always launch at least one // daemon, regardless of any memory reservation. if (count($daemons)) { $reserve = $this->getPoolMemoryReserve(); if ($reserve) { // On some systems this may be slightly more expensive than other // checks, so we only do it once we're prepared to scale up. $memory = PhutilSystem::getSystemMemoryInformation(); $free_ratio = ($memory['free'] / $memory['total']);
// If we don't have enough free memory, don't scale. if ($free_ratio <= $reserve) { $this->logMessage( 'POOL', pht( 'Autoscale pool "%s" does not have enough free memory to '. 'scale up (%s free of %s reserved).', $this->getPoolLabel(), new PhutilNumber($free_ratio, 3), new PhutilNumber($reserve, 3))); return; } } }
$this->logMessage( 'AUTO', pht( 'Scaling pool "%s" up to %s daemon(s).', $this->getPoolLabel(), new PhutilNumber(count($daemons) + 1)));
$this->newDaemon(); }
public function logMessage($type, $message, $context = null) { return $this->getOverseer()->logMessage($type, $message, $context); }
}