Skip to content

Commit 9ef24c2

Browse files
kpamnanyTakafumi Arakaki
authored andcommitted
Make compilecache atomic (#36416)
When several Julia processes compile the same package concurrently (e.g. during a cluster run), they can conflict on the compile cache file. This change makes a Julia process create a compile cache in a temporary file and atomically rename it to the final cache file. Co-authored-by: Takafumi Arakaki <tkf@@users.noreply.github.com> (cherry picked from commit 3bbb582)
1 parent f557436 commit 9ef24c2

File tree

3 files changed

+42
-18
lines changed

3 files changed

+42
-18
lines changed

base/loading.jl

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1256,9 +1256,9 @@ const MAX_NUM_PRECOMPILE_FILES = 10
12561256
function compilecache(pkg::PkgId, path::String)
12571257
# decide where to put the resulting cache file
12581258
cachefile = compilecache_path(pkg)
1259+
cachepath = dirname(cachefile)
12591260
# prune the directory with cache files
12601261
if pkg.uuid !== nothing
1261-
cachepath = dirname(cachefile)
12621262
entrypath, entryfile = cache_file_entry(pkg)
12631263
cachefiles = filter!(x -> startswith(x, entryfile * "_"), readdir(cachepath))
12641264
if length(cachefiles) >= MAX_NUM_PRECOMPILE_FILES
@@ -1276,20 +1276,34 @@ function compilecache(pkg::PkgId, path::String)
12761276
# run the expression and cache the result
12771277
verbosity = isinteractive() ? CoreLogging.Info : CoreLogging.Debug
12781278
@logmsg verbosity "Precompiling $pkg"
1279-
p = create_expr_cache(path, cachefile, concrete_deps, pkg.uuid)
1280-
if success(p)
1281-
# append checksum to the end of the .ji file:
1282-
open(cachefile, "a+") do f
1283-
write(f, _crc32c(seekstart(f)))
1279+
1280+
# create a temporary file in `cachepath` directory, write the cache in it,
1281+
# write the checksum, _and then_ atomically move the file to `cachefile`.
1282+
tmppath, tmpio = mktemp(cachepath)
1283+
local p
1284+
try
1285+
close(tmpio)
1286+
p = create_expr_cache(path, tmppath, concrete_deps, pkg.uuid)
1287+
if success(p)
1288+
# append checksum to the end of the .ji file:
1289+
open(tmppath, "a+") do f
1290+
write(f, _crc32c(seekstart(f)))
1291+
end
1292+
# inherit permission from the source file
1293+
chmod(tmppath, filemode(path) & 0o777)
1294+
1295+
# this is atomic according to POSIX:
1296+
rename(tmppath, cachefile)
1297+
return cachefile
12841298
end
1285-
# inherit permission from the source file
1286-
chmod(cachefile, filemode(path) & 0o777)
1287-
elseif p.exitcode == 125
1299+
finally
1300+
rm(tmppath, force=true)
1301+
end
1302+
if p.exitcode == 125
12881303
return PrecompilableError()
12891304
else
12901305
error("Failed to precompile $pkg to $cachefile.")
12911306
end
1292-
return cachefile
12931307
end
12941308

12951309
module_build_id(m::Module) = ccall(:jl_module_build_id, UInt64, (Any,), m)

doc/src/manual/faq.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,22 @@ Julia compiles and uses its own copy of OpenBLAS, with threads currently capped
935935

936936
Modifying OpenBLAS settings or compiling Julia with a different BLAS library, eg [Intel MKL](https://software.intel.com/en-us/mkl), may provide performance improvements. You can use [MKL.jl](https://github.com/JuliaComputing/MKL.jl), a package that makes Julia's linear algebra use Intel MKL BLAS and LAPACK instead of OpenBLAS, or search the discussion forum for suggestions on how to set this up manually. Note that Intel MKL cannot be bundled with Julia, as it is not open source.
937937

938+
## Computing cluster
939+
940+
### How do I manage precompilation caches in distributed file systems?
941+
942+
When using `julia` in high-performance computing (HPC) facilities, invoking
943+
_n_ `julia` processes simultaneously creates at most _n_ temporary copies of
944+
precompilation cache files. If this is an issue (slow and/or small distributed
945+
file system), you may:
946+
947+
1. Use `julia` with `--compiled-modules=no` flag to turn off precompilation.
948+
2. Configure a private writable depot using `pushfirst!(DEPOT_PATH, private_path)`
949+
where `private_path` is a path unique to this `julia` process. This
950+
can also be done by setting environment variable `JULIA_DEPOT_PATH` to
951+
`$private_path:$HOME/.julia`.
952+
3. Create a symlink from `~/.julia/compiled` to a directory in a scratch space.
953+
938954
## Julia Releases
939955

940956
### Do I want to use the Stable, LTS, or nightly version of Julia?

src/dump.c

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2837,11 +2837,10 @@ JL_DLLEXPORT jl_value_t *jl_uncompress_argname_n(jl_value_t *syms, size_t i)
28372837
JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
28382838
{
28392839
JL_TIMING(SAVE_MODULE);
2840-
char *tmpfname = strcat(strcpy((char *) alloca(strlen(fname)+8), fname), ".XXXXXX");
28412840
ios_t f;
28422841
jl_array_t *mod_array = NULL, *udeps = NULL;
2843-
if (ios_mkstemp(&f, tmpfname) == NULL) {
2844-
jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", tmpfname);
2842+
if (ios_file(&f, fname, 1, 1, 1, 1) == NULL) {
2843+
jl_printf(JL_STDERR, "Cannot open cache file \"%s\" for writing.\n", fname);
28452844
return 1;
28462845
}
28472846
JL_GC_PUSH2(&mod_array, &udeps);
@@ -2955,12 +2954,7 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
29552954
}
29562955
write_int32(&f, 0); // mark the end of the source text
29572956
ios_close(&f);
2958-
29592957
JL_GC_POP();
2960-
if (jl_fs_rename(tmpfname, fname) < 0) {
2961-
jl_printf(JL_STDERR, "Cannot write cache file \"%s\".\n", fname);
2962-
return 1;
2963-
}
29642958

29652959
return 0;
29662960
}

0 commit comments

Comments
 (0)