Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simultaneous SFTP UploadFile and ListDirectory causes disconnect #1612

Open
DavidYawCSpeed opened this issue Mar 7, 2025 · 0 comments
Open

Comments

@DavidYawCSpeed
Copy link

I'm using one SftpClient object, simultaneously on two different threads. While the primary thread is doing a large upload (UploadFile()), I have a background task making sure we're still connected by doing ListDirectory("/") every few seconds. When I do that, the SftpClient disconnects.

I tested this using two different SSH servers: My primary target is a small embedded device, running Dropbear SSH server. On that server, the disconnect usually happens on the very first call to ListDirectory. For testing, I also tried a server running OpenSSH on Rocky Linux 8. Using that, the call to ListDirectory generally succeeds several times, but it eventually fails as well.

I am using SSH.NET version 2024.2.0 on .Net 9.

Here is a self-contained demonstration program.

using Renci.SshNet;

namespace SshMultiUseTest;

internal class Program
{
#if false
    // This is my small embedded device. Low power. Running PetaLinux on a Xilinx SoC.
    // SSH Server is Dropbear v2020.80. (No, I can't change that.)

    // (Why 127.0.0.1? I'm on a work computer, with a strict VPN. I have to use an SSH tunnel
    // to make connections to machines on the local network.)
    const string host = "127.0.0.1";
    const int port = 2122;
    const string user = "root";
    const string privKey = "DeviceKey.id_rsa";
#else
    // This is my Linux server. It's a normal desktop PC, maybe 10 years old. Intel i7 of some sort.
    // Running Rocky Linux 8. SSH Server is OpenSSH 8.0p1.
    const string host = "127.0.0.1";
    const int port = 2200;
    const string user = "dyaw";
    const string privKey = "id_rsa";
#endif

    const string largeFile = "C:\\Data\\deviceUpgrade.mfuf";

    static void Main(string[] args)
    {
        SftpClient sftpClient = new(
            host, port, user,
            new PrivateKeyFile(typeof(Program).Assembly.GetManifestResourceStream(typeof(Program), privKey)!));

        sftpClient.Connect();

        // Verify connectivity
        foreach (var item in sftpClient.ListDirectory("/")) { }

        using FileStream fs = new(largeFile, FileMode.Open, FileAccess.Read);

        int count = 0;
        void callback(ulong uploaded)
        {
            int newCount = Interlocked.Increment(ref count);

            if (newCount < 20 || newCount % 100 == 0)
                Console.WriteLine("Uploaded: {0:N0}", uploaded);
        }

        Task largeUploadTask = Task.Run(() => sftpClient.UploadFile(fs, "/home/" + user + "/uploadTest", callback));

        int exceptionCount = 0;
        while (!largeUploadTask.IsCompleted)
        {
            Thread.Sleep(1000);
            try
            {
                foreach (var item in sftpClient.ListDirectory("/")) { }
                Console.WriteLine("Small Task: Directory Listing complete");
                exceptionCount = 0;
            }
            catch (Exception e)
            {
                if (++exceptionCount < 5)
                {
                    // These get repetitive after the first couple. Be quiet.
                    Console.WriteLine("From small task: {0}: {1}", e.GetType().FullName, e.Message);
                }
            }
        }

        try
        {
            largeUploadTask.Wait();
        }
        catch (Exception e)
        {
            if (e is AggregateException && e.InnerException != null)
                e = e.InnerException;

            Console.WriteLine("From large task: {0}: {1}", e.GetType().FullName, e.Message);
        }
    }
}

Result, connecting to the embedded device:

Uploaded: 65,460
Uploaded: 32,730
Uploaded: 98,190
Uploaded: 130,920
Uploaded: 163,650
Uploaded: 196,380
Uploaded: 229,110
Uploaded: 261,840
Uploaded: 294,570
Uploaded: 327,300
Uploaded: 360,030
Uploaded: 392,760
Uploaded: 425,490
Uploaded: 458,220
Uploaded: 490,950
Uploaded: 523,680
Uploaded: 556,410
Uploaded: 589,140
Uploaded: 621,870
Uploaded: 3,273,000
Uploaded: 6,546,000
From small task: Renci.SshNet.Common.SshException: Channel was closed.
From small task: System.InvalidOperationException: The session is not open.
From small task: System.InvalidOperationException: The session is not open.
From small task: System.InvalidOperationException: The session is not open.
(long pause here)
From large task: Renci.SshNet.Common.SshOperationTimeoutException: Session operation has timed out

