Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(connections): bound blocking SFTP calls and reuse the right cached session - #2843

Merged
datlechin merged 1 commit into
mainfrom
fix/remote-file-blocking-and-cached-session
Sep 14, 2026
Merged

datlechin merged 1 commit into
mainfrom
fix/remote-file-blocking-and-cached-session

Conversation

@datlechin

Copy link
Copy Markdown
Member

Two defects in the SFTP transport behind "Remote Database File" (shipped in #2553, v0.69.0). Found while investigating #2831; both reproduced with a stall proxy in front of a real OpenSSH server. This is the area CLAUDE.md's "Cancelling a connect does not stop the driver" invariant governs, and both fixes follow it.

1. A dropped connection hangs for minutes, and Cancel does nothing

LibSSH2SFTPSession runs realpath, stat and read as blocking libssh2 calls on one serial queue and never set a session timeout. When the peer dropped silently, a call blocked until TCP keepalive gave up (about eleven minutes), Cancel had no effect because Task.cancel() cannot interrupt a blocking C call, and close(), which runs on the same queue, waited behind it, so a disconnect stalled every other remote-file connection.

The fix is the invariant's app-owned-deadline shape: LibSSH2SFTPSession.open now sets libssh2_session_set_timeout to 60s, the same timeout the mobile SFTP client already uses. It bounds one operation, not the transfer: a healthy 32 KiB read returns in milliseconds, so only a dead peer reaches it. A stalled realpath or read then returns a timeout error, the session is discarded, and close() no longer waits.

2. The cached session ignores a changed host

RemoteFileTransportManager.session(for:) cached one SFTP session per connection id and reused it with no check on where it pointed, and only a failed transfer discarded it. So a connection whose transfer never ran (a failing pre-connect script) kept its session, and editing the connection's Host then reused the old server's session: the old server's file was downloaded and stored under the new server's identity.

Two changes:

  • The cache now remembers the server a session was opened against (host, port, user, auth, key, jump hosts, TOTP, with the file path and access mode removed because they name the file, not the server). A session is reused only when that key still matches; a host, port, user or auth change discards it and opens a new one.
  • Any failure after the session is cached discards it, not only a failed transfer. A dead session reused after the Mac slept now throws once and is replaced, instead of failing every retry until relaunch.

Tests

RemoteFileSessionKeyTests: the server key ignores the path and the access mode, and changes with the host, port, or user. The blocking-timeout and discard-on-drop behaviour was validated against a stall proxy during the #2831 investigation; it is not unit-testable without that infrastructure, which is noted here rather than left silent.

Verification

  • build (app + bundled plugins): PASS
  • test (RemoteFileSessionKeyTests, RemoteDatabaseFileTests): PASS, 22 cases
  • lint: clean

@datlechin
datlechin merged commit e5db926 into main Sep 14, 2026
8 checks passed
@datlechin
datlechin deleted the fix/remote-file-blocking-and-cached-session branch September 15, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant