Created
August 29, 2023 16:03
-
-
Save geofffranks/d534ee30e330847b7d608a81c01ecf1e to your computer and use it in GitHub Desktop.
bind-wait-listen-later.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io" | |
"net" | |
"net/http" | |
"os" | |
"strconv" | |
"strings" | |
"syscall" | |
"time" | |
) | |
func main() { | |
go func() { | |
// run an equivalent command to what we're doing in BBS, on port 8081, for comparison | |
// `ss -tnlp` should show its listening with the same backlog as the socket on :8080 | |
http.ListenAndServe("0.0.0.0:8081", nil) | |
}() | |
// create a socket file descriptor that we can use to bind/listen | |
sockfd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 6) | |
if err != nil { | |
panic(err) | |
} | |
// create the local address definition for our socket | |
sockaddr := &syscall.SockaddrInet4{ | |
Addr: [4]byte(net.IPv4(0, 0, 0, 0)), | |
Port: 8080, | |
} | |
// bind the socket to this process - this will prevent other processes from using it | |
err = syscall.Bind(sockfd, sockaddr) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("Bound to socket: %v\n", sockaddr) | |
// simulate the time taken for BBS to gain the lock and become the primary/active instance | |
// during this period, no other processes can bind to :8080, but additionally nothing can connect | |
// to port 8080 (processes get connection refused errors) | |
time.Sleep(5 * time.Second) | |
// Finally listen on the socket. clients connecting will now get an established TCP connection | |
// this connection will hang until the http Server is serving though | |
err = syscall.Listen(sockfd, maxListenerBacklog()) | |
if err != nil { | |
panic(err) | |
} | |
// create a Listner based off the socket file descriptor | |
listener, err := net.FileListener(os.NewFile(uintptr(sockfd), "server socket")) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Printf("listening on socket: %v\n", sockaddr) | |
// simulate some kind of delay to view that tcp connections are now established but not responded to | |
time.Sleep(5 * time.Second) | |
// finally serve http requests. clients connected to in the previous 5 seconds will finally get responses unless the client had a <5s timeout | |
fmt.Printf("Serving HTTP requests\n") | |
server := &http.Server{} | |
err = server.Serve(listener) | |
if err != nil { | |
panic(err) | |
} | |
} | |
// pulls the somaxconn value configured in the kernel (typically 4096) | |
// if that fails, defaults to golangs syscall.SOMAXCONN (128) | |
// this logic was based off of how golang determines backlog on linux: | |
// https://cs.opensource.google/go/go/+/refs/tags/go1.21.0:src/net/sock_linux.go;l=34 | |
func maxListenerBacklog() int { | |
file, err := os.OpenFile("/proc/sys/net/core/somaxconn", os.O_RDONLY, 0x000) | |
if err != nil { | |
fmt.Printf("Couldn't open somaxconn: %s\n", err) | |
return syscall.SOMAXCONN | |
} | |
defer file.Close() | |
maxconnStr, err := io.ReadAll(file) | |
if err != nil { | |
fmt.Printf("Couldn't read somaxconn: %s\n", err) | |
return syscall.SOMAXCONN | |
} | |
n, err := strconv.Atoi(strings.TrimSpace(string(maxconnStr))) | |
if err != nil { | |
fmt.Printf("couldn't convert somaxconn to int: %s\n", err) | |
return syscall.SOMAXCONN | |
} | |
return n | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment