Skip to content
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

Nospecialize close(c::Channel, excp::Exception) on excp. #49508

Merged
merged 3 commits into from
Apr 26, 2023

Conversation

NHDaly
Copy link
Member

@NHDaly NHDaly commented Apr 26, 2023

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and should avoid a call into the runtime for julia compilation when attempting to report an exception.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.
base/channels.jl Outdated
@@ -183,7 +183,7 @@ Close a channel. An exception (optionally given by `excp`), is thrown by:
* [`put!`](@ref) on a closed channel.
* [`take!`](@ref) and [`fetch`](@ref) on an empty, closed channel.
"""
function close(c::Channel, excp::Exception=closed_exception())
function close(c::Channel, @nospecialize(excp::Exception=closed_exception()))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoids dynamic dispatch when closing a Channel with an Exception

Oh, jk i forgot that Channel is also a parametric type, so this will still have a dispatch to do.

... is there any reason to specialize on the Channel type? None of the Channel fields accessed in this function depend on the type of the parameter.. Would julia be able to tell that, and still be able to produce concrete code for a type without its type parameter if none of the accessed fields depend on the parameter? My guess is the answer is no, but it would be a neat feature.

@vchuravy or @pchintalapudi you are probably the best people I could get to answer that question - do you know?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@quinnj and @vtjnash: Without the above, i guess this will still trigger compilation, due to the dispatch on the Channel type.

Can I mark that as nospecialize too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may incur internal dispatch (for lock and unlock), but it can potentially still figure out memory layout and dispatch specializations

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like inference should know the type of Channel. Only the exception argument is type Any

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may incur internal dispatch (for lock and unlock), but it can potentially still figure out memory layout and dispatch specializations

Oh right. Good points..

.... Is there any reason I can't mark these as @nospecialize(c::Channel) as well???:

julia/base/channels.jl

Lines 525 to 528 in 36ace54

lock(c::Channel) = lock(c.cond_take)
lock(f, c::Channel) = lock(f, c.cond_take)
unlock(c::Channel) = unlock(c.cond_take)
trylock(c::Channel) = trylock(c.cond_take)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...I'm finding that I can't rely on Cthulhu to answer this question, since it seems to disregard the @nospecialize, and shows me a function that's been inferred specifically for the type i'm calling with, even though codegen will ultimately produce a single generic native function, right?

It looks like inference should know the type of Channel. Only the exception argument is type Any

@vtjnash: I'm not sure what you meant by this message. Can you expand on it? How does it apply to the situation where I mark both arguments nospecialize in close?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhhhhh you're saying I don't need to mark it nospecialize in order to avoid a dispatch, since inference already knows the channel type, so i could still avoid the dispatch even if the exception type is unknown! 💡 great point. So it could still be an invoke, or even inlined 👍

Neat!

Oh, so then maybe Cthulhu was right after all, because it was devirtualizing my call! Aha, yes that was it. Neat!! :)
Julia is pretty cool you guys.

So then: the only fear would be that if this is the first exception we've ever called close() on for this channel type, we might still stackoverflow. But if we've done it for some other exceptions first, we'll be okay.
... that's probably still an improvement, and so we can probably do without marking it @nospecialize. (Unless we think it would still produce good code even in that case.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, incredible! Maybe it actually can produce good code, even if the Channel is nospecialize!

julia> struct Foo{T}
           a::Int
           b::T
       end

julia> function foo(@nospecialize(x::Foo))
           bar(x.a)
       end
foo (generic function with 1 method)

julia> bar(v) = v + 1
bar (generic function with 1 method)

julia> function run(v::Vector{Any})
           return foo(v[1])
       end
run (generic function with 1 method)

julia> using Cthulhu

julia> @descend run(Any[Foo(1, "hi")])
run(v::Vector{Any}) in Main at REPL[4]:1
1 function run(v::Vector{Any}::Vector{Any})::Int64
2     return foo(v::Vector{Any}[1]::Any)::Int64
3 end
Select a call to descend into or  to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
   2 v::Vector{Any}[1]
 • 2 foo(v::Vector{Any}[1]::Any)
   
foo(x::Foo) in Main at REPL[2]:1
1 function foo(@nospecialize(x::Foo::Foo))::Int64
2     bar(x::Foo.a::Int64)::Int64
3 end
Select a call to descend into or  to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
   2 x::Foo.a2 bar(x::Foo.a::Int64)
   
bar(v) in Main at REPL[3]:1
1 bar(v::Int64)::Int64 = v::Int64 + 1
Select a call to descend into or  to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
   1  v::Int64 + 1

julia>

Thanks Cthulhu: you're an amazing tool. (And thanks @vchuravy ❤️)

So I'm down to nospeicalize everything! Thoughts? I'll send up a commit like that too.

Copy link
Member Author

@NHDaly NHDaly Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

K, i put that into a separate PR, here, since I kind of went crazy and added a bunch of them everywhere it seemed to not depend on T:
#49509

I'll merge this one once tests are green, since we're happy with it, and we can discuss what to do next on #49509.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So then: the only fear would be that if this is the first exception we've ever called close() on for this channel type, we might still stackoverflow. But if we've done it for some other exceptions first, we'll be okay.

Oh, even this isn't a concern, since when it's devirtualized, it'll be inferred during its caller's inference! Wow, julia is pretty rad. 😎

@NHDaly
Copy link
Member Author

NHDaly commented Apr 26, 2023

BTW, yep, this does indeed resolve the issue by preventing dispatch+compilation when reporting the error, since it could already be compiled when compiling its caller.

Here's evidence: the reproducer from the original issue now succeeds:

julia> ch = let result = Channel()

       foo() = try
           foo()
       catch e;
           close(result, e)
       end

       foo()

       result
       end
Channel{Any}(0) (closed)

julia> take!(ch)
ERROR: StackOverflowError:
Stacktrace:
 [1] check_channel_state
   @ ./channels.jl:174 [inlined]
 [2] take_unbuffered(c::Channel{Any})
   @ Base ./channels.jl:472
 [3] take!(c::Channel{Any})
   @ Base ./channels.jl:451
 [4] top-level scope
   @ REPL[2]:1

@NHDaly
Copy link
Member Author

NHDaly commented Apr 26, 2023

Added a test, all tests passed, merging now. Thanks!

@NHDaly NHDaly merged commit a152d11 into master Apr 26, 2023
@NHDaly NHDaly deleted the nhd-close-channel-excp-nospecialize branch April 26, 2023 14:55
@NHDaly NHDaly added the backport 1.9 Change should be backported to release-1.9 label Apr 27, 2023
@NHDaly
Copy link
Member Author

NHDaly commented Apr 27, 2023

Since this is a bug-fix, could it be a backport candidate?

Comment on lines +630 to +631
# Issue #49507: stackoverflow in type inference caused by close(::Channel, ::Exception)
@testset "close(::Channel, ::StackOverflowError)" begin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot I meant to say this should go into the stack_overflow.jl file, since the handling of this error is quite bad and sometimes ends up breaking the process (as I found out today trying to debug something completely unrelated). Do you mind making a PR to move it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting. Yeah, i'm happy to move it! 👍 PR incoming

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR here: #49702

KristofferC pushed a commit that referenced this pull request May 8, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.

(cherry picked from commit a152d11)
@KristofferC KristofferC mentioned this pull request May 8, 2023
51 tasks
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 15, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit that referenced this pull request May 17, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
@KristofferC KristofferC removed the backport 1.9 Change should be backported to release-1.9 label May 28, 2023
kpamnany pushed a commit to RelationalAI/julia that referenced this pull request Jun 6, 2023
…9508)

* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes JuliaLang#49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
kpamnany pushed a commit to RelationalAI/julia that referenced this pull request Jun 6, 2023
…9508)

* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes JuliaLang#49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
NHDaly added a commit to RelationalAI/julia that referenced this pull request Jun 7, 2023
…9508) (#13)

* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes JuliaLang#49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.
kpamnany pushed a commit that referenced this pull request Jun 21, 2023
* Nospecialize close(c::Channel, excp::Exception) on excp.

Fixes #49507.

Avoids dynamic dispatch when closing a Channel with an Exception, and
should avoid a call into the runtime for julia compilation when
attempting to report an exception.

* Add test for this case.

(cherry picked from commit a152d11)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

stackoverflow in type inference caused by handling a StackOverflow exception
4 participants