Skip to content

@threadcall fragility #17819

@maleadt

Description

@maleadt

There's multiple issues with @threadcall I've spotted while debugging a test.

  1. segfaults when printing/... in called cfunction
foo(a) = (println("$a"); Cint(42))
foo_c = cfunction(foo, Cint, (Cint,))
@show       ccall(foo_c, Cint, (Cint,), 1)
@show @threadcall(foo_c, Cint, (Cint,), 1)

Probably by design, but the docs don't mention such a restriction.

  1. argument passing botched
foo(a,b) = (Cint(a+b))
foo_c = cfunction(foo, Cint, (Cint,Cint))
@show       ccall(foo_c, Cint, (Cint,Cint), 1, 2)
@show @threadcall(foo_c, Cint, (Cint,Cint), 1, 2)       # returns 2

The reason is a missing ptr adjustment when creating the args array in do_threadcall:

# cconvert, root and unsafe_convert arguments
roots = Any[]
args_size = isempty(argtypes) ? 0 : sum(sizeof, argtypes)
args_arr = Array{UInt8}(args_size)
ptr = pointer(args_arr)
for (T, x) in zip(argtypes, argvals)
    y = cconvert(T, x)
    push!(roots, y)
    unsafe_store!(convert(Ptr{T}, ptr), unsafe_convert(T, y))
    ptr += sizeof(T)    # ADDED
end
  1. example 2 often deadlocks

When debugging 2) by adding some print statements to do_threadcall, Julia easily locked up. Not sure whether or not this is expected, but I thought I'd mention it anyway.

import Base: do_threadcall

import Base: cconvert, unsafe_convert,
             acquire, threadcall_restrictor,
             thread_notifiers, c_notify_fun, release

function do_threadcall(wrapper::Function, rettype::Type, argtypes::Vector, argvals::Vector)
    # generate function pointer
    fun_ptr = cfunction(wrapper, Int, (Ptr{Void}, Ptr{Void}))

    # cconvert, root and unsafe_convert arguments
    roots = Any[]
    args_size = isempty(argtypes) ? 0 : sum(sizeof, argtypes)
    args_arr = Array{UInt8}(args_size)
    ptr = pointer(args_arr)
    for (T, x) in zip(argtypes, argvals)
        y = cconvert(T, x)
        push!(roots, y)
        unsafe_store!(convert(Ptr{T}, ptr), unsafe_convert(T, y))
        ptr += sizeof(T)    # ADDED
    end

    # create return buffer
    ret_arr = Array{UInt8}(sizeof(rettype))

    # wait for a worker thread to be available
    acquire(threadcall_restrictor)
    idx = findfirst(isnull, thread_notifiers)
    thread_notifiers[idx] = Nullable{Condition}(Condition())

    # queue up the work to be done
    println("queue work")
    ccall(:jl_queue_work, Void,
        (Ptr{Void}, Ptr{UInt8}, Ptr{UInt8}, Ptr{Void}, Cint),
        fun_ptr, args_arr, ret_arr, c_notify_fun, idx)

    # wait for a result & return it
    println("wait for it")
    wait(get(thread_notifiers[idx]))
    thread_notifiers[idx] = Nullable{Condition}()
    release(threadcall_restrictor)

    unsafe_load(convert(Ptr{rettype}, pointer(ret_arr)))
end


using Base.Test

foo(a,b) = (Cint(a+b))
foo_c = cfunction(foo, Cint, (Cint,Cint))
@show       ccall(foo_c, Cint, (Cint,Cint), 1, 2)
@show @threadcall(foo_c, Cint, (Cint,Cint), 1, 2)

Tested on latest master.

Metadata

Metadata

Assignees

No one assigned

    Labels

    multithreadingBase.Threads and related functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions