ShellStream.Dispose deadlocks when the server closes the channel at the same time
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 69/100
- Type d'issue
- Bug
- Clarté
- Clairement spécifiée
- Activité
- Active
- Stack technique
- csharp
- Domaine
- backend, networking
Piste de recherche
Start by reading src/Renci.SshNet/ShellStream.cs around Dispose(bool) and Channel_Closed, then trace the Close paths in src/Renci.SshNet/Channels/Channel.cs. Check how locks and event handlers interact in the reported race, and add a regression test that reproduces concurrent disposal and remote channel closure. Done means the test completes reliably without deadlocking.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Version
SSH.NET 2026.0.0 (tag 2026.0.0, 7b2fd3d). The same code is on develop today. .NET 10, Linux x64 and arm64.
Summary
Disposing a ShellStream while the server closes its channel can deadlock forever. The two threads take the same two locks in opposite order, and neither wait has a timeout:
| Thread | Holds | Waits for |
|---|---|---|
Caller: ShellStream.Dispose() |
ShellStream._sync |
Channel._messagingLock |
Session listener: SSH_MSG_CHANNEL_CLOSE received |
Channel._messagingLock |
ShellStream._sync |
The two paths
Caller thread, disposing the stream:
ShellStream.Dispose(bool)takeslock (_sync)(src/Renci.SshNet/ShellStream.cs:970).- Still inside that lock it calls
_channel.Dispose()(ShellStream.cs:986). Channel.Dispose(bool)callsClose()(src/Renci.SshNet/Channels/Channel.cs:844), which takeslock (_messagingLock)(Channel.cs:534).
Listener thread, handling the server's SSH_MSG_CHANNEL_CLOSE:
OnChannelClosecallsOnClose()(Channel.cs:698), which callsClose()(Channel.cs:426) and takeslock (_messagingLock)(Channel.cs:534).- Still inside that lock it raises
Closed(Channel.cs:578). ShellStream.Channel_Closedhandles it by callingDispose()(ShellStream.cs:1006), which waits forlock (_sync)(ShellStream.cs:970).
If the caller enters step 1 of its path before the listener reaches step 3 of its own, each thread holds the lock the other one needs. A stack dump of a hung process shows exactly these two frames.
Repro
The race is timing-dependent. A simplified loop against a real OpenSSH server (a sketch of our test, not run as written here):
for (var i = 0; i < 100; i++)
{
using var client = new SshClient(host, port, user, password);
client.Connect();
var stream = client.CreateShellStream("xterm", 80, 24, 0, 0, 4096);
stream.WriteLine("exit"); // the server closes the channel shortly after
stream.Dispose(); // races with the listener's Channel_Closed -> Dispose
}
In our test suite this hung 4 of 15 runs: a test class whose Dispose() disposed the ShellStream while the remote shell exited. The process never recovered; CI killed it after an hour.
Workaround we use
Call SshClient.Disconnect() before disposing the ShellStream. That stops the listener thread, so no Closed event can arrive while Dispose holds _sync. With it, 12 of 12 runs passed.
Possible fixes
- In
ShellStream.Dispose, detach the handlers and dispose_channeloutsidelock (_sync): take the lock only to set_disposed, then release it before calling_channel.Dispose(). - Or, in
Channel.Close, raiseClosedafter leavinglock (_messagingLock), so that no user code runs while that lock is held.
- Langage dominant
- C#
- Étoiles
- 4.4k
- Forks
- 992
- Merge moyen
- 4 j 22 h
- PR mergées (30 j)
- 5
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de sshnet/SSH.NET
-
Difficulté 3/5 1-2 jours Accessibilité débutants 55/100
-
Difficulté 5/5 Plus d'une semaine Accessibilité débutants 45/100
-
ShellStream.Expect over-discards on undecodable bytes, driving ArrayBuffer.ActiveLength negativeOuverte
Difficulté 4/5 3-5 jours Accessibilité débutants 52/100
-
Difficulté 3/5 1-2 jours Accessibilité débutants 68/100
-
Using SshClient on Linux under Wine throws System.Security.Cryptography.CryptographicExceptionOuverte
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
Toutes les issues de sshnet/SSH.NET
Issues similaires
-
dev/release: verify-release-candidate.sh runs 0 Java tests with Debian's MavenPeut-être pris @kou l’a pris aujourd’hui. OuverteType: bug
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
apache/arrow-adbc#4947 ·
Les mainteneurs répondent en général sous 1 jour
-
:watch: Not Triaged CAC
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
dotnet/AspNetCore.Docs#38001 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
Nimblesite/SharpLsp#468 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 1/5 Moins d'une heure Accessibilité débutants 88/100
elastic/ecs-dotnet#575 ·
-
V4 alert-rule reads have no scope gate, so any authenticated credential can list alert rulesOuvertealerts bug effort:S P2
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
nightscout/nocturne#2511 ·
Les mainteneurs répondent en général sous 1 jour