diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -378,6 +378,7 @@ '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 @@ 'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistNoURIConduitException' => 'ArcanistConduitException', + 'ArcanistNonblockingGuard' => 'Phobject', 'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer', 'ArcanistObjectListHardpoint' => 'ArcanistHardpoint', 'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', diff --git a/src/toolset/ArcanistPrompt.php b/src/toolset/ArcanistPrompt.php --- a/src/toolset/ArcanistPrompt.php +++ b/src/toolset/ArcanistPrompt.php @@ -93,16 +93,9 @@ // 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 @@ $is_saved = false; - if (phutil_is_windows()) { + if (!$guard->getIsNonblocking()) { $response = fgets($stdin); } else { while (true) { diff --git a/src/utils/ArcanistNonblockingGuard.php b/src/utils/ArcanistNonblockingGuard.php new file mode 100644 --- /dev/null +++ b/src/utils/ArcanistNonblockingGuard.php @@ -0,0 +1,56 @@ +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; + } + +}