How do you access static variables in a thread-safe manner?


#1

I have explored using a static increment counter in a blog post and this is the best I could come up with:

pub fn get_free_port() -> u16 {
    static PORT_NR: AtomicUsize = ATOMIC_USIZE_INIT;

    PORT_NR.compare_and_swap(0, 9090, Ordering::SeqCst);
    PORT_NR.fetch_add(1, Ordering::SeqCst) as u16
}

Is there a better/simpler/easier way to synchronize access to a static variable? This seems overly complicated for a function that just produces incremented integers.


#2
PORT_NR.fetch_add(1, Ordering::SeqCst) as u16 + 9090

The simpler option is to bind to port 0 and have the kernel choose an available one for you.


#3

Using the offset of 9090 at the end is a great idea, thanks!

That way we can remove the initialization condition and always operate on a fixed offset of 9090. This is still not super intuitive because the initial value of our counter is at the very end which makes this function hard to read.

Using port 0: since these are all integration tests we need to get the actual port number that the kernel assigned - so I would have to implement a helper function that reads out the port number of my dummy server and of my reverse proxy. Does not seem simpler to me. That is also not how the proxy is used by users: port numbers are pre-determined and passed as configuration, I think the integration test should operate the same way.


#4

I mean, you can stick the 9090 before the + if you want.

https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.local_addr will give you the port.


#5

I do something a little different when I’m making sure I’m testing the actual specification of a port:

https://github.com/bluejekyll/trust-dns/blob/master/server/tests/common/server_harness.rs#L24-L35

that grabs a free port, then immediately drops the sockets and starts a new server. There is definitely a race condition here, but I have yet to ever see it get hit…