Close instead of wait if TCP socket is never drained

In your example the server never reads from the socket. That means the send buffer on the client eventually fills up, and further write calls block.

When you later call close(sock), Julia’s close wraps uv_close from libuv. uv_close tries to flush/clean up any pending writes before completing. But because the server isn’t reading, those writes never drain. As a result, the close call hangs forever.

A workaround is to call shutdown(2) explicitly on the client socket before closing it. This tells the kernel to immediately tear down one or both directions of the TCP connection without waiting for buffered data to be delivered. That avoids libuv’s flush loop and lets Julia’s close return.

Note, however, that this will leave the server-side connection open. The server won’t notice the shutdown until it eventually attempts to read from or write to the socket.

using Sockets

# <sys/socket.h>
# #define SHUT_RDWR 2
const SHUT_RDWR = 2

function shutdown_close(sock)
    sockfd = Base._fd(sock)

    ret = ccall(:shutdown, Cint, (Cint, Cint), sockfd, SHUT_RDWR)
    if ret != 0
        err = Libc.errno()
        error("shutdown($sockfd, $how) failed errno=$err")
    end

    close(sock)
    return nothing
end

s = listen(1234)
try
    local c, cs
    @sync begin
        @async cs = accept(s)
        c = connect(1234)
    end
    try
        i = 0
        data = zeros(UInt8, 1000)
        while true
            t = Timer(_ -> shutdown_close(c), 1)
            write(c, data)
            close(t)

            i += 1
            println(i)
        end
    finally
        close(cs)
        close(c)
    end
finally
    close(s)
end
1 Like