"running in world age X, while current world is Y" errors

Sorry if I’m missing something obvious but I don’t quite understand why I suddenly get all these errors in v0.6 in all cases where I was successfully dynamically invoking functions before.

I did some searching and I discovered that invokelatest is the way to fix it – and indeed, it works (though I understand it might be slower).

For example, in one of the errors, which is

genie> Migration.all_up()

2017-09-13T18:21:03.276 - error: Failed executing migration CreateTableTweets up
2017-09-13T18:21:03.407 - error: MethodError(CreateTableTweets.up, (), 0x0000000000005c6c)
2017-09-13T18:21:03.407 - error: /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:246

ERROR: MethodError: no method matching up()
The applicable method may be too new: running in world age 23660, while current world is 23665.
Closest candidates are:
  up() at /Users/adrian/Dropbox/Projects/tweet_stats/db/migrations/20170630152325497_create_table_tweets.jl:6 (method too new to be called from this world context.)
Stacktrace:
 [1] #run_migration#11(::Bool, ::Function, ::Migration.DatabaseMigration, ::Symbol) at /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:238
 [2] (::Migration.#kw##run_migration)(::Array{Any,1}, ::Migration.#run_migration, ::Migration.DatabaseMigration, ::Symbol) at ./<missing>:0
 [3] #up#5(::Bool, ::Function, ::String) at /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:144
 [4] (::Migration.#kw##up)(::Array{Any,1}, ::Migration.#up, ::String) at ./<missing>:0
 [5] #up_by_module_name#6 at /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:150 [inlined]
 [6] up_by_module_name(::String) at /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:150
 [7] all_up() at /Users/adrian/.julia/v0.6/SearchLight/src/Migration.jl:382

I’m simply referencing and invoking a function in a module per the recommended way to do it in v0.5 (with getfield).

m = include(abspath(joinpath(config.db_migrations_folder, migration.migration_file_name))) # this is a module
getfield(m, direction)() # referencing the function and invoking it

I’d like to understand why this happens.

3 Likes

Here’s my understanding:

In version 0.6, Julia now keeps track of all the callers of a given function (called backedges) so it can go back and invalidate the previous code with any updated methods. This invalidation can only happen at “safe” points within the process — you cannot trash a piece of currently running code and expect everything to go well. You cannot even go back and update a function pointer since the old method be inlined or the new one might change the inferred optimizations. So the old code isn’t trashed completely; a new version is created alongside it. Of course, this means that Julia needs to manage all these different versions of the same code and which should be called at any given point. This is the world age. Any running code is frozen in its world age — even though the Julia process might have gone on to define more methods later.

A simple example with tasks:

julia> f() = 1
       t = @task while true
           try
               @show f(1)
           catch ex
               @show ex
               Base.showerror(stdout, ex) # showerror is what the REPL uses to display errors
           end
           wait()
       end
Task (runnable) @0x000000011303a1d0

julia> schedule(t); sleep(1); # Compiles and runs the task, freezing its world age
ex = MethodError(f, (1,), 0x0000000000005535)
MethodError: no method matching f(::Int64)
Closest candidates are:
  f() at REPL[1]:1
julia> f(::Int) = 2
f (generic function with 2 methods)

julia> schedule(t); sleep(1);
ex = MethodError(f, (1,), 0x0000000000005535)
MethodError: no method matching f(::Int64)
The applicable method may be too new: running in world age 21813, while current world is 21814.
Closest candidates are:
  f(::Int64) at REPL[3]:1 (method too new to be called from this world context.)
  f() at REPL[1]:1

That third argument in the MethodError? That’s the world age. The REPL error display knows about world ages and explicitly knows it should look “into the future” and see if any methods have been defined in a newer age. That’s how it’s able to helpfully tell you that a newer method is defined even though your existing code refuses to execute it. I always hate it when a computer knows what you’re trying to do but refuses to do it, but in this case it’s the other way around: it refused to do some action first and then looked to see if that might be the problem. And in fact, this refusal is one of the things that allows Julia to make some of its best optimizations.

13 Likes

Thanks for the detailed reply, very kind and very useful.

1/ I don’t define different methods for CreateTableTweets.up() - though it would appear that the method is loaded both in the REPL and a HttpServer task (through some deep nesting of modules that are hard to trace by a mere human like myself; but I will get to the bottom of it). I presume that’s why it happens. Couldn’t/shouldn’t Julia figure out that the method is the same?

2/ is this a Bad Thing ™? Is invokelatest considerably slower, can it cause problems in the long run, memory leaks, etc?

3/ are there any tools to help developers understand how and where such problem occurs? Is this knowledge that Julia has, exposed in any way (other than the error itself, which uncomfortably reminds me of “Inception”)? Will the concept of worlds make it into Gallium?

Thanks again!

2 Likes
  1. No, simply defining the same function twice wouldn’t cause this — you can try it by modifying the task example I have above. In your case, the problem is likely the same as the simpler example in the documentation: Redefining Methods. You’re evaluating code and defining new methods and then trying to call those new methods, but the code that is doing that is still frozen in the old world from when it started running.

  2. Yes, invokelatest is definitely slower. It has to emit very pessimized code since it cannot assume anything about the function it calls (it can change at any point). This means no static dispatch, no inlining, and no type inference for downstream optimizations. So use it only when you need to. That said, it’s totally reasonable to use when necessary — it’s precisely how the REPL works.

In general, I find it most instructive to think about this in terms of the valid optimizations inference can make. When you run a new function, inference goes through and looks at all the functions you call as it’s compiled. If everything prior to a given function call is well-typed, it can figure out the dispatch at compile time and avoid that overhead at run-time. Further, it can look into the function definition itself and see if it is small enough to be inlined. It can then recurse and analyze the called function to figure out what the return type will be. This allows the optimizations to continue to the next function you call.

But this all is only valid given the information the system has when it compiles your function. In your case, inference has no idea what this up() function might do or return since it isn’t defined at all yet. invokelatest is your way of telling it that it’s ok and expected, so it should just skip all those optimizations.

2 Likes

Thank you very much, makes total sense.

In the case of up() the performance penalty is not a problem since it’s used for migrations, a one time, manually triggered operation.

However, in other cases, the performance is critical, especially when routing web requests and invoking the corresponding methods. So I looked into these parts of the code and the pattern is that I dynamically include the files. For example, once I determine that a certain controller (which is a module) and action (which is a method in the said module) needs to be invoked, I dynamically load it. Something in the lines of:

"""
    load_controller(dir::String) :: Void

Loads (includes) the `controller` file that corresponds to the currently matched route.
The modules are included in the `App` module.
"""
function load_controller(dir::String) :: Void
  push!(LOAD_PATH, dir)
  file_path = joinpath(dir, Genie.GENIE_CONTROLLER_FILE_NAME)
  isfile(file_path) ? eval(App, :(include($file_path))) : Logger.log("Failed loading controller $dir", :err)

  nothing
end

A while ago I thought this could be an optimization for big apps, to avoid loading controllers that aren’t used.

I have a few more questions please:
1/ per the above example, as a general approach to best performance, it’s probably better to just include all the controllers statically from the beginning? (by adding each corresponding folder to the LOAD_PATH and letting Julia sort it out).
2/ would adding type information to the dynamically loaded methods help Julia? Say if instead of the current
function index() it would be function index() :: HttpServer.Response
3/ is there any penalty in using invokelatest even if it’s not needed? Ex:

julia> funcs = [()->1, ()->2, ()->3]
3-element Array{Function,1}:
 #3
 #4
 #5

julia> funcs[2]() # this works here, but it might crash in different circumstances 
2

julia> Base.invokelatest(funcs[2]) # any penalty in using this instead of the above, all the time? 
2

Thank you!

Replying to myself to #3:

Yes, there seems to be:

julia> @time funcs[2]()
  0.000003 seconds (4 allocations: 160 bytes)
2

julia> @time Base.invokelatest(funcs[2])
  0.000007 seconds (4 allocations: 160 bytes)
2

julia> @time funcs[2]()
  0.000003 seconds (4 allocations: 160 bytes)
2

julia> @time Base.invokelatest(funcs[2])
  0.000007 seconds (4 allocations: 160 bytes)
2

julia> @time funcs[2]()
  0.000003 seconds (4 allocations: 160 bytes)
2

julia> @time Base.invokelatest(funcs[2])
  0.000008 seconds (4 allocations: 160 bytes)
2

Replying myself to #2

No, it does not seem to help.

MethodError(App.TweetsController.index, (), 0x0000000000005c6c)
ERROR: MethodError: no method matching index()
The applicable method may be too new: running in world age 23660, while current world is 23678.
Closest candidates are:
  index() at /Users/adrian/Dropbox/Projects/tweet_stats/app/resources/tweets/controller.jl:7 (method too new to be called from this world context.)
function index() :: HttpServer.Response
  ...
end

Looks like I need to start refactoring the way I load routes and controllers so they’re known to Julia at compile time.

@mbauman Thank’s so much for your help!