What is the ideal workflow when you need to read and write to a socket in random order?

I have a proxy app where I dont know how much to read and write to clients so if I block and wait for read I might prevent writes.
What I do is to create 2 threads per socket to be able to read and write separately but it sounds a little expensive creating 2 threads per connection. Is there a better way to handle this? Something like select perhaps?

You can do this very easily with async/await. In synchronous code you are forced to spawn two threads.

as we discussed on the other thread for some reason async await messes things up in my workflow. I simply cannot get it working but sync code works perfectly and easier to debug. After that failure I am extremely cautious about async.

Unless you want to re-implement what async/await already does through mio, you are forced to spawn two threads.

I'm not sure which other thread you're referring to.

the post I created earlier I am referring to

Perhaps that is a problem you are having that we should tackle.

The good old fashioned way to do this is to use threads.

  1. A thread opens a socket and listens for incoming connections.
  2. When a client connection arrives and is accepted a new thread is spawned to handle that connection.

That is OK if one has a request/response protocol like HTTP, wait for a request, send response, close socket, end the thread.

But what if you want to handle receiving and sending on the clients socket independently? Say the data you send comes from some other source and what is received goes somewhere else. And potentially for a long period of time. Then one need:

  1. Spawn a second thread for the client connection. On receives, and block on recv(), the other sends but blocks on whatever source of the data it is to send.

Today the new kids like to do that using async. I have managed to do it fairly easily using tokio. Conceptually replacing all the "threads" above with async "tasks".

A couple of tricks I had to learn along the way to make this work:

  1. Use tokio's tcp stream split() the tcp stream and give the read and write halves to different tasks.

  2. Combining all this asyn activity with existing sync code is a bit of a problem. You can:

a) You can use tokio's spawn_blocking() to wrap a synchronous blocking call in an async task

b) With a cunning use of mpsc channels you can send messages between asyn and sync code. The trick being that you have to use tokio's async channels in one direction and normal sync channels in the other. This was suggested to me by Alice, who can do a far better job of explaining it than me.

This has all been working very well for some time now. Unfortunately don't have a concise example of all this to post here.

As for debugging, I'm don't know if async is harder or not. I almost never users debuggers, when I have been desperate enough to try they did not help. Debugging threaded code is hard anyway.

Luckily the Rust compiler stops one making a lot of those really hard to find data races and such.

I dont use async after having lots of problems with it. It was nearly impossible to debug and I ended up throwing weeks of effort away.

You are referring to Very mysterious blockage in tokio?

yep that one

I'd be interested to hear what problems you had.

Of course I had headaches figuring all this out well enough to get it working. Mostly sorted by my asking dumb questions here and getting very good hints and tips that made it all fall into place in an understandable manner. Thanks everyone here, by the way.

Mostly that was caused by my not being able to read the documentation and not even knowing what I might be looking for. For example I would never imagined there was tcp stream split().

Debugging for me mostly happens by means of logging info, warnings and errors with the log crate.

See my reply in that other thread.