1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-12-22 21:40:54 +01:00

Avoid leaving stdin in nonblocking mode after a modern prompt

Summary:
Ref T13649. Currently, "arc" may leave stdin nonblocking after showing a prompt. This can cause various odd behaviors down the line.

I can't immediately reproduce this behavior on macOS in "zsh" or "bash" (I'm unable to get stdin to remain nonblocking beyond the process lifespan), and also don't have pcntl locally so there's a fair amount of handwaving here.

Test Plan: This is somewhat speculative since I can't immediately reproduce the behavior. I tested the locally-reachable paths (no pcntl) but they're not interesting.

Maniphest Tasks: T13649

Differential Revision: https://secure.phabricator.com/D21666
This commit is contained in:
epriestley 2021-05-30 01:20:37 -07:00
parent f0f95e5b26
commit be1a4a9142
3 changed files with 61 additions and 10 deletions

View file

@ -378,6 +378,7 @@ phutil_register_library_map(array(
'ArcanistNoParentScopeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php',
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php',
'ArcanistNoURIConduitException' => 'conduit/ArcanistNoURIConduitException.php',
'ArcanistNonblockingGuard' => 'utils/ArcanistNonblockingGuard.php',
'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.php',
'ArcanistObjectListHardpoint' => 'hardpoint/ArcanistObjectListHardpoint.php',
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistObjectOperatorSpacingXHPASTLinterRule.php',
@ -1434,6 +1435,7 @@ phutil_register_library_map(array(
'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistNoURIConduitException' => 'ArcanistConduitException',
'ArcanistNonblockingGuard' => 'Phobject',
'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer',
'ArcanistObjectListHardpoint' => 'ArcanistHardpoint',
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',

View file

@ -93,16 +93,9 @@ final class ArcanistPrompt
// NOTE: We're making stdin nonblocking so that we can respond to signals
// immediately. If we don't, and you ^C during a prompt, the program does
// not handle the signal until fgets() returns.
// not handle the signal until fgets() returns. See also T13649.
// On Windows, we skip this because stdin can not be made nonblocking.
if (!phutil_is_windows()) {
$ok = stream_set_blocking($stdin, false);
if (!$ok) {
throw new Exception(pht('Unable to set stdin nonblocking.'));
}
}
$guard = ArcanistNonblockingGuard::newForStream($stdin);
echo "\n";
@ -123,7 +116,7 @@ final class ArcanistPrompt
$is_saved = false;
if (phutil_is_windows()) {
if (!$guard->getIsNonblocking()) {
$response = fgets($stdin);
} else {
while (true) {

View file

@ -0,0 +1,56 @@
<?php
final class ArcanistNonblockingGuard
extends Phobject {
private $stream;
private $didSetNonblocking;
public static function newForStream($stream) {
$guard = new self();
$guard->stream = $stream;
if (phutil_is_windows()) {
// On Windows, we skip this because stdin can not be made nonblocking.
} else if (!function_exists('pcntl_signal')) {
// If we can't handle signals, we: can't reset the flag if we're
// interrupted; but also don't benefit from setting it in the first
// place since it's only relevant for handling interrupts during
// prompts. So just skip this.
} else {
// See T13649. Note that the "blocked" key identifies whether the
// stream is blocking or nonblocking, not whether it will block when
// read or written.
$metadata = stream_get_meta_data($stream);
$is_blocking = idx($metadata, 'blocked');
if ($is_blocking) {
$ok = stream_set_blocking($stream, false);
if (!$ok) {
throw new Exception(pht('Unable to set stream nonblocking.'));
}
$guard->didSetNonblocking = true;
}
}
return $guard;
}
public function getIsNonblocking() {
return $this->didSetNonblocking;
}
public function __destruct() {
if ($this->stream && $this->didSetNonblocking) {
stream_set_blocking($this->stream, true);
}
$this->stream = null;
}
}