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

add dedicated IO thread (capability only) #53422

Merged
merged 1 commit into from
Feb 26, 2024
Merged

Conversation

vtjnash
Copy link
Sponsor Member

@vtjnash vtjnash commented Feb 21, 2024

It has been oft-requested that we have a dedicated IO thread. That actually turns out to already be the case of something that exists, except that we hard-code the identity of that thread as being thread 0. This PR replaces all of the places where we hard code that assumption with a variable so that they are more easily searched for in the code. It also adds an internal function (jl_set_io_loop_tid) that can be used to transfer ownership of the loop to any (valid) tid. In conjunction with the prior foreign-threads work and foreign-thread pool, this lets us spawn a dedicate IO-management thread with this bit of code:

function make_io_thread()
    tid = UInt[0]
    threadwork = @cfunction function(arg::Ptr{Cvoid})
            Base.errormonitor(current_task()) # this may not go particularly well if the IO loop is dead, but try anyways
            @ccall jl_set_io_loop_tid((Threads.threadid() - 1)::Int16)::Cvoid
            wait() # spin uv_run as long as needed
            nothing
        end Cvoid (Ptr{Cvoid},)
    err = @ccall uv_thread_create(tid::Ptr{UInt}, threadwork::Ptr{Cvoid}, C_NULL::Ptr{Cvoid})::Cint
    err == 0 || Base.uv_error("uv_thread_create", err)
    @ccall uv_thread_detach(tid::Ptr{UInt})::Cint
    err == 0 || Base.uv_error("uv_thread_detach", err)
    # n.b. this does not wait for the thread to start or to take ownership of the event loop 
    nothing
end

    function make_io_thread()
        tid = UInt[0]
        threadwork = @cfunction function(arg::Ptr{Cvoid})
                @CCall jl_set_io_loop_tid((Threads.threadid() - 1)::Int16)::Cvoid
                wait() # spin uv_run as long as needed
                nothing
            end Cvoid (Ptr{Cvoid},)
        err = @CCall uv_thread_create(tid::Ptr{UInt}, threadwork::Ptr{Cvoid}, C_NULL::Ptr{Cvoid})::Cint
        err == 0 || Base.uv_error("uv_thread_create", err)
        tid[]
    end
@vtjnash vtjnash added the domain:io Involving the I/O subsystem: libuv, read, write, etc. label Feb 21, 2024
@vtjnash vtjnash added the status:merge me PR is reviewed. Merge when all tests are passing label Feb 23, 2024
Copy link
Sponsor Member

@IanButterworth IanButterworth left a comment

Choose a reason for hiding this comment

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

Sounds great. Given this adds capability and you've given a tidy example, should that go in as a test, at least until an API is provided that can be used for the test?

@vtjnash vtjnash merged commit 7f92880 into master Feb 26, 2024
9 checks passed
@vtjnash vtjnash deleted the jn/iothread-dedicated branch February 26, 2024 21:06
@vtjnash
Copy link
Sponsor Member Author

vtjnash commented Feb 26, 2024

I don't really trust threading to be stable enough to run a test for it. Gabriel has an existing PR to fix some of the threading bugs, so this PR is really only intended to help someone in the future to implement this by providing an example.

@inkydragon inkydragon removed the status:merge me PR is reviewed. Merge when all tests are passing label Feb 26, 2024
@IanButterworth
Copy link
Sponsor Member

Is this a minor enough change with a NFC default state to backport to 1.11 as experimental & undocumented?

tecosaur pushed a commit to tecosaur/julia that referenced this pull request Mar 4, 2024
It has been oft-requested that we have a dedicated IO thread. That
actually turns out to already be the case of something that exists,
except that we hard-code the identity of that thread as being thread 0.
This PR replaces all of the places where we hard code that assumption
with a variable so that they are more easily searched for in the code.
It also adds an internal function (`jl_set_io_loop_tid`) that can be
used to transfer ownership of the loop to any (valid) tid. In
conjunction with the prior foreign-threads work and foreign-thread pool,
this lets us spawn a dedicate IO-management thread with this bit of
code:

```julia
function make_io_thread()
    tid = UInt[0]
    threadwork = @cfunction function(arg::Ptr{Cvoid})
            Base.errormonitor(current_task()) # this may not go particularly well if the IO loop is dead, but try anyways
            @CCall jl_set_io_loop_tid((Threads.threadid() - 1)::Int16)::Cvoid
            wait() # spin uv_run as long as needed
            nothing
        end Cvoid (Ptr{Cvoid},)
    err = @CCall uv_thread_create(tid::Ptr{UInt}, threadwork::Ptr{Cvoid}, C_NULL::Ptr{Cvoid})::Cint
    err == 0 || Base.uv_error("uv_thread_create", err)
    @CCall uv_thread_detach(tid::Ptr{UInt})::Cint
    err == 0 || Base.uv_error("uv_thread_detach", err)
    # n.b. this does not wait for the thread to start or to take ownership of the event loop 
    nothing
end
```
@JeffBezanson JeffBezanson added the domain:multithreading Base.Threads and related functionality label Mar 6, 2024
mkitti pushed a commit to mkitti/julia that referenced this pull request Mar 7, 2024
It has been oft-requested that we have a dedicated IO thread. That
actually turns out to already be the case of something that exists,
except that we hard-code the identity of that thread as being thread 0.
This PR replaces all of the places where we hard code that assumption
with a variable so that they are more easily searched for in the code.
It also adds an internal function (`jl_set_io_loop_tid`) that can be
used to transfer ownership of the loop to any (valid) tid. In
conjunction with the prior foreign-threads work and foreign-thread pool,
this lets us spawn a dedicate IO-management thread with this bit of
code:

```julia
function make_io_thread()
    tid = UInt[0]
    threadwork = @cfunction function(arg::Ptr{Cvoid})
            Base.errormonitor(current_task()) # this may not go particularly well if the IO loop is dead, but try anyways
            @CCall jl_set_io_loop_tid((Threads.threadid() - 1)::Int16)::Cvoid
            wait() # spin uv_run as long as needed
            nothing
        end Cvoid (Ptr{Cvoid},)
    err = @CCall uv_thread_create(tid::Ptr{UInt}, threadwork::Ptr{Cvoid}, C_NULL::Ptr{Cvoid})::Cint
    err == 0 || Base.uv_error("uv_thread_create", err)
    @CCall uv_thread_detach(tid::Ptr{UInt})::Cint
    err == 0 || Base.uv_error("uv_thread_detach", err)
    # n.b. this does not wait for the thread to start or to take ownership of the event loop 
    nothing
end
```
@lassepe
Copy link
Sponsor Contributor

lassepe commented Jul 24, 2024

FWIW, the example given above currently reliably segfaults errors on master.

@vtjnash
Copy link
Sponsor Member Author

vtjnash commented Jul 24, 2024

It does not segfault, but the Base.errormonitor line is not valid currently

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:io Involving the I/O subsystem: libuv, read, write, etc. domain:multithreading Base.Threads and related functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants