Skip to content

Writing a TCP client with JuliaC #126

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions