Skip to content

fix: Ignore remote peer aborts when accepting clients #165

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Kissaki
Copy link
Contributor

@Kissaki Kissaki commented Apr 29, 2025

Fixes FTP server becoming unresponsive due to stopped listeners.


A socket may throw SocketException.
This may occur as early as AcceptTcpClientAsync during initial connect hand-shake.

With this change, a socket exception with the error code 10054 is ignored during client accept. We do not want to stop the listener altogether when a new client closes their connection. That must only close the client connection.

Socket error code 10054: WSAECONNRESET, SocketError.ConnectionReset, 'The connection was reset by the remote peer.'

https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener.accepttcpclientasync


The issue is reproducible in a test scenario of generating mass-connects (my tests had 20 times sets of 400 started concurrently as Tasks). The server will consistently become unresponsive (as in, no longer handle new connection requests but still run as a service as if nothing were wrong) after a few hundred connections and connection closings.

I have seen the issue multiple times in production too.

Fixes FTP server becoming unresponsive due to stopped listeners.

---

A socket may throw SocketException.
This may occur as early as AcceptTcpClientAsync during initial connect hand-shake.

With this change, a socket exception with the error code 10054 is ignored during client accept.
We do not want to stop the listener altogether when a new client closes their connection.
That must only close the client connection.

Socket error code 10054: WSAECONNRESET, SocketError.ConnectionReset, 'The connection was reset by the remote peer.'

* https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
* https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socketerror

https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener.accepttcpclientasync

---

The issue is reproducible in a test scenario of generating mass-connects (my tests had 20 times sets of 400 started concurrently as Tasks).
The server will consistently become unresponsive (as in, no longer handle new connection requests but still run as a service as if nothing were wrong) after a few hundred connections and connection closings.

I have seen the issue multiple times in production too.

Co-authored-by: Jan Klass <jan.klass@ite-si.de>
@fubar-coder fubar-coder requested a review from Copilot May 23, 2025 13:39
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR ensures the FTP server’s listener ignores remote peer aborts (WSAECONNRESET) during client acceptance to prevent the listener from stopping.

  • Add a specific catch for SocketError.ConnectionReset in AcceptForListenerAsync.
  • Ensure the listener continues running when a client resets the connection.
  • Aligns behavior with existing handling of stopped listeners.
Comments suppressed due to low confidence (1)

src/FubarDev.FtpServer/MultiBindingTcpListener.cs:190

  • Add a unit or integration test that simulates a SocketException with SocketError.ConnectionReset during AcceptTcpClientAsync to verify that the listener remains active and continues accepting new clients.
return new AcceptInfo(null, index);

@@ -184,6 +184,11 @@ private async Task<AcceptInfo> AcceptForListenerAsync(TcpListener listener, int
// Ignore the exception. This happens when the listener gets stopped.
return new AcceptInfo(null, index);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
Copy link
Preview

Copilot AI May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Update the XML doc comment for AcceptForListenerAsync to explicitly note that a SocketError.ConnectionReset (10054) is intentionally ignored so consumers understand why certain connection resets don’t bubble up.

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no XML doc on this method or on this line. Where would I put it? Add a new one on this private method, and then cascade to callers?

IMO, XML doc should match common practice of specifying which exceptions may be thrown, but the lib currently does not do that - does not include this even on exposed interfaces - AFAIK anway.

@@ -184,6 +184,11 @@ private async Task<AcceptInfo> AcceptForListenerAsync(TcpListener listener, int
// Ignore the exception. This happens when the listener gets stopped.
return new AcceptInfo(null, index);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
Copy link
Preview

Copilot AI May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider merging this filter with the existing exception handling to reduce duplication, e.g., using a combined filter: catch (Exception ex) when (ex is ObjectDisposedException || (ex is SocketException se && se.SocketErrorCode == SocketError.ConnectionReset)).

Copilot uses AI. Check for mistakes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both blocks currently have different comments. Those would be hard to represent.

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