Result, connecting to my regular server:

Uploaded: 65,478
Uploaded: 32,739
Uploaded: 98,217
Uploaded: 130,956
Uploaded: 163,695
Uploaded: 196,434
Uploaded: 229,173
Uploaded: 261,912
Uploaded: 294,651
Uploaded: 327,390
Uploaded: 360,129
Uploaded: 392,868
Uploaded: 425,607
Uploaded: 458,346
Uploaded: 523,824
Uploaded: 556,563
Uploaded: 491,085
Uploaded: 589,302
Uploaded: 622,041
Uploaded: 3,273,900
Uploaded: 6,547,800
Uploaded: 9,821,700
Uploaded: 13,095,600
Uploaded: 16,369,500
Uploaded: 19,643,400
Uploaded: 22,917,300
Uploaded: 26,191,200
Uploaded: 29,465,100
Uploaded: 32,739,000
Uploaded: 36,012,900
Uploaded: 39,286,800
Uploaded: 42,560,700
Uploaded: 45,834,600
Uploaded: 49,108,500
Uploaded: 52,382,400
Uploaded: 55,656,300
Small Task: Directory Listing complete
Uploaded: 58,930,200
Uploaded: 62,204,100
Uploaded: 65,478,000
Uploaded: 68,751,900
Uploaded: 72,025,800
Uploaded: 75,299,700
Uploaded: 78,573,600
Uploaded: 81,847,500
Uploaded: 85,121,400
Uploaded: 88,395,300
Uploaded: 91,669,200
Uploaded: 94,943,100
Uploaded: 98,217,000
Uploaded: 101,490,900
Uploaded: 104,764,800
Uploaded: 108,038,700
Uploaded: 111,312,600
Uploaded: 114,586,500
Uploaded: 117,860,400
Uploaded: 121,134,300
Uploaded: 124,408,200
Uploaded: 127,682,100
Uploaded: 130,956,000
Uploaded: 134,229,900
Uploaded: 137,503,800
Uploaded: 140,777,700
Uploaded: 144,051,600
Uploaded: 147,325,500
Uploaded: 150,599,400
Uploaded: 153,873,300
Uploaded: 157,147,200
Uploaded: 160,421,100
Uploaded: 163,695,000
Uploaded: 166,968,900
Uploaded: 170,242,800
Uploaded: 173,516,700
Uploaded: 176,790,600
Uploaded: 180,064,500
Small Task: Directory Listing complete
Uploaded: 183,338,400
Uploaded: 186,612,300
Uploaded: 189,886,200
Uploaded: 193,160,100
Uploaded: 196,434,000
Uploaded: 199,707,900
Uploaded: 202,981,800
Uploaded: 206,255,700
Uploaded: 209,529,600
Uploaded: 212,803,500
Uploaded: 216,077,400
Uploaded: 219,351,300
Uploaded: 222,625,200
Uploaded: 225,899,100
Uploaded: 229,173,000
Uploaded: 232,446,900
Uploaded: 235,720,800
Uploaded: 238,994,700
Uploaded: 242,268,600
Uploaded: 245,542,500
Uploaded: 248,816,400
Uploaded: 252,090,300
Uploaded: 255,364,200
Uploaded: 258,638,100
Uploaded: 261,912,000
Uploaded: 265,185,900
Uploaded: 268,459,800
Uploaded: 271,733,700
Small Task: Directory Listing complete
Uploaded: 275,007,600
Uploaded: 278,281,500
Uploaded: 281,555,400
Uploaded: 284,829,300
Uploaded: 288,103,200
Uploaded: 291,377,100
Uploaded: 294,651,000
Uploaded: 297,924,900
Uploaded: 301,198,800
Uploaded: 304,472,700
Uploaded: 307,746,600
Uploaded: 311,020,500
Uploaded: 314,294,400
Uploaded: 317,568,300
Uploaded: 320,842,200
Uploaded: 324,116,100
Uploaded: 327,390,000
Uploaded: 330,663,900
Uploaded: 333,937,800
Uploaded: 337,211,700
Uploaded: 340,485,600
From small task: Renci.SshNet.Common.SshException: Channel was closed.
From small task: System.InvalidOperationException: The session is not open.
From small task: System.InvalidOperationException: The session is not open.
From small task: System.InvalidOperationException: The session is not open.
(long pause here)
From large task: Renci.SshNet.Common.SshOperationTimeoutException: Session operation has timed out
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

No branches or pull requests

1 participant