-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Change delayed delete mechanism to prevent cross-drive mv failure for in-use DLL #59635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
The CI failures come from the loading.jl test that attempt to remove the depot at the end of the test. This fails, because the DLL is still present (albeit renamed) within... So I'm reverting the design of this PR to something closer to the current state. In the current version of the PR:
|
base/file.jl
Outdated
drive = first(splitdrive(path)) | ||
tmpdrive = first(splitdrive(tempdir())) | ||
# in-use DLL must be kept on the same drive | ||
deletedir = if drive == tmpdrive | ||
joinpath(tempdir(), "julia_delayed_deletes") | ||
else | ||
joinpath(drive, "julia_delayed_deletes") | ||
end | ||
mkpath(deletedir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is generally a very unreliable way to go about finding a safe folder to move a file to. I suggest just keeping it in the same folder
drive = first(splitdrive(path)) | |
tmpdrive = first(splitdrive(tempdir())) | |
# in-use DLL must be kept on the same drive | |
deletedir = if drive == tmpdrive | |
joinpath(tempdir(), "julia_delayed_deletes") | |
else | |
joinpath(drive, "julia_delayed_deletes") | |
end | |
mkpath(deletedir) | |
path = abspath(path) | |
deletedir = dirname(path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n.b. that when Pkg.gc goes to clean up these "empty" folders it should remember to consider applying any remappings and try both the old and new (renamed) paths
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Actually that's what I did at first, but it messes with the current state of the tests in loading.jl, which call rm(depot)
during the same process that creates the in-use DLL, and thus fail. See CI for the previous commit: https://buildkite.com/julialang/julia-master/builds/50766/steps/canvas?sid=019976ae-d4af-457c-b0d1-9cc06fba875d
I agree that the solution here is not quite ideal... but I don't know how to find a better "safe" folder on a given drive. The joinpath(tempdir(), "julia_delayed_deletes")
one is the current solution on master, but there is no equivalent on any drive.
Thanks for working on this! |
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Thanks again for the reviews! In the current solution, I partially reverted the last changes so that the renamed obsolete DLLs remain in their folder. This required modifying the |
6e8770a
to
2e10a27
Compare
end | ||
""" | ||
cmd = Cmd(`$(Base.julia_cmd()) --startup-file=no -e $rmcmd`; ignorestatus=true, detach=true) | ||
run(cmd; wait=false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run(cmd; wait=false) | |
rd, wr = Base.link_pipe(true, true) | |
run(cmd, rd, devnull, stderr; wait=false) | |
Base.close_pipe_sync(rd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run(cmd; wait=false) | |
p = Base.PipeEndpoint() | |
run(cmd, p, devnull, stderr; wait=false) | |
join(p, DEPOTS_TOREMOVE, "\n") | |
Base.dup(Base.rawhandle(p)) # intentionally leak a reference, until the process exits | |
close(p) |
for _ in 1:3600 # wait up to 1h from atexit() until the julia testing process dies | ||
sleep(1) | ||
ccall(:uv_kill, Cint, (Cuint, Cint), $(getpid()), 0) == Base.UV_ESRCH && break | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for _ in 1:3600 # wait up to 1h from atexit() until the julia testing process dies | |
sleep(1) | |
ccall(:uv_kill, Cint, (Cuint, Cint), $(getpid()), 0) == Base.UV_ESRCH && break | |
end | |
eof(stdin) |
drive = first(splitdrive(path)) | ||
tmpdrive = first(splitdrive(tempdir())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unused variable cleanup
drive = first(splitdrive(path)) | |
tmpdrive = first(splitdrive(tempdir())) |
sleep(1) | ||
ccall(:uv_kill, Cint, (Cuint, Cint), $(getpid()), 0) == Base.UV_ESRCH && break | ||
end | ||
for path in $DEPOTS_TOREMOVE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Windows command line is very limited in how much text it can reliably transfer. Sometimes as low as 2k characters. This list should be passed via stdin to avoid the coammnd getting truncated
The special julia process to be spawned is very clever. Nice job. We might even want to move this code into |
On Windows, during precompilation of package
A
, a DLL file is generated and may replace the existing one. IfA
is already loaded in the julia session however, the corresponding soon-to-be-obsolete DLL cannot be removed while julia is running. Currently, this problem is solved by moving the old DLL in a special "julia_delayed_deletes" folder, which will be cleaned in a later julia session byPkg.gc()
.However, moving an in-use DLL is only permitted on the same drive, and the "julia_delayed_deletes" is located in the
tempdir
, a.k.a. on a fixed drive, sayC:
. Thus, if theDEPOT_PATH
points to a ".julia" in another drive, sayD:
, any precompilation occuring on an already loaded package will fail. This is what happens in #59589, actually resulting in an infinite loop that bloats up both memory and disk. @staticfloat had actually predicted that such an issue could occur in #53456 (comment).This PR fixes #59589 by changing the "delayed deleting" mechanism: instead of moving the old DLLs to a fixed folder, they are renamed in their initial folder and recorded in a list stored at a fixed location. Upon
Pkg.gc()
, the listed obsolete files will be deleted (JuliaLang/Pkg.jl#4392).It also prevents any similar infinite loops by cutting the
rm -> mv -> rm
cycle intorm -> rename
.I also removed the private argumentallow_delayed_delete
fromrm
, which is only called in by Pkg but does not appear to be useful.EDIT: current state is #59635 (comment)