-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Disposing Socket then rebinding fails with SocketError.AddressAlreadyInUse on Unix #23803
Comments
I assume if you add: serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); before the Bind call, it then works fine? Related: |
👍 For me, It's strange Windows allows to bind during TIME_WAIT. |
Thanks for the suggestion. The ReuseAddress option successfully allowed rebinding on macOS and Linux. I think we should consider making the option the default for more consistency between Windows and Unix. @tmds What problems might arise binding to an endpoint with a socket in the TIME_WAIT state? |
Unfortunately that doesn't purely bring consistency as it leads to other issues, e.g. you probably don't want this code succeeding, but it does: using System;
using System.Net;
using System.Net.Sockets;
namespace SocketRebind
{
class Program
{
static void Main(string[] args)
{
for (var i = 0;; i++)
{
Console.WriteLine("Iteration #{0}", i);
var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
serverSocket.Listen(512);
var serverSocket2 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket2.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
serverSocket2.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
serverSocket2.Listen(512);
serverSocket2.Dispose();
serverSocket.Dispose();
}
}
}
} As the thorough write-up at https://stackoverflow.com/a/14388707 states, "Welcome to the wonderful world of portability... or rather the lack of it." |
Thanks @stephentoub. It's true that I don't want to allow two sockets to bind to the same endpoint simultaneously. I'm open to suggestions on how to actually make the Unix behavior more similar to Windows. With libuv, we are able to immediate rebind to a port without resorting to using SO_REUSEADDR. I'm still reading through the write-up. Maybe that will give me some ideas. |
@halter73 If you are using the port for a TCP listen socket, it's appropriate to set the option (per https://stackoverflow.com/questions/6960219/why-not-using-so-reuseaddr-on-unix-tcp-ip-servers).
@stephentoub I believe this is because corefx is setting both SO_REUSEADDR and SO_REUSEPORT. I think this would not be possible if SO_REUSEPORT was not set (not 100% sure). Perhaps it would be better to not set SO_REUSEPORT. |
I guess libuv is setting SO_REUSEADDR and not SO_REUSEPORT. |
That's likely because libuv sets SO_REUSEADDR: |
Interesting. Is there no way to set SO_REUSEADDR and not SO_REUSEPORT with the managed Socket API? |
The reasoning behind this is outlined at https://github.com/dotnet/corefx/issues/11292#issuecomment-243911958 and dotnet/corefx#11509. If this is going to be changed, someone would need to do a thorough investigation to validate it's actually making things better, not worse. |
@tmds, the example I sent behaves the same on Windows, where there is no PAL in the middle. |
If it is too breaking to stop setting SO_REUSEPORT when setting SocketOptionName.ReuseAddress, I would appreciate having some way to set SO_REUSEADDR by itself so the Kestrel Sockets transport can behave more similarly to the Libuv transport on Unix. |
You could always fall back to a P/Invoke to setsockopt, but that's far from ideal. I don't think we want to change how SocketOptionName.ReuseAddress is implemented, as it's explicitly implemented as it is to maintain parity with Windows when it's set to true. Someone could look into what the impact would be of setting SO_REUSEADDR (not SO_REUSEPORT) in the unix PAL when a socket is created, but I don't know what kind fall out that would have, and it's possible there could be variations between unix platforms such that we'd need/want to only do so in specific cases. |
The behavior of the sockets is different. When ExclusiveAddressUse is false, one server becomes unavailable (vs load-balance to both on Linux). When ExclusiveAddressUse is true, setting ReuseAddress will throw. I guess @halter73 may want to set ExclusiveAddressUse on Windows.
I think, as a minimum, we should not set SO_REUSEPORT on Linux. The load balancing is not expected behavior. |
@stephentoub I've been exploring the wonderful world of portability... SO_REUSEADDR en SO_REUSEPORT control 4 features on Windows and Unix platforms. On Windows, default behavior of a
To have the same behavior on Unix, you need to set SO_REUSEADDR. However, when setting this on Unix. It also means multiple sockets can receive the same multicast traffic (2). Setting this, also means on Windows, you can now bind to exactly the same address on TCP and UDP sockets (stealing the port (3)). Linux supports enabling/disabling multicast sharing via SO_REUSEADDR. I believe this is also the case for BSD Unix(*). Port stealing is not supported on Linux (SO_REUSEPORT does loadbalancing (4)), it may be a feature of BSD Unix (using SO_REUSEPORT*). So:
My preference would be to:
Off course, it needs to be validated if this all holds up on different platforms. *: I don't have a BSD Unix derivative, the most interesting doc I found on this is: http://www.unixguide.net/network/socketfaq/4.11.shtml. |
cc: @wfurt, @geoffkizer, @Priya91 |
I missed something when reading http://www.unixguide.net/network/socketfaq/4.11.shtml.
There is also the non-portable way which is to bind to the any address (or an interface address). So to enable sharing SO_REUSEPORT is needed. We can limit enabling undesired features (in particular Linux kernel TCP load balancing) by only setting this for UDP/Dgram sockets. |
Actually we are better off not controlling SO_REUSEADDR for TCP as it may still introduce unwanted xplat differences. So, updated proposal: On TCP Bind set SO_REUSEADDR. This matches with libuv's behavior for tcp and udp. |
Codifying the problem: var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
serverSocket.Listen(512); Now, we see what @halter73 observed, on unix this throws AddressAlreadyInUse when restarting our server. serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); Now, it works on Linux. But on Windows, ReuseAddress means we'd like to steal the address. serverSocket.ExclusiveAddressUse = true; This is the default on Linux, on Windows it makes ReuseAddress throw. if (OSPlatform.CurrentPlatform.IsUnix)
{
serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
} Are we there yet? No, on Unix we are now actually stealing/allowing others to steal our port (BSD-Unix) or loadbalancing the port with others (Linux). Suggested change: For TCP Sockets:
Then this code works on all platforms: var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// optional: serverSocket.ExclusiveAddressUse = true;
serverSocket.Bind(new IPEndPoint(IPAddress.Loopback, 5000));
serverSocket.Listen(512); Open question: What should SocketOptionName.ReuseAddress do for a TCP socket?
@wfurt @geoffkizer @Priya91 @halter73 @stephentoub your thoughts? |
@tmds If SO_REUSEADDR is set for TCP sockets when binding on Unix already, could SocketOptionName.ReuseAddress be mapped exclusively to SO_REUSEPORT on Unix? This seems to align the behavior of Windows and Linux a little more. I guess the behavior is never going to align exactly considering the port stealing vs load balancing behavior on BSD and Linux respectively. Another problem I see with this idea is that there would no longer be a managed API on Unix to set SO_REUSEADDR, but I don't know how common/important that option is for non-TCP and/or non-listen sockets. |
To look at how xplat .NET core needs to behave, Windows is the reference (most existing code).
For non-TCP I'd keep the current behavior. This is needed to make UDP multicast work xplat. |
@wfurt @geoffkizer @Priya91 @halter73 @stephentoub I will look at implementing the proposed changes next week and do a PR. |
@tmds any progress on this one? |
I agree with @tmds, it would be nice to have this fix in some next 2.0.x update. Now our code looks like: public static void Main(string[] args)
{
var serverUrl = "http://*:31000/";
Console.WriteLine("Server host {0}", serverUrl);
var closing = new AutoResetEvent(false);
Console.CancelKeyPress += (sender, ev) =>
{
closing.Set();
ev.Cancel = true;
};
try
{
using (WebApp.Start(serverUrl, OsaApplication.Initialize))
{
Console.WriteLine("Server started at {0}", DateTime.UtcNow);
closing.WaitOne();
}
}
catch (HttpListenerException)
{
// This catch section is just a workaround for .Net Core issue
Console.WriteLine("Oops, we catched https://github.com/dotnet/corefx/issues/25016 bug");
}
Console.WriteLine("Server stopped at {0}", DateTime.UtcNow);
} Do you believe it's acceptable solution? IMO no, looks ugly. [EDIT] Add C# syntax highlighing by @karelz |
"Ugly", but working workaround & affecting a few developers is not a good reason for porting a bug fix into servicing branch. Otherwise we would make servicing branch the new master :)
If you tell me that you cannot work around the problem at all, or that the number of developers affected is more than just few, then I am interested in more details. |
In most cases there isn't because the Socket is not accessible to the developer.
This affects every TCP server. Unless the Socket code gets some 'extra' that isn't needed on Windows, it will fail to restart in the 2 minutes after being stopped. |
Kestrel should not hit this issue as it does not use Sockets in 2.0 - see https://github.com/dotnet/corefx/issues/24562#issuecomment-350358401. |
As does every other .NET TCP server (except Kestrel). |
What are they? How popular they are? How many customers have you seen using them? (I haven't seen any so far) |
Port stealing might be desired by someone, so atleast it should be an option to be enabled. I use ansi c on linux for a few things where saving connections info from socket to a file, then dropping socket in one process, then exec a new version of the process, rebinding the socket in the new process loading the connection info from the old process, then closing the old process. |
like keeping already connections linger, and continue processing on the new process, without anyone knowing ;) |
@Mga-Denmark port stealing is still possible by setting |
@karelz we just ported over from Mono 4.8 to .NET Core 2 and are hitting this issue. In some cases we've been able to specify the Reuse Address socket option, but we are also using HttpListener. All it takes to hit is restarting the server. The new process will fail to start for about a minute. |
@joshgarnett This should be fixed with .NET Core 2.1. If you need a workaround for now, it's pretty ugly, but here it is. Just call |
@halter73 how's that work with something like HttpListener where the underlying socket isn't exposed? |
@joshgarnett I'm not sure if there's a workaround for HttpListener. For the times you have direct access to the managed socket, you can call HttpListener might still fail to bind to a port used by a recently-disposed managed socket even if the recently-disposed socket used the In other words:
The entire SO answer (which was already linked to in a comment above) is a good read if you're interested. |
Is there a fix for using HttpListener on Linux for this yet? We are using Net Core 2.1.3 currently. Thanks! |
That would be the sdk version. What version of the runtime are you using? What's the target framework in your csproj? |
Sorry about that, we are using |
Correct |
Yup. Please give it a try and let us know whether the problem is addressed. Thanks. |
I don't see a
Looks like those packages are still using the Net Core 2.0.5 runtimes and not 2.1.2. |
@doyouevensunbro I noticed that the debian/ubuntu repository is missing the latest SDK version yesterday. I adapted the debian dockerfile to work on Ubuntu Xenial.
|
@joshgarnett Thanks! Will give that a try today. |
Ended up just grabbing the 2.1.302 tarball and manually installing it, and I'm happy to say it fixed the HttpListener socket issue. Thanks! |
Thanks for confirming. |
…This issue caused a bind to a recently used endpoint to fail on macOS and Linux. Addresses aspnet#2820
This issue caused a bind to a recently used endpoint to fail on macOS and Linux. Addresses #2820
This issue caused a bind to a recently used endpoint to fail on macOS and Linux. Addresses #2820
.NET Core 2.0.0 runtime
macOS 10.12.6 Seirra and Ubuntu 16.04.2 LTS (Xenial Xerus)
Expected behavior:
Program should iterate indefinitely. This is what happens on Windows.
Actual behavior:
After the first iteration, Socket.Bind throws "SocketException: Address already in use"
Output (identical on macOS and Ubuntu):
The text was updated successfully, but these errors were encountered: