mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 08:12:40 +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:
parent
f0f95e5b26
commit
be1a4a9142
3 changed files with 61 additions and 10 deletions
|
@ -378,6 +378,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistNoParentScopeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php',
|
'ArcanistNoParentScopeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php',
|
||||||
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php',
|
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistNoURIConduitException' => 'conduit/ArcanistNoURIConduitException.php',
|
'ArcanistNoURIConduitException' => 'conduit/ArcanistNoURIConduitException.php',
|
||||||
|
'ArcanistNonblockingGuard' => 'utils/ArcanistNonblockingGuard.php',
|
||||||
'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.php',
|
'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.php',
|
||||||
'ArcanistObjectListHardpoint' => 'hardpoint/ArcanistObjectListHardpoint.php',
|
'ArcanistObjectListHardpoint' => 'hardpoint/ArcanistObjectListHardpoint.php',
|
||||||
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistObjectOperatorSpacingXHPASTLinterRule.php',
|
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistObjectOperatorSpacingXHPASTLinterRule.php',
|
||||||
|
@ -1434,6 +1435,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistNoURIConduitException' => 'ArcanistConduitException',
|
'ArcanistNoURIConduitException' => 'ArcanistConduitException',
|
||||||
|
'ArcanistNonblockingGuard' => 'Phobject',
|
||||||
'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer',
|
'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer',
|
||||||
'ArcanistObjectListHardpoint' => 'ArcanistHardpoint',
|
'ArcanistObjectListHardpoint' => 'ArcanistHardpoint',
|
||||||
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
|
|
|
@ -93,16 +93,9 @@ final class ArcanistPrompt
|
||||||
|
|
||||||
// NOTE: We're making stdin nonblocking so that we can respond to signals
|
// 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
|
// 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.
|
$guard = ArcanistNonblockingGuard::newForStream($stdin);
|
||||||
|
|
||||||
if (!phutil_is_windows()) {
|
|
||||||
$ok = stream_set_blocking($stdin, false);
|
|
||||||
if (!$ok) {
|
|
||||||
throw new Exception(pht('Unable to set stdin nonblocking.'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
|
|
||||||
|
@ -123,7 +116,7 @@ final class ArcanistPrompt
|
||||||
|
|
||||||
$is_saved = false;
|
$is_saved = false;
|
||||||
|
|
||||||
if (phutil_is_windows()) {
|
if (!$guard->getIsNonblocking()) {
|
||||||
$response = fgets($stdin);
|
$response = fgets($stdin);
|
||||||
} else {
|
} else {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
56
src/utils/ArcanistNonblockingGuard.php
Normal file
56
src/utils/ArcanistNonblockingGuard.php
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue