Path: blob/master/src/infrastructure/daemon/PhutilDaemonPool.php
12241 views
<?php12final class PhutilDaemonPool extends Phobject {34private $properties = array();5private $commandLineArguments;67private $overseer;8private $daemons = array();9private $argv;1011private $lastAutoscaleUpdate;12private $inShutdown;1314private function __construct() {15// <empty>16}1718public static function newFromConfig(array $config) {19PhutilTypeSpec::checkMap(20$config,21array(22'class' => 'string',23'label' => 'string',24'argv' => 'optional list<string>',25'load' => 'optional list<string>',26'log' => 'optional string|null',27'pool' => 'optional int',28'up' => 'optional int',29'down' => 'optional int',30'reserve' => 'optional int|float',31));3233$config = $config + array(34'argv' => array(),35'load' => array(),36'log' => null,37'pool' => 1,38'up' => 2,39'down' => 15,40'reserve' => 0,41);4243$pool = new self();44$pool->properties = $config;4546return $pool;47}4849public function setOverseer(PhutilDaemonOverseer $overseer) {50$this->overseer = $overseer;51return $this;52}5354public function getOverseer() {55return $this->overseer;56}5758public function setCommandLineArguments(array $arguments) {59$this->commandLineArguments = $arguments;60return $this;61}6263public function getCommandLineArguments() {64return $this->commandLineArguments;65}6667private function shouldShutdown() {68return $this->inShutdown;69}7071private function newDaemon() {72$config = $this->properties;7374if (count($this->daemons)) {75$down_duration = $this->getPoolScaledownDuration();76} else {77// TODO: For now, never scale pools down to 0.78$down_duration = 0;79}8081$forced_config = array(82'down' => $down_duration,83);8485$config = $forced_config + $config;8687$config = array_select_keys(88$config,89array(90'class',91'log',92'load',93'argv',94'down',95));9697$daemon = PhutilDaemonHandle::newFromConfig($config)98->setDaemonPool($this)99->setCommandLineArguments($this->getCommandLineArguments());100101$daemon_id = $daemon->getDaemonID();102$this->daemons[$daemon_id] = $daemon;103104$daemon->didLaunch();105106return $daemon;107}108109public function getDaemons() {110return $this->daemons;111}112113public function didReceiveSignal($signal, $signo) {114switch ($signal) {115case PhutilDaemonOverseer::SIGNAL_GRACEFUL:116case PhutilDaemonOverseer::SIGNAL_TERMINATE:117$this->inShutdown = true;118break;119}120121foreach ($this->getDaemons() as $daemon) {122switch ($signal) {123case PhutilDaemonOverseer::SIGNAL_NOTIFY:124$daemon->didReceiveNotifySignal($signo);125break;126case PhutilDaemonOverseer::SIGNAL_RELOAD:127$daemon->didReceiveReloadSignal($signo);128break;129case PhutilDaemonOverseer::SIGNAL_GRACEFUL:130$daemon->didReceiveGracefulSignal($signo);131break;132case PhutilDaemonOverseer::SIGNAL_TERMINATE:133$daemon->didReceiveTerminateSignal($signo);134break;135default:136throw new Exception(137pht(138'Unknown signal "%s" ("%d").',139$signal,140$signo));141}142}143}144145public function getPoolLabel() {146return $this->getPoolProperty('label');147}148149public function getPoolMaximumSize() {150return $this->getPoolProperty('pool');151}152153public function getPoolScaleupDuration() {154return $this->getPoolProperty('up');155}156157public function getPoolScaledownDuration() {158return $this->getPoolProperty('down');159}160161public function getPoolMemoryReserve() {162return $this->getPoolProperty('reserve');163}164165public function getPoolDaemonClass() {166return $this->getPoolProperty('class');167}168169private function getPoolProperty($key) {170return idx($this->properties, $key);171}172173public function updatePool() {174$daemons = $this->getDaemons();175176foreach ($daemons as $key => $daemon) {177$daemon->update();178179if ($daemon->isDone()) {180$daemon->didExit();181182unset($this->daemons[$key]);183184if ($this->shouldShutdown()) {185$this->logMessage(186'DOWN',187pht(188'Pool "%s" is exiting, with %s daemon(s) remaining.',189$this->getPoolLabel(),190new PhutilNumber(count($this->daemons))));191} else {192$this->logMessage(193'POOL',194pht(195'Autoscale pool "%s" scaled down to %s daemon(s).',196$this->getPoolLabel(),197new PhutilNumber(count($this->daemons))));198}199}200}201202$this->updateAutoscale();203}204205public function isHibernating() {206foreach ($this->getDaemons() as $daemon) {207if (!$daemon->isHibernating()) {208return false;209}210}211212return true;213}214215public function wakeFromHibernation() {216if (!$this->isHibernating()) {217return $this;218}219220$this->logMessage(221'WAKE',222pht(223'Autoscale pool "%s" is being awakened from hibernation.',224$this->getPoolLabel()));225226$did_wake_daemons = false;227foreach ($this->getDaemons() as $daemon) {228if ($daemon->isHibernating()) {229$daemon->wakeFromHibernation();230$did_wake_daemons = true;231}232}233234if (!$did_wake_daemons) {235// TODO: Pools currently can't scale down to 0 daemons, but we should236// scale up immediately here once they can.237}238239$this->updatePool();240241return $this;242}243244private function updateAutoscale() {245if ($this->shouldShutdown()) {246return;247}248249// Don't try to autoscale more than once per second. This mostly stops the250// logs from getting flooded in verbose mode.251$now = time();252if ($this->lastAutoscaleUpdate >= $now) {253return;254}255$this->lastAutoscaleUpdate = $now;256257$daemons = $this->getDaemons();258259// If this pool is already at the maximum size, we can't launch any new260// daemons.261$max_size = $this->getPoolMaximumSize();262if (count($daemons) >= $max_size) {263$this->logMessage(264'POOL',265pht(266'Autoscale pool "%s" already at maximum size (%s of %s).',267$this->getPoolLabel(),268new PhutilNumber(count($daemons)),269new PhutilNumber($max_size)));270return;271}272273$scaleup_duration = $this->getPoolScaleupDuration();274275foreach ($daemons as $daemon) {276$busy_epoch = $daemon->getBusyEpoch();277// If any daemons haven't started work yet, don't scale the pool up.278if (!$busy_epoch) {279$this->logMessage(280'POOL',281pht(282'Autoscale pool "%s" has an idle daemon, declining to scale.',283$this->getPoolLabel()));284return;285}286287// If any daemons started work very recently, wait a little while288// to scale the pool up.289$busy_for = ($now - $busy_epoch);290if ($busy_for < $scaleup_duration) {291$this->logMessage(292'POOL',293pht(294'Autoscale pool "%s" has not been busy long enough to scale up '.295'(busy for %s of %s seconds).',296$this->getPoolLabel(),297new PhutilNumber($busy_for),298new PhutilNumber($scaleup_duration)));299return;300}301}302303// If we have a configured memory reserve for this pool, it tells us that304// we should not scale up unless there's at least that much memory left305// on the system (for example, a reserve of 0.25 means that 25% of system306// memory must be free to autoscale).307308// Note that the first daemon is exempt: we'll always launch at least one309// daemon, regardless of any memory reservation.310if (count($daemons)) {311$reserve = $this->getPoolMemoryReserve();312if ($reserve) {313// On some systems this may be slightly more expensive than other314// checks, so we only do it once we're prepared to scale up.315$memory = PhutilSystem::getSystemMemoryInformation();316$free_ratio = ($memory['free'] / $memory['total']);317318// If we don't have enough free memory, don't scale.319if ($free_ratio <= $reserve) {320$this->logMessage(321'POOL',322pht(323'Autoscale pool "%s" does not have enough free memory to '.324'scale up (%s free of %s reserved).',325$this->getPoolLabel(),326new PhutilNumber($free_ratio, 3),327new PhutilNumber($reserve, 3)));328return;329}330}331}332333$this->logMessage(334'AUTO',335pht(336'Scaling pool "%s" up to %s daemon(s).',337$this->getPoolLabel(),338new PhutilNumber(count($daemons) + 1)));339340$this->newDaemon();341}342343public function logMessage($type, $message, $context = null) {344return $this->getOverseer()->logMessage($type, $message, $context);345}346347}348349350