Readavailable with timeout

I would like to read the first message out of a websocket as soon as it arrives, but also I want to close the connection if nothing arrives within a second.

How can I do that?

Example:

task = @async HTTP.WebSockets.listen("127.0.0.1", UInt16(9091)) do ws
    while !eof(ws)
        cmd = JSON.parse(String(readavailable(ws)))
        println(cmd)
     end
end

The readavailable function does not have a timeout parameter. If I call it then it may block indefinitely. The eof function does not block at all, so it cannot be used to “wait for the first message”.

So how do I do this?

Background info: I want to create a server that can respond quickly, closes the connection if there is no communication for a while.

I had a similar problem, except that I needed to read at most one message. I solved it with the following function, which may point you towards a solution for your case. The idea is to create a timer that interrupts the asynchronous task:

function async_reader(io::IO, timeout_sec)::Channel
    ch = Channel(1)
    task = @async begin
        reader_task = current_task()
        function timeout_cb(timer)
            put!(ch, :timeout)
            Base.throwto(reader_task, InterruptException())
        end
        timeout = Timer(timeout_cb, timeout_sec)
        data = String(readavailable(io))
        timeout_sec > 0 && close(timeout) # Cancel the timeout
        put!(ch, data)
    end
    bind(ch, task)
    return ch
end

I call it like this:

ch_out = async_reader(io, timeout)
out = take!(ch_out)  # if out===:timeout, a timeout occurred

So, out is equal to either the data from io, or :timeout. The take! function will block for at most timeout seconds.

1 Like

Thank you for your answer. It does work, but it creates a Channel, a Timer and a Task for each read operation. It seems to be an overkill.

Probably I could rewrite async_reader to put data into the channel in a loop. But then I would have to use the same timeout for all operations, and it would still create and cancel a Timer object for each read operation, even if there is plenty of data available.

It cannot be efficient. I cannot write speedy network I/O with this. There must be a better way.

I would have expected the result from the readAvailable function would be binary : Yes or No ( true or false ) without any waiting, as per other languages. Then the readAvailable can be used in a loop - and divert the routine to take action ( such as reading the input read(stdin, Char) when it is known that there is a character to be read. ) Seems very strange to me that readAvailable should hang the process to wait for an input - surely that is not what was intended - at the moment it operating no different than the standard read function

readavailable blocking is actually the intended behavior. You can do what you describe by using bytesavailable.

1 Like

Actually, you can’t do that with bytesavailable, because it is also a synchronous function. What I really wanted (e.g. wait at most 2 seconds for data to be available) cannot be done with synchronous/blocking functions.

I am not sure what you mean by synchronous. What I meant is that bytesavailable is not blocking; that is, if there is nothing to read, it immediately returns 0, which I think is what @GregMcC wanted.

1 Like

Originally, this is my question and I wanted to the server to respond quickly, and close the connection if there is no communication for a while (e.g. a few seconds).

@GremMcC did not have a question, he was just suprised.

Back to the original question: bytesavailable does not block, that is true. But bytesavailable cannot be used to “wait for incoming data and act quickly”. Simply because it does not wait at all. It cannot respond to external events efficiently. On the other side, readavailable could be used to respond to incoming data quickly (because it blocks until data is available, but then returns immediately), but it may block the program indefinitely, and does not fulfill the second condition (be able to close the connection if there is no communication for a while).

We only have two functions. One may block forever, the other does not block at all. There is nothing in between. The perfect solution would be to add a timeout parameter to readavailable. The workaround provided by @mbaz works, but it is clumsy, and probably not nearly as efficient as it should be. Integrating readavailable with the core I/O loop would be the best solution. Starting a new thread for reading data, and interrupting with an external exception is clearly not a good way to do it. But this is the only way, as far as I can see.

I hear you: for me, the hardest part of coding Gaston (a plotting package based on gnuplot) has always been reading/writing to/from streams. I’m still not sure my solution is optimal.

Having said that, what I’ve heard from the developers these past few years is, the way to do streaming I/O is to wrap the I/O functions in a task and use channels. My understanding is that this is the Julian way to do it. Since Julia is optimized for this use case, it should be faster and more efficient than it looks at first sight.

Of course, the state of I/O can still be significantly improved, both in documentation and implementation. I think this is one of the main issues to follow or contribute to: https://github.com/JuliaLang/julia/issues/24526.