smoltcp-rs / smoltcp

a smol tcp/ip stack
BSD Zero Clause License
3.73k stars 414 forks source link

Split reader and writer #967

Open Ddystopia opened 1 month ago

Ddystopia commented 1 month ago

Hello,

It is common in async rust to have split method on sockets to get read and write halves. For example, tokio. While reading the docs, I didn't find anything like this. While reading your code for tcp::Socket read and write operations, I didn't find anything that would block it.

Embassy TcpSocket wraps your tcp::Socket and provides that split api, but they are using RefCell.

There is also picoserve, embedded web server, that requires separate Read and Write halves.

Do you have any particular reason to not have such useful API? If it is just because nobody implemented it, I can do it.

Dirbaio commented 1 month ago

in smoltcp the socket needs to be owned by the SocketSet. Your own code only owns the handle, every time you need to do something with the socket you have to use the handle to look it up in the SocketSet and get temporary access to it.

Given this, I'm not sure what "split" in smoltcp itself would do. You can already clone the handle and access it from multiple places in your own code, for example for reading in some place and writing in another.

Ddystopia commented 1 month ago

SocketSet can only return shared reference to the socket if you have a shared reference to the SocketSet. But to read or write you need a mutable reference to the socket. So you must have mutable reference to the SocketSet to read or write. That way I'm not sure how to concurrently read and write from different locations without something like refcell.

Am I reading the docs correctly?

As I've seen in source code (tcp), read is not mutating any state relating to writing, and writing is not mutating any state related to reading. There are separate tx_buffer and rx_buffer, and separate wakers.

Dirbaio commented 1 month ago

But to read or write you need a mutable reference to the socket. So you must have mutable reference to the SocketSet to read or write. That way I'm not sure how to concurrently read and write from different locations without something like refcell.

You can either write your application as a single giant main loop like the examples do, so there's no need for shared mutable access at all. Or yes, you can put the SocketSet in a RefCell.

As I've seen in source code (tcp), read is not mutating any state relating to writing, and writing is not mutating any state related to reading. There are separate tx_buffer and rx_buffer, and separate wakers.

But processing packets for the socket (when you call Interface::poll()) requires full access to the entire TCP state, both the read and write half. So even if we had split you'd still need a RefCell somewhere to share the state between the read half and the interface, and between the write half and the interface.

In a network stack you'll always have some "background task" processing packets, so you always need some mechanism for "sharing" state between the background task and user code. In desktop Linux the kernel does it for you, in embassy-net it does the RefCell for you, in smoltcp this is intentionally left to the user.

If the user has to do their own sharing they can already do their own split.