Is there a general solution to this problem?

  • I create a tungstenite WebSocket reference
  • I connect, then spawn a thread that calls read() until the socket errors
  • The socket must be moved into the thread closure to do this

Then:

  • I want to be able to disconnect the socket from my end by calling close()
  • To do that I need to store a reference to the socket that another thread can access
  • This is not possible due to the move
  • Even using Arc doesn't work due to lifetime issues, and the fact that read() takes mut &self and can't be called thru an Arc

The simplest solution is to make the function that you're spawning return the socket. Then you can join the thread to get it back. Otherwise, you can use a mutex.

I'm not familiar with tungstenite, but I would expect its sockets to close automatically on drop, which in your case would happen when the thread closure completes (due either to an error or to success).

Oh yes, it's weird that you want to move the socket to another thread just to call close on it. Why can't you close it in the spawned thread? How are you notifying the main thread that the socket should be closed?

The spawned thread can't call close() because it blocks on read(). There is no read() variant with a timeout. It will block forever.

The fn can't return the socket for the reasons above - the socket has to be moved into the thread closure.

And it can't be cloned.

And it can't be wrapped with an Arc, because read() take mut &self.

Are you saying that you want to be able to close the socket "across threads" while it's in the middle of a read? If so, option 1 would be to use a non-blocking version of read() that returns immediately if there's no data available — if such a method exists on the socket — and then call this in a loop that checks a flag on every iteration, and then another thread can set this flag to true when you want to close the socket. Option 2 would be to go full async.

Looking at the API I see that both read and close require &mut WebSocket. And there is no read with a timeout as you said. That means it can be impossible to close the socket! That's a terrible API. Hopefully we're missing something but there isn't a lot to it.

1 Like

There is no non-blocking read. That's just the problem. Closure takes ownership, then blocks on read.

I've avoided async throughout the app because I'm not a big fan of it. The app using threads and channels for concurrency - and all of it works great.

In practice, calling close() may not be necessary because read() will return an error when the server ends the connection. But I'd rather not build the system around that.

Thank you for that validation! :slight_smile:

Yeah, just when I thought again I had mastered the borrow checker, this pops up... but, as you note, it's really an API design flaw.

1 Like

:slight_smile: In fact this note on the read method confirms it:

Closing the connection

When the remote endpoint decides to close the connection this will return the close message with an optional close frame.

You should continue calling read, write or flush to drive the reply to the close frame until Error::ConnectionClosed is returned. Once that happens it is safe to drop the underlying connection.

I think the intention of tungstenite is that you will give it a reader that never blocks, but instead throws WouldBlock. This will be passed up through WebSocket::read and you can handle it there. If you don't want to deal with that stuff, then tokio-tungstenite is probably better.

1 Like

After reading enough of the API docs, I agree with this. And it looks like you can use client to supply a non-blocking stream.

Give it a reader?

I call connect, which returns a WebSocket<MaybeTlsStream<TcpStream>>, and call read (the only read method on the interface).

I'll explore the docs, but what you're describing wasn't in any of the samples I saw when this was written.

Alternatively it looks all variants of the MaybeTlsStream<TcpStream> you get out of connect[1] offer a route (e.g. .get_ref()) to get a &TcpStream you can call set_nonblocking on.


  1. to date ↩ī¸Ž

1 Like

I was going to suggest that also, but if you have to do the handshake yourself, you've lost the simplicity of it. I would just use an async websocket crate at that point.

1 Like