Skip to content

Conversation

jonatanklosko
Copy link
Member

Fixes error reported in #14793.

Replaces File.rename/2, which is not atomic on Windows (the destination file can be briefly deleted), with a switch-file:

# We need a mechanism to atomically replace file content. Typically,
# we could use File.rename/2 to do that, however File.rename/2 is
# not atomic on Windows, if the destination exists [1].
#
# As an alternative approach we use a switch-file. The file content
# consists of 1 switch byte (either 0 or 1) and two content segments
# with fixed, equal lengths. The switch byte indicates which segment
# is currently active. To replace the file content, we write to the
# non-active segment and call :file.sync/1 to ensure the segment is
# persisted, then we toggle the switch byte. While we cannot write
# multiple bytes atomically (since they may reside in multiple disk
# sectors), if we toggle only a single byte, there is no intermediate
# invalid state, which gives us the atomic replace we need.
#
# Note that file content can be replaced only by a single process
# at a time.
#
# [1]: https://github.com/elixir-lang/elixir/pull/14793#issuecomment-3338665065

It's a bit of extra complexity, but it should effectively be a drop-in replacement for the current File.rename/2 and the overall algorithm doesn't change.


Path.join(
System.tmp_dir!(),
"mix_lock_#{@version}_#{Base.url_encode64(user, padding: false)}"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I bumped a version to make sure we don't try to read old lock files, since the expected format changed.

Comment on lines +366 to +368
if os_pid_size > 32 do
Mix.raise("unexpectedly long PID: #{inspect(os_pid)}")
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I picked an arbitrary limit. Ideally we would store OS PID it as an integer, but my understanding is that it's not guaranteed to be one.

@josevalim josevalim merged commit 898e61a into elixir-lang:main Sep 30, 2025
13 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants