Skip to content

Commit 95072fb

Browse files
vtjnashjeffwong
authored andcommitted
RFC: include: assume that shared file systems exist on clusters / remove include_from_node1 (JuliaLang#22588)
* include: assume that shared file systems exist for clusters remove buggy support for emulating shared file-systems from Julia: the kernel is much better at this, and can do it transparently * only broadcast using/import to nodes which need it fix JuliaLang#12381 fix JuliaLang#13999
1 parent b7bedb3 commit 95072fb

File tree

6 files changed

+127
-212
lines changed

6 files changed

+127
-212
lines changed

base/loading.jl

+45-143
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,6 @@ end
120120
find_in_path(name::AbstractString, wd::AbstractString = pwd()) =
121121
find_in_path(String(name), String(wd))
122122

123-
function find_in_node_path(name::String, srcpath, node::Int=1)
124-
if myid() == node
125-
return find_in_path(name, srcpath)
126-
else
127-
return remotecall_fetch(find_in_path, node, name, srcpath)
128-
end
129-
end
130-
131123
function find_source_file(file::String)
132124
(isabspath(file) || isfile(file)) && return file
133125
file2 = find_in_path(file)
@@ -158,51 +150,9 @@ function _include_from_serialized(path::String)
158150
end
159151

160152
# returns an array of modules loaded, or an Exception that describes why it failed
161-
# and also attempts to load the same file across all nodes (if toplevel_node and myid() == master)
162153
# and it reconnects the Base.Docs.META
163-
function _require_from_serialized(node::Int, mod::Symbol, path_to_try::String, toplevel_load::Bool)
164-
local restored = nothing
165-
local content::Vector{UInt8}
166-
if toplevel_load && myid() == 1 && nprocs() > 1
167-
# broadcast top-level import/using from node 1 (only)
168-
if node == myid()
169-
content = open(read, path_to_try)
170-
else
171-
content = remotecall_fetch(open, node, read, path_to_try)
172-
end
173-
restored = _include_from_serialized(content)
174-
isa(restored, Exception) && return restored
175-
176-
results = sizehint!(Vector{Tuple{Int,Any}}(), nprocs())
177-
@sync for p in procs()
178-
if p != myid()
179-
@async begin
180-
result = remotecall_fetch(p) do
181-
let m = try
182-
_include_from_serialized(content)
183-
catch ex
184-
isa(ex, Exception) ? ex : ErrorException(string(ex))
185-
end
186-
isa(m, Exception) ? m : nothing
187-
end
188-
end
189-
push!(results, (p, result))
190-
end
191-
end
192-
end
193-
for (id, m) in results
194-
if m !== nothing
195-
warn("Node state is inconsistent: node $id failed to load cache from $path_to_try. Got:")
196-
warn(m, prefix="WARNING: ")
197-
end
198-
end
199-
elseif node == myid()
200-
restored = _include_from_serialized(path_to_try)
201-
else
202-
content = remotecall_fetch(open, node, read, path_to_try)
203-
restored = _include_from_serialized(content)
204-
end
205-
154+
function _require_from_serialized(mod::Symbol, path_to_try::String)
155+
restored = _include_from_serialized(path_to_try)
206156
if !isa(restored, Exception)
207157
for M in restored::Vector{Any}
208158
if isdefined(M, Base.Docs.META)
@@ -216,24 +166,13 @@ end
216166
# returns `true` if require found a precompile cache for this mod, but couldn't load it
217167
# returns `false` if the module isn't known to be precompilable
218168
# returns the set of modules restored if the cache load succeeded
219-
function _require_search_from_serialized(node::Int, mod::Symbol, sourcepath::String, toplevel_load::Bool)
220-
if node == myid()
221-
paths = find_all_in_cache_path(mod)
222-
else
223-
paths = @fetchfrom node find_all_in_cache_path(mod)
224-
end
225-
169+
function _require_search_from_serialized(mod::Symbol, sourcepath::String)
170+
paths = find_all_in_cache_path(mod)
226171
for path_to_try in paths::Vector{String}
227-
if node == myid()
228-
if stale_cachefile(sourcepath, path_to_try)
229-
continue
230-
end
231-
else
232-
if @fetchfrom node stale_cachefile(sourcepath, path_to_try)
233-
continue
234-
end
172+
if stale_cachefile(sourcepath, path_to_try)
173+
continue
235174
end
236-
restored = _require_from_serialized(node, mod, path_to_try, toplevel_load)
175+
restored = _require_from_serialized(mod, path_to_try)
237176
if isa(restored, Exception)
238177
if isa(restored, ErrorException) && endswith(restored.msg, " uuid did not match cache file.")
239178
# can't use this cache due to a module uuid mismatch,
@@ -270,15 +209,11 @@ const _track_dependencies = Ref(false) # set this to true to track the list of f
270209
function _include_dependency(_path::AbstractString)
271210
prev = source_path(nothing)
272211
if prev === nothing
273-
if myid() == 1
274-
path = abspath(_path)
275-
else
276-
path = joinpath(remotecall_fetch(abspath, 1, "."), _path)
277-
end
212+
path = abspath(_path)
278213
else
279214
path = joinpath(dirname(prev), _path)
280215
end
281-
if myid() == 1 && _track_dependencies[]
216+
if _track_dependencies[]
282217
push!(_require_dependencies, (path, mtime(path)))
283218
end
284219
return path, prev
@@ -334,52 +269,31 @@ order to throw an error if Julia attempts to precompile it.
334269
using `__precompile__()`. Failure to do so can result in a runtime error when loading the module.
335270
"""
336271
function __precompile__(isprecompilable::Bool=true)
337-
if (myid() == 1 &&
338-
JLOptions().use_compilecache != 0 &&
272+
if (JLOptions().use_compilecache != 0 &&
339273
isprecompilable != (0 != ccall(:jl_generating_output, Cint, ())) &&
340-
!(isprecompilable && toplevel_load::Bool))
274+
!(isprecompilable && toplevel_load[]))
341275
throw(PrecompilableError(isprecompilable))
342276
end
343277
end
344278

345-
function require_modname(name::AbstractString)
346-
# This function can be deleted when the deprecation for `require`
347-
# is deleted.
348-
# While we could also strip off the absolute path, the user may be
349-
# deliberately directing to a different file than what got
350-
# cached. So this takes a conservative approach.
351-
if Bool(JLOptions().use_compilecache)
352-
if endswith(name, ".jl")
353-
tmp = name[1:end-3]
354-
for prefix in LOAD_CACHE_PATH
355-
path = joinpath(prefix, tmp*".ji")
356-
if isfile(path)
357-
return tmp
358-
end
359-
end
360-
end
361-
end
362-
return name
363-
end
364-
365279
"""
366280
reload(name::AbstractString)
367281
368282
Force reloading of a package, even if it has been loaded before. This is intended for use
369283
during package development as code is modified.
370284
"""
371285
function reload(name::AbstractString)
372-
if isfile(name) || contains(name,Filesystem.path_separator)
286+
if contains(name, Filesystem.path_separator) || contains(name, ".")
373287
# for reload("path/file.jl") just ask for include instead
374288
error("use `include` instead of `reload` to load source files")
375289
else
376290
# reload("Package") is ok
377-
require(Symbol(require_modname(name)))
291+
require(Symbol(name))
378292
end
379293
end
380294

381295
# require always works in Main scope and loads files from node 1
382-
toplevel_load = true
296+
const toplevel_load = Ref(true)
383297

384298
"""
385299
require(module::Symbol)
@@ -401,8 +315,19 @@ all platforms, including those with case-insensitive filesystems like macOS and
401315
Windows.
402316
"""
403317
function require(mod::Symbol)
404-
_require(mod::Symbol)
405-
# After successfully loading notify downstream consumers
318+
_require(mod)
319+
# After successfully loading, notify downstream consumers
320+
if toplevel_load[] && myid() == 1 && nprocs() > 1
321+
# broadcast top-level import/using from node 1 (only)
322+
@sync for p in procs()
323+
p == 1 && continue
324+
@async remotecall_wait(p) do
325+
if !isbindingresolved(Main, mod) || !isdefined(Main, mod)
326+
_require(mod)
327+
end
328+
end
329+
end
330+
end
406331
for callback in package_callbacks
407332
invokelatest(callback, mod)
408333
end
@@ -415,7 +340,7 @@ function _require(mod::Symbol)
415340
_track_dependencies[] = false
416341
DEBUG_LOADING[] = haskey(ENV, "JULIA_DEBUG_LOADING")
417342

418-
global toplevel_load
343+
# handle recursive calls to require
419344
loading = get(package_locks, mod, false)
420345
if loading !== false
421346
# load already in progress for this module
@@ -424,20 +349,20 @@ function _require(mod::Symbol)
424349
end
425350
package_locks[mod] = Condition()
426351

427-
last = toplevel_load::Bool
352+
last = toplevel_load[]
428353
try
429-
toplevel_load = false
354+
toplevel_load[] = false
430355
# perform the search operation to select the module file require intends to load
431356
name = string(mod)
432-
path = find_in_node_path(name, nothing, 1)
357+
path = find_in_path(name, nothing)
433358
if path === nothing
434359
throw(ArgumentError("Module $name not found in current path.\nRun `Pkg.add(\"$name\")` to install the $name package."))
435360
end
436361

437362
# attempt to load the module file via the precompile cache locations
438363
doneprecompile = false
439364
if JLOptions().use_compilecache != 0
440-
doneprecompile = _require_search_from_serialized(1, mod, path, last)
365+
doneprecompile = _require_search_from_serialized(mod, path)
441366
if !isa(doneprecompile, Bool)
442367
return # success
443368
end
@@ -457,10 +382,10 @@ function _require(mod::Symbol)
457382
end
458383

459384
if doneprecompile === true || JLOptions().incremental != 0
460-
# spawn off a new incremental pre-compile task from node 1 for recursive `require` calls
385+
# spawn off a new incremental pre-compile task for recursive `require` calls
461386
# or if the require search declared it was pre-compiled before (and therefore is expected to still be pre-compilable)
462387
cachefile = compilecache(mod)
463-
m = _require_from_serialized(1, mod, cachefile, last)
388+
m = _require_from_serialized(mod, cachefile)
464389
if isa(m, Exception)
465390
warn("The call to compilecache failed to create a usable precompiled cache file for module $name. Got:")
466391
warn(m, prefix="WARNING: ")
@@ -473,49 +398,35 @@ function _require(mod::Symbol)
473398
# just load the file normally via include
474399
# for unknown dependencies
475400
try
476-
# include on node 1 first to check for PrecompilableErrors
477-
Base.include_from_node1(Main, path)
478-
479-
if last && myid() == 1 && nprocs() > 1
480-
# broadcast top-level import/using from node 1 (only)
481-
@sync begin
482-
for p in filter(x -> x != 1, procs())
483-
@async remotecall_fetch(p) do
484-
Base.include_from_node1(Main, path)
485-
nothing
486-
end
487-
end
488-
end
489-
end
401+
Base.include_relative(Main, path)
490402
catch ex
491403
if doneprecompile === true || JLOptions().use_compilecache == 0 || !precompilableerror(ex, true)
492404
rethrow() # rethrow non-precompilable=true errors
493405
end
494406
# the file requested `__precompile__`, so try to build a cache file and use that
495407
cachefile = compilecache(mod)
496-
m = _require_from_serialized(1, mod, cachefile, last)
408+
m = _require_from_serialized(mod, cachefile)
497409
if isa(m, Exception)
498410
warn(m, prefix="WARNING: ")
499411
# TODO: disable __precompile__(true) error and do normal include instead of error
500412
error("Module $mod declares __precompile__(true) but require failed to create a usable precompiled cache file.")
501413
end
502414
end
503415
finally
504-
toplevel_load = last
416+
toplevel_load[] = last
505417
loading = pop!(package_locks, mod)
506418
notify(loading, all=true)
507419
_track_dependencies[] = old_track_dependencies
508420
end
509421
nothing
510422
end
511423

512-
# remote/parallel load
424+
# relative-path load
513425

514426
"""
515427
include_string(m::Module, code::AbstractString, filename::AbstractString="string")
516428
517-
Like `include`, except reads code from the given string rather than from a file. Since there
518-
is no file path involved, no path processing or fetching from node 1 is done.
429+
Like `include`, except reads code from the given string rather than from a file.
519430
"""
520431
include_string(m::Module, txt::String, fname::String) =
521432
ccall(:jl_load_file_string, Any, (Ptr{UInt8}, Csize_t, Cstring, Any),
@@ -543,29 +454,22 @@ function source_dir()
543454
p === nothing ? pwd() : dirname(p)
544455
end
545456

546-
include_from_node1(mod::Module, path::AbstractString) = include_from_node1(mod, String(path))
547-
function include_from_node1(mod::Module, _path::String)
457+
include_relative(mod::Module, path::AbstractString) = include_relative(mod, String(path))
458+
function include_relative(mod::Module, _path::String)
548459
path, prev = _include_dependency(_path)
549460
tls = task_local_storage()
550461
tls[:SOURCE_PATH] = path
551462
local result
552463
try
553-
if myid()==1
554-
# sleep a bit to process file requests from other nodes
555-
nprocs()>1 && sleep(0.005)
556-
result = Core.include(mod, path)
557-
nprocs()>1 && sleep(0.005)
558-
else
559-
result = include_string(mod, remotecall_fetch(readstring, 1, path), path)
560-
end
464+
result = Core.include(mod, path)
561465
finally
562466
if prev === nothing
563467
delete!(tls, :SOURCE_PATH)
564468
else
565469
tls[:SOURCE_PATH] = prev
566470
end
567471
end
568-
result
472+
return result
569473
end
570474

571475
"""
@@ -574,8 +478,7 @@ end
574478
Evaluate the contents of the input source file into module `m`. Returns the result
575479
of the last evaluated expression of the input file. During including, a task-local include
576480
path is set to the directory containing the file. Nested calls to `include` will search
577-
relative to that path. All paths refer to files on node 1 when running in parallel, and
578-
files will be fetched from node 1. This function is typically used to load source
481+
relative to that path. This function is typically used to load source
579482
interactively, or to combine files in packages that are broken into multiple source files.
580483
"""
581484
include # defined in sysimg.jl
@@ -655,7 +558,6 @@ This can be used to reduce package load times. Cache files are stored in
655558
for important notes.
656559
"""
657560
function compilecache(name::String)
658-
myid() == 1 || error("can only precompile from node 1")
659561
# decide where to get the source file from
660562
path = find_in_path(name, nothing)
661563
path === nothing && throw(ArgumentError("$name not found in path"))
@@ -777,7 +679,7 @@ function stale_cachefile(modpath::String, cachefile::String)
777679
continue
778680
end
779681
name = string(mod)
780-
path = find_in_node_path(name, nothing, 1)
682+
path = find_in_path(name, nothing)
781683
if path === nothing
782684
return true # Won't be able to fullfill dependency
783685
end

0 commit comments

Comments
 (0)