-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
automatic recompilation of stale cache files #12458
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1104,6 +1104,7 @@ export | |
evalfile, | ||
include, | ||
include_string, | ||
include_dependency, | ||
|
||
# RTS internals | ||
finalizer, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,9 +58,10 @@ function _include_from_serialized(content::Vector{UInt8}) | |
end | ||
|
||
# returns an array of modules loaded, or nothing if failed | ||
function _require_from_serialized(node::Int, path_to_try::ByteString, toplevel_load::Bool) | ||
function _require_from_serialized(node::Int, mod::Symbol, path_to_try::ByteString, toplevel_load::Bool) | ||
restored = nothing | ||
if toplevel_load && myid() == 1 && nprocs() > 1 | ||
recompile_stale(mod, path_to_try) | ||
# broadcast top-level import/using from node 1 (only) | ||
if node == myid() | ||
content = open(readbytes, path_to_try) | ||
|
@@ -78,6 +79,7 @@ function _require_from_serialized(node::Int, path_to_try::ByteString, toplevel_l | |
end | ||
end | ||
elseif node == myid() | ||
myid() == 1 && recompile_stale(mod, path_to_try) | ||
restored = ccall(:jl_restore_incremental, Any, (Ptr{Uint8},), path_to_try) | ||
else | ||
content = remotecall_fetch(node, open, readbytes, path_to_try) | ||
|
@@ -97,8 +99,9 @@ end | |
|
||
function _require_from_serialized(node::Int, mod::Symbol, toplevel_load::Bool) | ||
paths = @fetchfrom node find_all_in_cache_path(mod) | ||
sort!(paths, by=mtime, rev=true) # try newest cachefiles first | ||
for path_to_try in paths | ||
restored = _require_from_serialized(node, path_to_try, toplevel_load) | ||
restored = _require_from_serialized(node, mod, path_to_try, toplevel_load) | ||
if restored === nothing | ||
warn("deserialization checks failed while attempting to load cache from $path_to_try") | ||
else | ||
|
@@ -112,9 +115,30 @@ end | |
const package_locks = Dict{Symbol,Condition}() | ||
const package_loaded = Set{Symbol}() | ||
|
||
# used to optionally track dependencies when requiring a module: | ||
const _require_dependencies = ByteString[] | ||
const _track_dependencies = [false] | ||
function _include_dependency(_path::AbstractString) | ||
prev = source_path(nothing) | ||
path = (prev === nothing) ? abspath(_path) : joinpath(dirname(prev),_path) | ||
if _track_dependencies[1] | ||
push!(_require_dependencies, abspath(path)) | ||
end | ||
return path, prev | ||
end | ||
function include_dependency(path::AbstractString) | ||
_include_dependency(path) | ||
return nothing | ||
end | ||
|
||
# require always works in Main scope and loads files from node 1 | ||
toplevel_load = true | ||
function require(mod::Symbol) | ||
# dependency-tracking is only used for one top-level include(path), | ||
# and is not applied recursively to imported modules: | ||
old_track_dependencies = _track_dependencies[1] | ||
_track_dependencies[1] = false | ||
|
||
global toplevel_load | ||
loading = get(package_locks, mod, false) | ||
if loading !== false | ||
|
@@ -133,7 +157,7 @@ function require(mod::Symbol) | |
if JLOptions().incremental != 0 | ||
# spawn off a new incremental compile task from node 1 for recursive `require` calls | ||
cachefile = compile(mod) | ||
if nothing === _require_from_serialized(1, cachefile, last) | ||
if nothing === _require_from_serialized(1, mod, cachefile, last) | ||
warn("require failed to create a precompiled cache file") | ||
end | ||
return | ||
|
@@ -154,6 +178,7 @@ function require(mod::Symbol) | |
toplevel_load = last | ||
loading = pop!(package_locks, mod) | ||
notify(loading, all=true) | ||
_track_dependencies[1] = old_track_dependencies | ||
end | ||
nothing | ||
end | ||
|
@@ -189,9 +214,8 @@ end | |
|
||
macro __FILE__() source_path() end | ||
|
||
function include_from_node1(path::AbstractString) | ||
prev = source_path(nothing) | ||
path = (prev === nothing) ? abspath(path) : joinpath(dirname(prev),path) | ||
function include_from_node1(_path::AbstractString) | ||
path, prev = _include_dependency(_path) | ||
tls = task_local_storage() | ||
tls[:SOURCE_PATH] = path | ||
local result | ||
|
@@ -248,6 +272,7 @@ function create_expr_cache(input::AbstractString, output::AbstractString) | |
task_local_storage()[:SOURCE_PATH] = $(source) | ||
end) | ||
end | ||
serialize(io, :(Base._track_dependencies[1] = true)) | ||
serialize(io, :(Base.include($(abspath(input))))) | ||
if source !== nothing | ||
serialize(io, quote | ||
|
@@ -272,3 +297,73 @@ function compile(name::ByteString) | |
create_expr_cache(path, cachefile) | ||
return cachefile | ||
end | ||
|
||
module_uuid(m::Module) = ccall(:jl_module_uuid, UInt64, (Any,), m) | ||
|
||
isvalid_cache_header(f::IOStream) = 0 != ccall(:jl_deserialize_verify_header, Cint, (Ptr{Void},), f.ios) | ||
|
||
function cache_dependencies(f::IO) | ||
modules = Tuple{Symbol,UInt64}[] | ||
files = ByteString[] | ||
while true | ||
n = ntoh(read(f, Int32)) | ||
n == 0 && break | ||
push!(modules, | ||
(symbol(readbytes(f, n)), # module symbol | ||
ntoh(read(f, UInt64)))) # module UUID (timestamp) | ||
end | ||
read(f, Int64) # total bytes for file dependencies | ||
while true | ||
n = ntoh(read(f, Int32)) | ||
n == 0 && break | ||
push!(files, bytestring(readbytes(f, n))) | ||
end | ||
return modules, files | ||
end | ||
|
||
function cache_dependencies(cachefile::AbstractString) | ||
io = open(cachefile, "r") | ||
try | ||
!isvalid_cache_header(io) && throw(ArgumentError("invalid cache file $cachefile")) | ||
return cache_dependencies(io) | ||
finally | ||
close(io) | ||
end | ||
end | ||
|
||
function stale_cachefile(cachefile::AbstractString, cachefile_mtime::Real=mtime(cachefile)) | ||
io = open(cachefile, "r") | ||
try | ||
if !isvalid_cache_header(io) | ||
return true # invalid cache file | ||
end | ||
modules, files = cache_dependencies(io) | ||
for f in files | ||
if mtime(f) > cachefile_mtime | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe instead of checking whether There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see what you mean. Somehow I took it that we store all those mtimes and if one of them changes we should recompile. We are clearly not doing that, sorry for the noise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With #12559, now we do store all the mtimes. |
||
return true | ||
end | ||
end | ||
# files are not stale, so module list is valid and needs checking | ||
for (M,uuid) in modules | ||
if !isdefined(Main, M) | ||
require(M) # should recursively recompile module M if stale | ||
end | ||
if module_uuid(Main.(M)) != uuid | ||
return true | ||
end | ||
end | ||
return false # fresh cachefile | ||
finally | ||
close(io) | ||
end | ||
end | ||
|
||
function recompile_stale(mod, cachefile) | ||
cachestat = stat(cachefile) | ||
if iswritable(cachestat) && stale_cachefile(cachefile, cachestat.mtime) | ||
if isinteractive() || 0 != ccall(:jl_generating_output, Cint, ()) | ||
info("Recompiling stale cache file $cachefile for module $mod.") | ||
end | ||
create_expr_cache(find_in_path(string(mod)), cachefile) | ||
end | ||
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.
Newly added functions do not appear to get correctly populated by
doc/genstdlib.jl
. I think you need to add an opening signature for this somewhere in the RST docs for it to work?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.
I don't really understand the new doc system for the manual. @one-more-minute, can you clarify what we are supposed to do? I'd really rather just put the documentation inline in
loading.jl
.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.
Putting the docs inline should work fine. As with the rest of the docstrings,
genstdlib.jl
will look for.. function:: include_dependency(...)
and splice the doc string it finds there, so if that doesn't exist already it needs to be added (or there wouldn't be any way to know where the doc string should go).When I have more free time I'm going to put up a bunch of examples of how to work with this, which will hopefully make it clearer.