diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2afe1995..e065636f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/toolset/ArcanistPrompt.php b/src/toolset/ArcanistPrompt.php index caff32c6..7c201dd7 100644 --- a/src/toolset/ArcanistPrompt.php +++ b/src/toolset/ArcanistPrompt.php @@ -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) { diff --git a/src/utils/ArcanistNonblockingGuard.php b/src/utils/ArcanistNonblockingGuard.php new file mode 100644 index 00000000..17a300f2 --- /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; + } + +} diff --git a/support/arcanoid/arcanoid.py b/support/arcanoid/arcanoid.py index 55d191a7..6d99d5e0 100755 --- a/support/arcanoid/arcanoid.py +++ b/support/arcanoid/arcanoid.py @@ -212,11 +212,16 @@ def main(stdscr): i = int(time.time() / 0.8) for x in range(width): for y in range(6): - game.addch(height / 2 + y - 3 + (x / 8 + i) % 2, x, - curses.ACS_BLOCK, - curses.A_BOLD | curses.color_pair(colors[y])) - game.addstr(height / 2, (width - len(message)) / 2, message, - curses.A_BOLD | curses.color_pair(7)) + game.addch( + int(height / 2 + y - 3 + (x / 8 + i) % 2), + x, + curses.ACS_BLOCK, + curses.A_BOLD | curses.color_pair(colors[y])) + game.addstr( + int(height / 2), + int((width - len(message)) / 2), + message, + curses.A_BOLD | curses.color_pair(7)) game.refresh() status.refresh()