Changeset View
Standalone View
support/ArcanistRuntime.php
<?php | <?php | ||||
final class ArcanistRuntime { | final class ArcanistRuntime { | ||||
private $workflows; | private $workflows; | ||||
private $logEngine; | private $logEngine; | ||||
private $lastInterruptTime; | |||||
amckinley: Rename this to `$lastInterruptTime`? (Or create an `ArcanistInterrupt` class with `$signo` and… | |||||
private $stack = array(); | |||||
public function execute(array $argv) { | public function execute(array $argv) { | ||||
try { | try { | ||||
$this->checkEnvironment(); | $this->checkEnvironment(); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
echo "CONFIGURATION ERROR\n\n"; | echo "CONFIGURATION ERROR\n\n"; | ||||
echo $ex->getMessage(); | echo $ex->getMessage(); | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | private function executeCore(array $argv) { | ||||
$args = id(new PhutilArgumentParser($argv)) | $args = id(new PhutilArgumentParser($argv)) | ||||
->parseStandardArguments(); | ->parseStandardArguments(); | ||||
$is_trace = $args->getArg('trace'); | $is_trace = $args->getArg('trace'); | ||||
$log->setShowTraceMessages($is_trace); | $log->setShowTraceMessages($is_trace); | ||||
$log->writeTrace(pht('ARGV'), csprintf('%Ls', $argv)); | $log->writeTrace(pht('ARGV'), csprintf('%Ls', $argv)); | ||||
// We're installing the signal handler after parsing "--trace" so that it | |||||
// can emit debugging messages. This means there's a very small window at | |||||
// startup where signals have no special handling, but we couldn't really | |||||
// route them or do anything interesting with them anyway. | |||||
$this->installSignalHandler(); | |||||
$args->parsePartial($config_args, true); | $args->parsePartial($config_args, true); | ||||
$config_engine = $this->loadConfiguration($args); | $config_engine = $this->loadConfiguration($args); | ||||
$config = $config_engine->newConfigurationSourceList(); | $config = $config_engine->newConfigurationSourceList(); | ||||
$this->loadLibraries($args, $config); | $this->loadLibraries($args, $config); | ||||
// Now that we've loaded libraries, we can validate configuration. | // Now that we've loaded libraries, we can validate configuration. | ||||
▲ Show 20 Lines • Show All 430 Lines • ▼ Show 20 Lines | private function applyAliasEffects(array $effects, array $argv) { | ||||
if ($command !== null) { | if ($command !== null) { | ||||
$argv = array_merge(array($command), $arguments); | $argv = array_merge(array($command), $arguments); | ||||
} | } | ||||
return $argv; | return $argv; | ||||
} | } | ||||
private function installSignalHandler() { | |||||
$log = $this->getLogEngine(); | |||||
if (!function_exists('pcntl_signal')) { | |||||
$log->writeTrace( | |||||
pht('PCNTL'), | |||||
pht( | |||||
'Unable to install signal handler, pcntl_signal() unavailable. '. | |||||
'Continuing without signal handling.')); | |||||
return; | |||||
} | |||||
// NOTE: SIGHUP, SIGTERM and SIGWINCH are handled by "PhutilSignalRouter". | |||||
// This logic is largely similar to the logic there, but more specific to | |||||
// Arcanist workflows. | |||||
pcntl_signal(SIGINT, array($this, 'routeSignal')); | |||||
} | |||||
public function routeSignal($signo) { | |||||
switch ($signo) { | |||||
case SIGINT: | |||||
$this->routeInterruptSignal($signo); | |||||
break; | |||||
} | |||||
} | |||||
Done Inline ActionsI'm still not actually registering for or routing any other signals, but the API won't need to change if we choose to in the future. epriestley: I'm still not //actually// registering for or routing any other signals, but the API won't need… | |||||
private function routeInterruptSignal($signo) { | |||||
$log = $this->getLogEngine(); | |||||
$last_interrupt = $this->lastInterruptTime; | |||||
$now = microtime(true); | |||||
$this->lastInterruptTime = $now; | |||||
$should_exit = false; | |||||
// If we received another SIGINT recently, always exit. This implements | |||||
// "press ^C twice in quick succession to exit" regardless of what the | |||||
// workflow may decide to do. | |||||
$interval = 2; | |||||
if ($last_interrupt !== null) { | |||||
if ($now - $last_interrupt < $interval) { | |||||
$should_exit = true; | |||||
} | |||||
} | |||||
$handler = null; | |||||
if (!$should_exit) { | |||||
// Look for an interrupt handler in the current workflow stack. | |||||
$stack = $this->getWorkflowStack(); | |||||
foreach ($stack as $workflow) { | |||||
if ($workflow->canHandleSignal($signo)) { | |||||
$handler = $workflow; | |||||
break; | |||||
} | |||||
} | |||||
Not Done Inline ActionsI might redefine canHandleInterrupt to take a $signo argument to avoid making subclasses reimplement the handlers from their parent that they don't want to handle. This is more to support extensions that want to do stuff like, uh.... register a signal handler that dumps arc upload progress to /tmp/arc_progress after receiving USR1 or something. amckinley: I might redefine `canHandleInterrupt` to take a `$signo` argument to avoid making subclasses… | |||||
Done Inline ActionsI think only USR1 and USR2 are even plausible, and since there's no way to send them from the keyboard (as far as I could tell?) it seems like a stretch to imagine that we'd ever use them for anything. I'll happily generalize this if you can identify any other signal that we can send from the keyboard and reasonably do something in response to, but last time I looked everything either can't be sent from the keyboard; or has some other important meaning already; or didn't seem to be something we can handle. In particular, we already handle SIGHUP (dump a stack trace; can't send from keyboard AFIAK), SIGTERM (we just terminate slightly more cleanly, don't know any way to send it from the keyboard) and SIGWINCH (as a legitimate signal when you resize your terminal window, updating terminal window metrics caches) elsewhere. ^Z sends a suspend signal but I didn't have any luck doing anything with it or preventing the suspension, as far as I can recall. I don't think USR1 and USR2 can be sent from the keyboard, or I couldn't figure out how at least. They could plausibly be used for something, but it's a stretch for me to imagine us building features based on "open a second terminal window, then run ps, then..." epriestley: I //think// only USR1 and USR2 are even plausible, and since there's no way to send them from… | |||||
Not Done Inline ActionsBefore I do more research, have you seen the trick that dd uses to make it report progress? It’s literally “send USR1 from another terminal”: https://askubuntu.com/a/215521 I’m not arguing that that’s a super-brilliant and accessible design pattern, but there is some precedent for it. amckinley: Before I do more research, have you seen the trick that dd uses to make it report progress? | |||||
// If no workflow in the current execution stack can handle an interrupt | |||||
// signal, just exit on the first interrupt. | |||||
if (!$handler) { | |||||
$should_exit = true; | |||||
} | |||||
} | |||||
if ($should_exit) { | |||||
$log->writeHint( | |||||
pht('INTERRUPT'), | |||||
pht('Interrupted by SIGINT (^C).')); | |||||
Not Done Inline ActionsWon't this get printed under lots of circumstances where SIGINT wasn't the actual signal though? amckinley: Won't this get printed under lots of circumstances where `SIGINT` wasn't the actual signal… | |||||
Done Inline ActionsRight now, since we only register for SIGINT with pcntl_signal(SIGINT, ...) above, this stuff will only be called for actual SIGINT. epriestley: Right now, since we only register for SIGINT with `pcntl_signal(SIGINT, ...)` above, this stuff… | |||||
exit(128 + $signo); | |||||
} | |||||
$log->writeHint( | |||||
pht('INTERRUPT'), | |||||
pht('Press ^C again to exit.')); | |||||
$handler->handleSignal($signo); | |||||
} | |||||
public function pushWorkflow(ArcanistWorkflow $workflow) { | |||||
$this->stack[] = $workflow; | |||||
return $this; | |||||
} | |||||
public function popWorkflow() { | |||||
if (!$this->stack) { | |||||
throw new Exception(pht('Trying to pop an empty workflow stack!')); | |||||
} | |||||
return array_pop($this->stack); | |||||
} | |||||
public function getWorkflowStack() { | |||||
return $this->stack; | |||||
} | |||||
} | } |
Rename this to $lastInterruptTime? (Or create an ArcanistInterrupt class with $signo and $timeHandled (and maybe $handledBy) instance variables).
(And maybe initialize to 0; I had to think about the interval counting behavior below).