Skip to content

Writing a TCP client with JuliaC #126

@vchuravy

Description

@vchuravy

I have a fairly simple piece of code that does:

function recv_message(client::LEDCubeClient)
    buf = UInt8[]
    byte = read(client.sock, UInt8)
    while byte != 0x00
        push!(buf, byte)
        byte = read(client.sock, UInt8)
    end
    recv_msg = COBSReduced.cobs_decode(buf)
    decoder = ProtoDecoder(IOBuffer(recv_msg))
    return decode(decoder, MatrixServerMessage)
end

where client.sock is a TCPSocket.

It fails with:

Precompiling packages finished.
  2 dependencies successfully precompiled in 2 seconds. 13 already precompiled.
◑ Compiling...Verifier error #1: unresolved call from statement (Base.var"#readcb_specialized#uv_readcb##0"())(ccall(:jl_value_ptr)::Base.LibuvStream, nread::Any, ccall(:jl_uv_buf_len)::UInt64)::Any
Stacktrace:
 [1] uv_readcb(handle::Ptr{Nothing}, nread::Int64, buf::Ptr{Nothing})
   @ Base stream.jl:720

Trim verify finished with 1 error, 0 warnings.

I have a horrible fix of monkey-patching Base:

function Base.uv_readcb(handle::Ptr{Cvoid}, nread::Cssize_t, buf::Ptr{Cvoid})
    stream_unknown_type = Base.@handle_as handle Base.LibuvStream
    nrequested = ccall(:jl_uv_buf_len, Csize_t, (Ptr{Cvoid},), buf)
    function readcb_specialized(stream::Base.LibuvStream, nread::Int, nrequested::UInt)
        lock(stream.cond)
        if nread < 0
            if nread == Base.UV_ENOBUFS && nrequested == 0
                # remind the client that stream.buffer is full
                notify(stream.cond)
            elseif nread == Base.UV_EOF # libuv called uv_stop_reading already
                if stream.status != Base.StatusClosing
                    stream.status = Base.StatusEOF
                    notify(stream.cond)
                    if stream isa Base.TTY
                        # stream can still be used by reseteof (or possibly write)
                    elseif !(stream isa Base.PipeEndpoint) && ccall(:uv_is_writable, Cint, (Ptr{Cvoid},), stream.handle) != 0
                        # stream can still be used by write
                    else
                        # underlying stream is no longer useful: begin finalization
                        ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
                        stream.status = Base.StatusClosing
                    end
                end
            else
                stream.readerror = Base._UVError("read", nread)
                notify(stream.cond)
                # This is a fatal connection error
                ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), stream.handle)
                stream.status = Base.StatusClosing
            end
        else
            Base.notify_filled(stream.buffer, nread)
            notify(stream.cond)
        end
        unlock(stream.cond)
        # Stop background reading when
        # 1) there's nobody paying attention to the data we are reading
        # 2) we have accumulated a lot of unread data OR
        # 3) we have an alternate buffer that has reached its limit.
        if stream.status == Base.StatusPaused ||
                       (stream.status == Base.StatusActive &&
            ((bytesavailable(stream.buffer) >= stream.throttle) ||
             (bytesavailable(stream.buffer) >= stream.buffer.maxsize)))
            # save cycles by stopping kernel notifications from arriving
            ccall(:uv_read_stop, Cint, (Ptr{Cvoid},), stream)
            stream.status = Base.StatusOpen
        end
        nothing
    end
    if stream_unknown_type isa TCPSocket
        return readcb_specialized(stream_unknown_type::TCPSocket, nread, nrequested)
    else
        return readcb_specialized(stream_unknown_type, nread, nrequested)
    end
    nothing
end

Version

julia> versioninfo()
Julia Version 1.12.5
Commit 5fe89b8ddc1 (2026-02-09 16:05 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 16 × AMD Ryzen 7 7840U w/ Radeon  780M Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, znver4)
  GC: Built with stock GC

(@v1.12) pkg> app st
[acedd4c2] JuliaC v0.3.1
  juliac ~/.julia/juliaup/julia-1.12.5+0.x64.linux.gnu/bin/julia

Metadata

Metadata

Assignees

No one assigned

    Labels

    trimmingRelated to --trim mode and verifier errorsupstreamRequires fix in Julia base or external package

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions