Skip to content

Cannot use channels to send data from a write callback: Channel is closed #82

Closed
@bluesmoon

Description

@bluesmoon

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.

Code:

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)
    end

    sz::Csize_t
end

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)
    end

    return curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)
end


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, "https://postman-echo.com/post")

    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))
    end

    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(length(databuffer))
    println(join(map(String, databuffer), ""))

    @test 0 == res
end


test_writeCB()

Output (after all the TCP and SSL stuff):

> POST /post HTTP/1.1
Host: postman-echo.com
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)
Stacktrace:
 [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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions