[PECL-CVS] [pecl-networking-ssh2] fix-channel-close-timeout: add timeout to close path
[email protected] (Rasmus Lerdorf) Sat, 11 Apr 2026 03:02:51 +0000
| Newsgroups | php.pecl.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Rasmus Lerdorf (rlerdorf)
Date: 2026-04-10T23:02:31-04:00
Commit: https://github.com/php/pecl-networking-ssh2/commit/e414d876beb6134a3b17217992458d8484dd9c63
Raw diff: https://github.com/php/pecl-networking-ssh2/commit/e414d876beb6134a3b17217992458d8484dd9c63.diff
add timeout to close path
Changed paths:
A tests/ssh2_close_timeout.phpt
M ssh2_fopen_wrappers.c
Diff:
diff --git a/ssh2_fopen_wrappers.c b/ssh2_fopen_wrappers.c
index f1b3ce1..c654893 100644
--- a/ssh2_fopen_wrappers.c
+++ b/ssh2_fopen_wrappers.c
@@ -141,8 +141,31 @@ static int php_ssh2_channel_stream_close(php_stream *stream, int close_handle)
if (abstract->refcount) {
efree(abstract->refcount);
}
+#ifdef PHP_SSH2_SESSION_TIMEOUT
+ /* Apply session timeout during channel close to prevent blocking
+ * indefinitely when the remote command hasn't finished or the
+ * connection has been lost. libssh2_channel_free() calls
+ * libssh2_channel_close() internally which blocks waiting for
+ * SSH_MSG_CHANNEL_CLOSE from the remote end. */
+ if (abstract->timeout > 0) {
+ LIBSSH2_SESSION *session;
+ session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
+ if (session) {
+ libssh2_session_set_timeout(session, abstract->timeout);
+ }
+ }
+#endif
libssh2_channel_eof(abstract->channel);
libssh2_channel_free(abstract->channel);
+#ifdef PHP_SSH2_SESSION_TIMEOUT
+ if (abstract->timeout > 0) {
+ LIBSSH2_SESSION *session;
+ session = (LIBSSH2_SESSION *)zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session);
+ if (session) {
+ libssh2_session_set_timeout(session, 0);
+ }
+ }
+#endif
zend_list_delete(abstract->session_rsrc);
}
efree(abstract);
diff --git a/tests/ssh2_close_timeout.phpt b/tests/ssh2_close_timeout.phpt
new file mode 100644
index 0000000..e8079cb
--- /dev/null
+++ b/tests/ssh2_close_timeout.phpt
@@ -0,0 +1,34 @@
+--TEST--
+ssh2_exec() channel close respects stream timeout
+--SKIPIF--
+<?php
+require('ssh2_skip.inc');
+if (!function_exists('ssh2_set_timeout')) {
+ die("skip ssh2_set_timeout not available");
+}
+?>
+--FILE--
+<?php require('ssh2_test.inc');
+
+$ssh = ssh2_connect(TEST_SSH2_HOSTNAME, TEST_SSH2_PORT);
+ssh2_auth_pubkey_file($ssh, TEST_SSH2_USER, TEST_SSH2_PUB_KEY, TEST_SSH2_PRIV_KEY);
+
+$start = microtime(true);
+
+/* Run a command that takes much longer than our timeout */
+$stream = ssh2_exec($ssh, 'sleep 60');
+stream_set_timeout($stream, 3);
+
+/* Without the fix, fclose() blocks for 60s waiting for
+ * SSH_MSG_CHANNEL_CLOSE. With the fix, it respects the
+ * stream timeout and returns in ~3s. */
+fclose($stream);
+
+$elapsed = microtime(true) - $start;
+
+/* Should complete well under 10s. Without the fix this would take 60s. */
+var_dump($elapsed < 10);
+printf("Completed in %.1fs\n", $elapsed);
+--EXPECTF--
+bool(true)
+Completed in %fs