In your case where the two instances started very close together? If not a double race bind shouldn't be a problem.
On Tue, 18 Nov 2025 at 12:59, Zhang Jie (Kn) <[email protected]> wrote: > I searched the web and Linux commit history, after which I only found two > meaningful pieces of information: > 1) before linux 2.6, there's double bind race > 2) linux 6.0.16, there's double bind race > but it seems there's no reports in 5.x kernel. > > --- > > And, I talked with chatgpt, it says: > > ``` > > "Double bind race" refers to a scenario where multiple threads/CPUs > attempt to bind() to the same (IP, port, proto) almost simultaneously. Due > to a race condition window in the kernel when creating and inserting an > inet_bind_bucket (port binding bucket), the following may occur : > > - > > Both threads may believe the port is available. > - > > Both threads may create their own inet_bind_bucket. > - > > The kernel might ultimately insert one bucket, but in an inconsistent > state. > - > > This can lead to one thread's bind operation failing with an > unexpected error (e.g., not EADDRINUSE), or, in older versions, even result > in a temporary "successful duplicate bind" (which theoretically should not > happen) . > > This type of race condition is typically difficult to reproduce and > requires a multi-core environment with near-instantaneous concurrent > attempts to bind to the same port . > > ``` > > I don't know if the kernel bug really exists, or is it caused by some > virtualization technology bugs. > On Tuesday, November 18, 2025 at 8:49:18 PM UTC+8 Zhang Jie (Kn) wrote: > >> The release is Tencent tlinux3, the kernel is Linux 5.4, it's modified by >> Tencent. >> >> --- >> >> In golang, net.ListenTCP will set REUSEADDR to quickly reuse the same >> ipport, but listen twice shouldn't success unless REUSEPORT set. >> >> When the problem occurs, we try use `fuser port/tcp` to check if there's >> only one process listening on the same ipport. Yes, there's only one. >> The other process trying to listen on the same ipport succeeded: >> ``` >> ln, err := net.ListenTCP(...), >> ``` >> here err is nil. >> >> Then: >> ``` >> conn, err := ln.Accept() >> ``` >> here conn is nil, and err != nil, but in our previous code, the err is >> ignored (bad practice), I didn't know what error it returned. >> And I cannot reproduce this problem. >> On Tuesday, November 18, 2025 at 8:38:59 PM UTC+8 Robert Engels wrote: >> >>> >>> I believe that if the port has pre ious connections still in the >>> CLOSE_WAIT state (could be a previous run of the same app) the port cannot >>> be opened. >>> >>> Linux also has a REUSE_PORT option that allows multiple processes to >>> bind to the same port and it balances the incoming requests automatically. >>> >>> On Nov 18, 2025, at 3:36 AM, 'Brian Candler' via golang-nuts < >>> [email protected]> wrote: >>> >>> When the problem occurs, I suggest you look at "ss -natp" ("netstat >>> -natp" on older systems) and see if you really do have two listening >>> sockets on the same port and address. >>> >>> >>> If you do, that seems like a kernel bug / some sort of race. What >>> kernel version is the VM running? (The kernel on the physical host >>> shouldn't really make any difference). >>> >>> On Tuesday, 18 November 2025 at 03:11:24 UTC Zhang Jie (Kn) wrote: >>> >>>> Hello everyone, >>>> >>>> Over the past year, I've encountered two strange issues >>>> with net.ListenTCPand listener.Accept. Without explicitly >>>> enabling reuseport, multiple service processes on the same machine, all >>>> searching for available ports starting from 9000, managed to successfully >>>> call listenon the same IP and port. At least when calling net.ListenTCP, it >>>> returned err == nil, and the error only appeared during listener.Accept. >>>> However, at the time, we weren't explicitly checking the returned error or >>>> printing the error message. Instead, when we found the returned conn == >>>> nil, we kept retrying listener.Acceptin a for-loop. >>>> >>>> We've reproduced this issue twice within a year. The environment was a >>>> virtual machine allocated on a physical host with a Linux 5.4 kernel, and >>>> it was very difficult to reproduce. Our immediate fix was to add the error >>>> checking logic and print the specific error. While handling this issue, we >>>> also ran into the problem with netError.Temporary(). >>>> >>>> I completely agree with Ian's insight: "Whether an error is temporary >>>> depends on what you were doing at the time." For the specific case >>>> of listener.Accept(), even if netError.Temporary()returns true, retrying >>>> doesn't necessarily mean the service can remain available. Errors always >>>> manifest in wildly different ways. In our specific flawed usage scenario, >>>> the service had already successfully registered with the name service, and >>>> other services had already discovered it and started sending requests. >>>> However, because the listenwasn't actually successful (the IP:port was held >>>> by another process), it resulted in persistent access failures. >>>> >>>> But if we don't use Temporary(), asking developers to enumerate all >>>> possible temporary errors that can be retried isn't a very straightforward >>>> task. Could several categorical functions, similar to IsTimeout, be >>>> provided to allow developers to combine them freely? For example, something >>>> like if ne.IsTimeout() || ne.IsXXX() || ne.IsYYY(). >>>> >>>> On Friday, April 22, 2022 at 6:39:39 AM UTC+8 Caleb Spare wrote: >>>> >>>>> On Thu, Apr 21, 2022 at 7:16 AM 'Bryan C. Mills' via golang-nuts >>>>> <[email protected]> wrote: >>>>> > >>>>> > Even ENFILE and EMFILE are not necessarily blindly retriable: if the >>>>> process has run out of files, it may be because they have leaked (for >>>>> example, they may be reachable from deadlocked goroutines). >>>>> > If that is the case, it is arguably better for the program to fail >>>>> with a useful error than to keep retrying without making progress. >>>>> > >>>>> > (I would argue that the retry loop in net/http.Server is a mistake, >>>>> and should be replaced with a user-configurable semaphore limiting the >>>>> number of open connections — thus avoiding the file exhaustion in the >>>>> first >>>>> place!) >>>>> >>>>> ENFILE might be caused by a different process entirely, no? >>>>> >>>>> > >>>>> > On Wednesday, April 20, 2022 at 10:49:20 PM UTC-4 Ian Lance Taylor >>>>> wrote: >>>>> >> >>>>> >> On Wed, Apr 20, 2022 at 6:46 PM 'Damien Neil' via golang-nuts >>>>> >> <[email protected]> wrote: >>>>> >> > >>>>> >> > The reason for deprecating Temporary is that the set of >>>>> "temporary" errors was extremely ill-defined. The initial issue for >>>>> https://go.dev/issue/45729 discusses the de facto definition of >>>>> Temporary and the confusion resulting from it. >>>>> >> > >>>>> >> > Perhaps there's a useful definition of temporary or retriable >>>>> errors, perhaps limited in scope to syscall errors such as EINTR and >>>>> EMFILE. I don't know what that definition is, but perhaps we should come >>>>> up >>>>> with one and add an os.ErrTemporary or some such. I don't think leaving >>>>> net.Error.Temporary undeprecated was the right choice, however; the need >>>>> for a good way to identify transient system errors such as EMFILE doesn't >>>>> mean that it was a good way to do so or could ever be made into one. >>>>> >> >>>>> >> To frame issue 45729 in a different way, whether an error is >>>>> temporary >>>>> >> is not a general characteristic. It depends on the context in which >>>>> >> it appears. For the Accept loop in http.Server.Serve really the >>>>> only >>>>> >> plausible temporary errors are ENFILE and EMFILE. Perhaps the net >>>>> >> package needs a RetriableAcceptError function. >>>>> >> >>>>> >> Ian >>>>> >> >>>>> >> >>>>> >> >>>>> >> > On Wednesday, April 20, 2022 at 6:02:34 PM UTC-7 [email protected] >>>>> wrote: >>>>> >> >> >>>>> >> >> In Go 1.18 net.Error.Temporary was deprecated (see >>>>> >> >> https://go.dev/issue/45729). However, in trying to remove it >>>>> from my >>>>> >> >> code, I found one way in which Temporary is used for which there >>>>> is no >>>>> >> >> obvious replacement: in a TCP server's Accept loop, when >>>>> deciding >>>>> >> >> whether to wait and retry an Accept error. >>>>> >> >> >>>>> >> >> You can see an example of this in net/http.Server today: >>>>> >> >> >>>>> https://github.com/golang/go/blob/ab9d31da9e088a271e656120a3d99cd3b1103ab6/src/net/http/server.go#L3047-L3059 >>>>> >> >> >>>>> >> >> In this case, Temporary seems useful, and enumerating the >>>>> OS-specific >>>>> >> >> errors myself doesn't seem like a good idea. >>>>> >> >> >>>>> >> >> Does anyone have a good solution here? It doesn't seem like this >>>>> was >>>>> >> >> adequately considered when making this deprecation decision. >>>>> >> >> >>>>> >> >> Caleb >>>>> >> > >>>>> >> > -- >>>>> >> > You received this message because you are subscribed to the >>>>> Google Groups "golang-nuts" group. >>>>> >> > To unsubscribe from this group and stop receiving emails from it, >>>>> send an email to [email protected]. >>>>> >> > To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/golang-nuts/1024e668-795f-454f-a659-ab5a4bf9517cn%40googlegroups.com. >>>>> >>>>> > >>>>> > -- >>>>> > You received this message because you are subscribed to the Google >>>>> Groups "golang-nuts" group. >>>>> > To unsubscribe from this group and stop receiving emails from it, >>>>> send an email to [email protected]. >>>>> > To view this discussion on the web visit >>>>> https://groups.google.com/d/msgid/golang-nuts/1826b3b5-c147-4015-9769-984fd84eacb3n%40googlegroups.com. >>>>> >>>>> >>>> -- >>> You received this message because you are subscribed to the Google >>> Groups "golang-nuts" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to [email protected]. >>> >>> To view this discussion visit >>> https://groups.google.com/d/msgid/golang-nuts/86d641cd-4503-4568-b491-f82b5fa705c9n%40googlegroups.com >>> <https://groups.google.com/d/msgid/golang-nuts/86d641cd-4503-4568-b491-f82b5fa705c9n%40googlegroups.com?utm_medium=email&utm_source=footer> >>> . >>> >>> -- > You received this message because you are subscribed to the Google Groups > "golang-nuts" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To view this discussion visit > https://groups.google.com/d/msgid/golang-nuts/0f47147b-5911-4f67-aa45-8eb00e722f5fn%40googlegroups.com > <https://groups.google.com/d/msgid/golang-nuts/0f47147b-5911-4f67-aa45-8eb00e722f5fn%40googlegroups.com?utm_medium=email&utm_source=footer> > . > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/CAA38peYU0zT1sZKvAEM%3DbyhbWbEmqPCFYLByit67rTV3yYtSRA%40mail.gmail.com.
