Channel is closed error when trying to put! objects in LibCURL write callback

Solved by wrapping curl_easy_perform in @async

Julia version: 1.0.5
LibCURL: [b27032c2] LibCURL v0.5.2

julia> unsafe_string(LibCURL.curl_version())
"libcurl/7.64.1 mbedTLS/2.6.1 zlib/1.2.11"

When I use a write callback with a Channel, I always get a Channel is closed error even though I never close the channel.


using LibCURL, Test

function curl_write_cb(curlbuf::Ptr{Cvoid}, s::Csize_t, n::Csize_t, p_ctxt::Ptr{Cvoid})::Csize_t
    sz = s * n
    data = Array{UInt8}(undef, sz)

    ccall(:memcpy, Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, UInt64), data, curlbuf, sz)

    ch_ref = unsafe_pointer_to_objref(p_ctxt)

    GC.@preserve data ch_ref begin
        put!(ch_ref[], data)


c_curl_write_cb = @cfunction(curl_write_cb, Csize_t, (Ptr{Cvoid}, Csize_t, Csize_t, Ptr{Cvoid}))

function curl_add_headers(curl::Ptr, headers::Vector{String})
    slist = Ptr{Cvoid}(0)
    for header in headers
        slist = curl_slist_append(slist, header)

    return curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)

function test_writeCB()
    curl = curl_easy_init()

    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION,  1)     # Follow HTTP redirects
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,  1)     # Verify the peer's SSL cert
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,  2)     # Verify the server's Common Name
    curl_easy_setopt(curl, CURLOPT_SSLVERSION,      7<<16) # Try highest version up to TLS 1.3
    curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,    4)     # Use H2 over SSL or HTTP/1.1 otherwise
    curl_easy_setopt(curl, CURLOPT_TCP_FASTOPEN,    1)     # Use TCP Fastopen
    curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE,   1)     # Use TCP Keepalive
    curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "")    # Use best supported encoding (compression) method. gzip or deflate
    curl_easy_setopt(curl, CURLOPT_POST, 1)            # Use HTTP Post
    curl_easy_setopt(curl, CURLOPT_URL, "")

    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1)

    # We create a channel to pass data between the curl write handler and this function
    databuffer = Array{UInt8}[]
    ch = Channel(ctype=Array{UInt8}) do ch
        push!(databuffer, take!(ch))

    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, c_curl_write_cb)
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, Ref(ch))

    errorbuffer = Array{UInt8}(undef, CURL_ERROR_SIZE)
    curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuffer)

    requestBody = """{"a":10,"b":[1,2,3]}"""
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, length(requestBody))
    curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, requestBody)

    curl_add_headers(curl, [
        "Content-Type: application/json",
        "Content-Length: $(length(requestBody))"

    res = curl_easy_perform(curl)

    println(join(map(String, databuffer), ""))

    @test 0 == res


Output (after all the TCP and SSL stuff):

> POST /post HTTP/1.1
Accept: */*
Accept-Encoding: deflate, gzip
Content-Type: application/json
Content-Length: 20

* upload completely sent off: 20 out of 20 bytes
< HTTP/1.1 200 OK
< Date: Thu, 04 Jun 2020 01:39:12 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 383
< Connection: keep-alive
< ETag: W/"17f-a0IU3ZFzsjQnxeU9NYDf8IpRn7I"
< Vary: Accept-Encoding
< set-cookie: sails.sid=s%3AKXgZGDIOUCGmkt61Dp26tyyEckbI2cBk.AeM6UJ6UjW7L9HA1jbKrM7W1rSPiyemT5Ez%2FJ%2BCodlg; Path=/; HttpOnly
ERROR: LoadError: InvalidStateException("Channel is closed.", :closed)
 [1] check_channel_state at ./channels.jl:120 [inlined]
 [2] put!(::Channel{Array{UInt8,N} where N}, ::Array{UInt8,1}) at ./channels.jl:250
 [3] macro expansion at ./gcutils.jl:87 [inlined]
 [4] curl_write_cb(::Ptr{Nothing}, ::UInt64, ::UInt64, ::Ptr{Nothing}) at test/runtests.jl:12
 [5] curl_easy_perform at /home/ubuntu/.julia/packages/LibCURL/lWJxD/src/lC_curl_h.jl:162 [inlined]
 [6] test_writeCB() at test/runtests.jl:68
 [7] top-level scope at none:0
 [8] include at ./boot.jl:317 [inlined]
 [9] include_relative(::Module, ::String) at ./loading.jl:1044
 [10] include(::Module, ::String) at ./sysimg.jl:29
 [11] exec_options(::Base.JLOptions) at ./client.jl:266
 [12] _start() at ./client.jl:425
in expression starting at test/runtests.jl:77

Figured out that I could wrap curl_easy_perform in @async to get this to work