Remix.run Logo
zzo38computer 15 hours ago

Even though it can do some things atomically, it only does with one file at a time, and race conditions are still possible because it only does one operation at a time (even if you are only need one file). Some of these are helpful anyways, such as O_EXCL, but it is still only one thing at a time which can cause problems in some cases.

What else it does not do is a transaction with multiple objects. That is why, I would design a operating system, that you can do a transaction with multiple objects.

ptx 15 hours ago | parent | next [-]

Windows had APIs for this sort of thing added in Vista, but they're now deprecating it "due to its complexity and various nuances which developers need to consider":

https://learn.microsoft.com/en-us/windows/win32/fileio/about...

Orphis 9 hours ago | parent | prev | next [-]

In some cases, you can start by using the "at" functions (openat...) to work on a directory tree. If you have your logical "locking" done at the top-level of the tree, it might be a fine option.

In some other cases, I've used a pattern where I used a symlink to folders. The symlink is created, resolved or updated atomically, and all I need is eventual consistency.

That last case was to manage several APT repository indices. The indices were constantly updated to publish new testing or unstable releases of software and machines in the fleet were regularly fetching the repository index. The APT protocol and structure being a bit "dumb" (for better or worse) requires you to fetch files (many of them) in the reverse order they are created, which leads to obvious issues like the signature is updated only after the list of files is updated, or the list of files is created only after the list of packages is created.

Long story short, each update would create a new folder that's consistent, and a symlink points to the last created folder (to atomically replace the folder as it was not possible to swap them), and a small HTTP server would initiate a server side session when the first file is fetched and only return files from the same index list, and everything is eventually consistent, and we never get APT complaining about having signature or hash mismatches. The pivotal component was indeed the atomicity of having a symlink to deal with it, as the Java implementation didn't have access to a more modern "openat" syscall, relative to a specific folder.

akoboldfrying 13 hours ago | parent | prev [-]

I don't follow, sorry. Are you saying that if we run:

    mv a b
    mv c d
We could observe a state where a and d exist? I would find such "out of order execution" shocking.

If that's not what you're saying, could you give an example of something you want to be able to do but can't?

zbentley 7 hours ago | parent | next [-]

Depending on metadata cache behavior configuration, if the system is powered off immediately after the first command, then that could indeed happen I think.

As to whether it’s technically possible for it to happen on a system that stays on, I’m not sure, but it’s certainly vanishingly rare and likely requires very specific circumstances—not just a random race condition.

LgWoodenBadger 5 hours ago | parent [-]

Uhh, if the system powers off immediately after the first command (mv a b), the second command (mv c d) would never run. So where would d come from if the command that created it never executed?

zbentley 4 hours ago | parent [-]

Er, sorry: I meant: if the first command runs, the plug is pulled, system starts again, second command runs.

lpribis 39 minutes ago | parent [-]

Sure, but splitting "atomic" operations across a reboot is an interesting design choice. Surely upon reboot you would re-try the first `mv a b` before doing other things.

jstimpfle 13 hours ago | parent | prev | next [-]

I don't think that's happening in practice, but 1) it may not be specified and 2) What you say could well be the persisted state after a machine crash or power loss. In particular if those files live in different directories.

You can remedy 2) by doing fsync() on the parent directory in between. I just asked ChatGPT which directory you need to fsync. It says it's both, the source and the target directory. Which "makes sense" and simplifies implementations, but it means the rename operation is atomic only at runtime, not if there's a crash in between. It think you might end up with 0 or 2 entries after a crash if you're unlucky.

If that's true, then for safety maybe one should never rename across directories, but instead do a coordinated link(source, target), fsync(target_dir), unlink(source), fsync(source_dir)

jstimpfle 8 hours ago | parent [-]

why is this being downvoted? If there's something wrong, explain?

duped 9 hours ago | parent | prev | next [-]

All you need for this to occur is the window where both renames occurs overlap. A system polling to check if a, b, c, and d exist while the renames are happening might find all four of them.

jstimpfle 8 hours ago | parent [-]

Assuming that the two `mv` commands are run in sequence, there shouldn't be any possibility for a and d to be observed "at once" (i.e. first d and then afterwards still a, by a single process).

devnonymous 12 hours ago | parent | prev [-]

I'm almost certain what the OP meant was if the commands were run synchronously (ie: from 2 different shells or as `mv a b &; mv c d`) yes there is a possibility that a and d exist (eg: On a busy system where neither of the 2 commands can be immediately scheduled and eventually the second one ends up being scheduled before the first)

Or to go a level deeper, if you have 2 occurrences of rename(2) from the stdlibc ...

rename('a', 'b'); rename('c', 'd');

...and the compiler decides on out of order execution or optimizing by scheduling on different cpus, you can get a and d existing at the same time.

The reason it won't happen in the example you posted is the shell ensures the atomicity (by not forking the second mv until the wait() on the first returns)

isodude 11 hours ago | parent [-]

nitpick, it should be `touch a c & mv a b & mv c d` as `&;` returns `bash: syntax error near unexpected token `;'`. I always find this oddly weird, but that would not be the first pattern in BASH that is.

`inotifywait` actually sees them in order, but nothing ensure that it's that way.

  $ inotifywait -m /tmp
  /tmp/ MOVED_FROM a
  /tmp/ MOVED_TO b
  /tmp/ MOVED_FROM c
  /tmp/ MOVED_TO d

`stat` tells us that the timestamps are equal as well.

  $ stat b d | grep '^Change'
  Change: 2026-02-06 12:22:55.394932841 +0100
  Change: 2026-02-06 12:22:55.394932841 +0100

However, speeding things up changes it a bit.

Given

  $ (
    set -eo pipefail
    for i in {1..10000}
    do
      printf '%d ' "$i"
      touch a c
      mv a b &
      mv c d &
      wait
      rm b d
    done
  )
  1 2 3 4 5 6 .....
And with `inotifywait` I saw this when running it for a while.

  $ inotifywait -m -e MOVED_FROM,MOVED_TO /tmp > /tmp/output
  cat /tmp/output | xargs -l4 | sort | uniq -c
  9104 /tmp/ MOVED_FROM a /tmp/ MOVED_TO b /tmp/ MOVED_FROM c /tmp/ MOVED_TO d
  896 /tmp/ MOVED_FROM c /tmp/ MOVED_TO d /tmp/ MOVED_FROM a /tmp/ MOVED_TO b