5

Common method for preventing concurrent runs for a given script is utilizing flock as:

LOCK=/var/lock/my-lock
exec 9>>"$LOCK"

if ! flock --exclusive --nonblock 9; then
    echo "already running"
    exit 1
fi

What if we have two separate scripts (who can run concurrently) that want to use the same locking mechanism. They use different $LOCK file, but same FD 9 is used. Will there be collision between those two scripts' executions/locking?

If there's collision, then what would be a robust solution to this problem?

0

1 Answer 1

6

What if we have two separate scripts (who can run concurrently) that want to use the same locking mechanism. They use different $LOCK file, but same FD 9 is used. Will there be collision between those two scripts' executions/locking?

File descriptors are private, not global – they only have meaning within the single process that creates them (or inherits them from parent, such as 'flock' inheriting the file descriptor '9' from the 'bash' script interpreter process).

There is no automatic relationship between file descriptors having the same number, even if they both independently open the same file – aside from locks, of course, which are still shared through the file and not through the fd number.

Indeed practically every one of your processes is using the same file descriptor numbers 0/1/2/3 concurrently, but they all represent different "file descriptions" which are usually attached to different objects. When your script does echo "already running" >&2 to write the message to stderr, that goes specifically to where its own stderr file descriptor points, while just opening a new terminal window will give you processes with 2 being a completely different file descriptor.


All that aside, I'd still rather avoid hardcoding file descriptor numbers in principle. It's nicer (and closer to how other languages work) to use exec {fd}>>"$LOCK" and flock -xn $fd, letting the shell to just take whatever file descriptor it got from the OS.

what's happening here? Does the shell itself find a free FD and assign $fd var its value?

The program (shell or otherwise) actually doesn't get to choose. The OS always gives you the first free FD (usually that's 3 and upwards). When {fd}> is used the shell just directly assigns $fd whichever value it gets from the OS.

(If you look at lsfd -Q 'FD >= 0' or at lsof or at ls -l /proc/<pid>/fd you'll usually see 3 being the first actual file or socket that the program has open. When bash is running a script, it manually moves the fd representing the script file somewhere to 255 or so.)

Whenever the program wants a specific FD (e.g. when doing 9>> in shell), it has to first take whatever FD it gets, then it needs to call dup2() to "copy" that FD to the desired number, then close() the no-longer-needed original.

Most programs don't have such a specific need, so they just take the OS result and store it in a variable, e.g. int myfile = open("/etc/passwd", O_RDONLY). (This C example would be equivalent to exec {myfile}</etc/passwd in Bash.)

Wouldn't another instance of same script cause another FD to be assigned, effectively breaking the locking?

Another instance of the same script would run in its own process, therefore would have its own memory and its own variables. Even if both are running the same script file, they are still separate processes, so what one instance assigns to its fd variable has no effect on the fd variable of other scripts or instances.

So you would have two independent processes, each opening the file separately and getting a new file descriptor (and a new "file description"). Chances are that both will receive the same file descriptor number (usually 3 or so), but again, that file descriptor only has meaning within the same process.

Additionally (though unrelated), in Bash, the variable only holds the number; resetting it doesn't actually close the file descriptor. (If you wanted to close the file descriptor without waiting for the script to exit, you would have to do that explicitly using e.g. exec {fd}>&- or exec 9>&-.)

given separate instances of same script (i.e. still different processes) will possibly end up using different FDs?

They will always end up using different FDs. The numbers merely represent kernel-side objects (tracked separately for each process), so even if the FD number is the same, the actual FD it represents will be different.

But this is fine for locking; file locks, by design, are visible across all file descriptors associated with the same file. (As that is their main purpose – a process doesn't really need file locks to protect against itself; it can use faster in-process methods for that. The whole point of file-level locking is for it to be visible across processes.)

So when the 2nd script instance opens the lock file and attempts to flock() it, it will see that there already is a lock held by some other file descriptor associated with that file.

7
  • 1
    "they only have meaning within the single process that creates them" - I'm an idiot! Of course, otherwise stdout & stderr (i.e. FD 1 & 2) would be mangled as well! Thanks! Commented yesterday
  • 1
    exec {fd}>>"$LOCK" - what's happening here? Does the shell itself find a free FD and assign $fd var its value? Wouldn't another instance of same script cause another FD to be assigned, effectively breaking the locking? Commented yesterday
  • The program (shell or otherwise) actually doesn't get to choose. The OS always gives you the first free FD (usually that's 3 and upwards). Whenever the program wants a specific FD (e.g. when doing 9>> in shell), it has to first take whatever FD it gets, then it needs to call dup2() to "copy" that FD to the desired number, then close() the no-longer-needed original. Most programs don't have such a specific need, so they just take the OS result and store it in a variable, e.g. int myfile = open("/etc/passwd", O_RDONLY). Commented yesterday
  • (If you look at lsfd -Q 'FD >= 0' or at lsof or at ls -l /proc/<pid>/fd you'll usually see 3 being the first actual file or socket that the program has open. When bash is running a script, it manually moves the fd representing the script file somewhere to 255 or so.) Commented yesterday
  • 1
    They will always end up using different FDs. The number being different is completely irrelevant – it only represents a kernel-side object that the OS keeps track of (separately for each process). But file locks, by design, are "visible" across all file descriptors associated with the same file. (As that is their whole purpose – after all, a process doesn't really need file locks to protect against itself; it can use faster in-process methods for that. The whole point of file-level locking is for it to be visible across processes.) Commented yesterday

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.