Skip to content

An async task that picks up a patch for a mocked method can MethodError on world age issue #108

@NHDaly

Description

@NHDaly

This is very similar to #89, but a slight variant:

If an async task was scheduled before a patch was created, and it picks up the patch during a patch() do block, then it will throw a MethodError because the patch is too new:

julia> using Mocking

julia> function foo(x)
           @mock bar(x)
       end
foo (generic function with 1 method)

julia> bar(x) = x
bar (generic function with 1 method)

julia> Mocking.enable()

julia> ch = Channel() do ch
           put!(ch, foo(take!(ch)))
       end
Channel{Any}(0) (empty)

julia> p = @patch bar(x) = 10*x
Patch{typeof(bar)}(bar, var"##bar_patch#314")

julia> apply(p) do
           put!(ch, 2)
           @info take!(ch)
       end
┌ Error: Exception while generating log record in module Main at REPL[7]:3
│   exception =
│    TaskFailedException
│    Stacktrace:
│      [1] try_yieldto(undo::typeof(Base.ensure_rescheduled))
│        @ Base ./task.jl:871
│      [2] wait()
│        @ Base ./task.jl:931
│      [3] wait(c::Base.GenericCondition{ReentrantLock})
│        @ Base ./condition.jl:124
│      [4] take_unbuffered(c::Channel{Any})
│        @ Base ./channels.jl:433
│      [5] take!(c::Channel{Any})
│        @ Base ./channels.jl:410
│      [6] macro expansion
│        @ ./logging.jl:360 [inlined]
│      [7] (::var"#3#4")()
│        @ Main ./REPL[7]:3
│      [8] apply(body::var"#3#4", pe::Mocking.PatchEnv)
│        @ Mocking ~/.julia/packages/Mocking/MRkF3/src/patch.jl:132
│      [9] apply(body::Function, patches::Patch{typeof(bar)}; debug::Bool)
│        @ Mocking ~/.julia/packages/Mocking/MRkF3/src/patch.jl:139
│     [10] apply(body::Function, patches::Patch{typeof(bar)})
│        @ Mocking ~/.julia/packages/Mocking/MRkF3/src/patch.jl:138
│     [11] top-level scope
│        @ REPL[7]:1
│     [12] eval
│        @ ./boot.jl:368 [inlined]
│     [13] eval_user_input(ast::Any, backend::REPL.REPLBackend)
│        @ REPL ~/builds/julia-1.8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:151
│     [14] repl_backend_loop(backend::REPL.REPLBackend)
│        @ REPL ~/builds/julia-1.8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:247
│     [15] start_repl_backend(backend::REPL.REPLBackend, consumer::Any)
│        @ REPL ~/builds/julia-1.8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:232
│     [16] run_repl(repl::REPL.AbstractREPL, consumer::Any; backend_on_current_task::Bool)
│        @ REPL ~/builds/julia-1.8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:369
│     [17] run_repl(repl::REPL.AbstractREPL, consumer::Any)
│        @ REPL ~/builds/julia-1.8/usr/share/julia/stdlib/v1.8/REPL/src/REPL.jl:355
│     [18] (::Base.var"#967#969"{Bool, Bool, Bool})(REPL::Module)
│        @ Base ./client.jl:419
│     [19] #invokelatest#2
│        @ ./essentials.jl:729 [inlined]
│     [20] invokelatest
│        @ ./essentials.jl:726 [inlined]
│     [21] run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
│        @ Base ./client.jl:404
│     [22] exec_options(opts::Base.JLOptions)
│        @ Base ./client.jl:318
│     [23] _start()
│        @ Base ./client.jl:522
│
│        nested task error: MethodError: no method matching var"##bar_patch#314"(::Int64)
│        The applicable method may be too new: running in world age 32489, while current world is 32491.
│        Closest candidates are:var"##bar_patch#314"(::Any) at REPL[6]:1 (method too new to be called from this world context.)
│        Stacktrace:
│         [1] macro expansion
│           @ ~/.julia/packages/Mocking/MRkF3/src/mock.jl:24 [inlined]
│         [2] foo(x::Int64)
│           @ Main ./REPL[2]:2
│         [3] (::var"#1#2")(ch::Channel{Any})
│           @ Main ./REPL[5]:2
│         [4] (::Base.var"#591#592"{var"#1#2", Channel{Any}})()
│           @ Base ./channels.jl:134
└ @ Main REPL[7]:3

julia>

If we aren't sure how to fix #89, can we fix this issue in the meantime by changing the call to the alternate to use invokelatest?
i.e. change this:

$alternate_var($args_var...; $(kwargs...))

to this:

    Base.invokelatest($alternate_var, $args_var...; $(kwargs...)) 

?

I don't see any downside there, and it can help avoid catastrophic issues when #89 is encountered.
(I guess the main counter-argument would be that maybe it's better to crash, since this is probably accidental (otherwise the user could just move the patch definition to before the task is spawned) and it's better to have this error be more visible?)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions