Skip to content

Conversation

@vtjnash
Copy link
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 io Involving the I/O subsystem: libuv, read, write, etc. label Feb 21, 2024
@vtjnash vtjnash requested review from gbaraldi and vchuravy February 21, 2024 20:47
@vtjnash vtjnash added the merge me PR is reviewed. Merge when all tests are passing label Feb 23, 2024
Copy link
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
@vtjnash vtjnash deleted the jn/iothread-dedicated branch February 26, 2024 21:06
@vtjnash
Copy link
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 merge me PR is reviewed. Merge when all tests are passing label Feb 26, 2024
@IanButterworth
Copy link
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 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
Contributor

lassepe commented Jul 24, 2024

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

@vtjnash
Copy link
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

io Involving the I/O subsystem: libuv, read, write, etc. multithreading Base.Threads and related functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants