This fixes a bug where forking and running a command from a Rust program could cause single-instance to mistakenly think a copy of the app is running when it isn't, when running on Linux. We can recreate this problem with a very simple example:
At this point, if you CTRL-C, then everything works exactly as you expect it would - you can restart single-instance-problem and it will print out "Forking..." again. But, if we run single-instance-problem, then open a second terminal and run:
Yup! It's the sleep 6000 we spawned in our Rust app.
To understand what's going on here (you probably know a lot of this already, but just in case...) when our app does Command::new(...).spawn(), what's actually happening in Linux is that we're doing a fork and exec. When we fork in Linux, as the man page says:
The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent.
This means the sleep 6000 gets a copy of the open file descriptor for our abstract socket, which we probably don't want. The fix here is to set SOCK_CLOEXEC on the abstract socket. To quote from the socket man page:
SOCK_CLOEXEC - Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.
What this means is that when we run our child command, we'll still fork and get a copy of the file descriptor, but when the child execs the "sleep 6000" this file descriptor will automatically be closed. The parent process will still have it open, so we'll still get our "single instance" behavior, but if someone kill -9s our single instance, we can recreate it immediately instead of having to wait for all the execed child processes to die.
This fixes a bug where forking and running a command from a Rust program could cause single-instance to mistakenly think a copy of the app is running when it isn't, when running on Linux. We can recreate this problem with a very simple example:
And then in our
main.rs
:Let's build and run this:
At this point, if you
CTRL-C
, then everything works exactly as you expect it would - you can restartsingle-instance-problem
and it will print out "Forking..." again. But, if we runsingle-instance-problem
, then open a second terminal and run:Then we go back to our first window:
Ohs noes! We can use
lsof
to see who has the abstract socket still open:Yup! It's the
sleep 6000
we spawned in our Rust app.To understand what's going on here (you probably know a lot of this already, but just in case...) when our app does
Command::new(...).spawn()
, what's actually happening in Linux is that we're doing a fork and exec. When we fork in Linux, as the man page says:This means the
sleep 6000
gets a copy of the open file descriptor for our abstract socket, which we probably don't want. The fix here is to set SOCK_CLOEXEC on the abstract socket. To quote from the socket man page:What this means is that when we run our child command, we'll still
fork
and get a copy of the file descriptor, but when the childexec
s the "sleep 6000" this file descriptor will automatically be closed. The parent process will still have it open, so we'll still get our "single instance" behavior, but if someonekill -9
s our single instance, we can recreate it immediately instead of having to wait for all the execed child processes to die.