Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArgumentError("send() requires !(ws.writeclosed)") #659

Open
wiseac opened this issue Jul 27, 2023 · 6 comments
Open

ArgumentError("send() requires !(ws.writeclosed)") #659

wiseac opened this issue Jul 27, 2023 · 6 comments
Assignees

Comments

@wiseac
Copy link

wiseac commented Jul 27, 2023

In these dev logs, i keep getting this error.

Error: 2023-07-26 16:14:02 ArgumentError("send() requires !(ws.writeclosed)") └ @ Genie.WebChannels /home/crd/.julia/packages/Genie/XpFvB/src/WebChannels.jl:239

It happens pretty often and I'm not sure why?
┌ Error: 2023-07-26 16:14:02 ArgumentError("send() requires!(ws.writeclosed)") └ @ Genie.WebChannels /home/crd/.julia/packages/Genie/XpFvB/src/WebChannels.jl:239 ┌ Error: 2023-07-26 16:14:53 ArgumentError("send() requires !(ws.writeclosed)") └ @ Genie.WebChannels /home/crd/.julia/packages/Genie/XpFvB/src/WebChannels.jl:239 ┌ Error: 2023-07-26 16:15:02 ArgumentError("send() requires !(ws.writeclosed)") └ @ Genie.WebChannels /home/crd/.julia/packages/Genie/XpFvB/src/WebChannels.jl:239

I am running a global model webpage and have a for loop running for long periods at a time 1 hr+.

Best,
W

@essenciary
Copy link
Member

essenciary commented Jul 27, 2023

@wiseac I've never seen this one. Can you provide a small example of how to replicate this? Thanks

@essenciary
Copy link
Member

Closing due to feedback. Please reopen if you can provide more info.

@zygmuntszpak
Copy link

zygmuntszpak commented Jan 23, 2024

I'm also encountering the same error but in a rather unpredictable fashion (hence difficult to reproduce).
Comparing the Genie log with the console message in the browser I noticed the following:
send requires not write closed

and then in the Console at around the same timestamp

invalid frame header

However, the same error appears later in the Console

invalid frame header again

but this time there is no concomitant error in Genie.

Moreover, there are other errors in the Console such as

could not decode text

which sometimes do not give rise to any error in Genie, but at other times do as in this example:

could not decode text again send requires not write closed again

I don't know if concurrency is related to the issue, but in my use case I am continually reading a stream of MQTT messages and updating reactive plots every second. The code to retrieve the latest MQTT messages from a database and update the reactive variables is running in its own task.

Regarding the "Could not decode a text frame as UTF-8" there is a discussion of some possible solutions here https://stackoverflow.com/questions/17126227/could-not-decode-a-text-frame-as-utf-8 but I'm not sure if this is a Websockets.jl issue or a Genie issue.

@essenciary
Copy link
Member

Looks like the socket connection is closed when Genie attempts to push some data. Hence the error message which could be read as sending requires that the websocket is not closed (while in fact it is closed).

Btw, that error is not thrown by Genie, but comes from WebSockets/HTTP package.

@hhaensel hhaensel reopened this Feb 6, 2024
@hhaensel
Copy link
Member

hhaensel commented Feb 6, 2024

I encountered a very similar issue when sending messages to the websocket asynchronously.

In normal circumstances this doesn't happen. But if you have external async events triggering updates then this can occur.
The root cause is that Socket.send(ws, message) doesn't protect the ws from being written to by other processes. When this happens, the two messages get mixed and the messages are misunderstood.

@essenciary

We could solve this by a setup like the following:

using Genie.WebChannels.HTTP.WebSockets
using Sockets

const WS_QUEUES = Dict{UInt, Channel{Tuple{String, Channel{Nothing}}}}()

function message_safe(clientid, message::String)
    ws = Genie.WebChannels.CLIENTS[clientid].client
    future = Channel{Nothing}(1)

    q = get!(WS_QUEUES, clientid) do
        println("Setting up websocket queue for $(repr(clientid)) ...")
        queue = Channel{Tuple{String, Channel{Nothing}}}(10)
        t = @async while true
            mymessage, myfuture = take!(queue)
            try
                Sockets.send(ws, mymessage)
            finally
                println("future: $myfuture")
                println(mymessage)
                put!(myfuture, nothing)
            end
        end
        queue
    end

    put!(q, (message, future))
        
    take!(future) # Wait until the message is processed
end

If you want to test the approach, simply start any Genie App, open the dev tools to observe the browser's console and execute the following

say_secure(clientid, x) = message_safe(clientid,
    Genie.WebChannels.tagbase64encode(""">eval:console.log($(js_attr(x)))""")
)

say(ws::WebSocket, x) = send(ws,
    Genie.WebChannels.tagbase64encode(""">eval:console.log($(js_attr(x)))""")
)

say(clientid::UInt, x::String) = say(Genie.WebChannels.CLIENTS[clientid].client, x)

ws = Genie.WebChannels.clients()[1].client
client = Genie.WebChannels.id(ws)

say(client, "hello")
# will say 'hello' at the browser console

say_secure(client, "hello2")
# will say 'hello2' at the browser console

# this will work
for i in 1:20
    @async say_secure(client, "hello $i you")
end


# this will produce the UTF-8 error as described in the issue above
for i in 1:20
    @async say(client, "hello $i")
end

Here I set up the handler during the first send() but we could make it part of the subscription.

@essenciary
Copy link
Member

@hhaensel thanks! It would make sense to have this as the default - would you like to make a PR please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants