[pecl-networking-ssh2] fix-channel-close-timeout: add timeout to close path

From: Date: Sat, 11 Apr 2026 03:02:51 +0000
Subject: [pecl-networking-ssh2] fix-channel-close-timeout: add timeout to close path
Groups: php.pecl.cvs 
Request: Send a blank email to pecl-cvs+get-28531@lists.php.net to get a copy of this message
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

« previous php.pecl.cvs (#28531) next »