Task switch not allowed from inside staged nor pure functions

question

#1

Recently I encountered the following error while developing my experimental Grassmann.jl package

The following error only occurs if I run the commands at once using ; on one line

julia> using Grassmann; mixedbasis"2"
[ Info: Allocating thread-safe 16×Basis{VectorSpace{4,0,0,12}*,...}
┌ Error: Exception while generating log record in module Grassmann at /home/flow/.julia/dev/Grassmann/src/Grassmann.jl:136
│   exception =
│    task switch not allowed from inside staged nor pure functions
│    Stacktrace:
│     [1] unsafe_write(::Base.TTY, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:830
│     [2] macro expansion at ./io.jl:509 [inlined]
│     [3] write(::Base.TTY, ::Array{UInt8,1}) at ./io.jl:532
│     [4] #handle_message#2(::Nothing, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Logging.ConsoleLogger, ::Base.CoreLogging.LogLevel, ::String, ::Module, ::String, ::Symbol, ::String, ::Int64) at /build/julia/src/julia-1.0.3/usr/share/julia/stdlib/v1.0/Logging/src/ConsoleLogger.jl:161
│     [5] handle_message(::Logging.ConsoleLogger, ::Base.CoreLogging.LogLevel, ::String, ::Module, ::String, ::Symbol, ::String, ::Int64) at /build/julia/src/julia-1.0.3/usr/share/julia/stdlib/v1.0/Logging/src/ConsoleLogger.jl:100
│     [6] getalgebra(::Int64, ::Int64, ::UInt64) at ./logging.jl:320
│     [7] @mixedbasis_str(::LineNumberNode, ::Module, ::Any) at /home/flow/.julia/dev/Grassmann/src/multivectors.jl:212
└ @ Grassmann ~/.julia/dev/Grassmann/src/Grassmann.jl:136
Exception handling log message: 
(++--*, v, v₁, v₂, w¹, w², v₁₂, v₁w¹, v₁w², v₂w¹, v₂w², w¹², v₁₂w¹, v₁₂w², v₁w¹², v₂w¹², v₁₂w¹²)

otherwise, if I first run using Grassmann and then mixedbasis"2" afterwards, there is no issue.

This is likely because the REPL is doing some task switching due to the combined input.

What can I do about this issue? As far as I can tell, the error message does not affect the actual Julia session, and the result of the mixedbasis"2" command is returned after the error message.

This is an error which only takes place in some other REPL scope, but not in the program execution.

Now, I know some people might suggest that I should not be using @pure methods in certain contexts; however, I am having success with them if they are used safely. It is my choice to work with it and I don’t intend to change my design currently only to solve this one particular case.

Is there some way to disable the logging which causes this error? What’s exactly going on here, would be useful to know more details about how the error occurs.


#2

@pure functions may not have side effects. Printing a log message most certainly counts as a side effect. Either don’t declare the function @pure or remove the log message.


#3

I didn’t use any log messages, they are from the REPL internals I believe

as I said, there is no error if the commands are entered separately

julia> using Grassmann

julia> mixedbasis"2"

#4

In a recent topic, several posters warned you that your “pure” code was in fact not pure, and that you shouldn’t declare it such… It’s your choice to work with it, but consider that users of your package will have no choice but to work with it too (with issues like this).

Hmm, is it not the @info statement at Grassmann.jl:136 that triggers this? Could you wrap that in a try/catch? (or remove it/move it outside the pure function, as @tkoolen suggests)


#5

Thanks, you’re right. Yea, I might just remove the entire message I suppose, unless there is some way of detecting this situation and turning it off

This package is for my own personal enjoyment and explorations, but I hope that it can be useful for other people too. If people encounter a specific issue, they should let me know.


#6

I know that Core.print can be used in generated function. Not sure if you can use it inside @pure though. But I guess this is only for debugging, not for messaging in in-production packages.


#7

Alright, just pushed the commit and removed the @info logging message that I had.

This is actually the best option, now you can use Base.Threads.@threads without calling first

julia> using Grassmann

julia> out = Array{Any,1}(undef,7);

julia> Base.Threads.@threads for i ∈ 1:7
           out[i] = Λ(7)[i]
       end

julia> out
7-element Array{Any,1}:
 v 
 v₁
 v₂
 v₃
 v₄
 v₅
 v₆

As you can see, it is no longer necessary to call the function before multithreading it. You can just simply run a multithreaded call without problem, since the @info is now removed.

This means that the @info message itself is no longer necessary, because the algebra does not need to be declared thread safe to the user anymore, because it is thread safe. How ironic.

Thanks for the suggestions.


#8

I still think it would be cool and extremely useful to arrange @info and other logging so that it’s useful in restricted contexts like generated functions and finalizers. I think this would require some internal changes and might come with restrictions, but it would be ever so handy.

One way to preserve most of the semantics might be to have a (circular?) buffer of pending log events and a way to empty the buffer on leaving the restricted context. It seems kind of tricky but doable.


#9

I’ve used this in the past:

using Logging

# define safe loggers for use in generated functions (where task switches are not allowed)
for level in [:debug, :info, :warn, :error]
    @eval begin
        macro $(Symbol("safe_$level"))(ex...)
            macrocall = :(@placeholder $(ex...))
            # NOTE: `@placeholder` in order to avoid hard-coding @__LINE__ etc
            macrocall.args[1] = Symbol($"@$level")
            quote
                old_logger = global_logger()
                global_logger(Logging.ConsoleLogger(Core.stderr, old_logger.min_level))
                ret = $(esc(macrocall))
                global_logger(old_logger)
                ret
            end
        end
    end
end

#10

Hah thanks, that looks handy (if evil :-D)