UNIX's "shutdown" has very strange semantics. Do you really want to keep UNIX's legacy?
safinaskar opened this issue · 7 comments
I noticed that WASI has well-known UNIX's shutdown
function (in https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/wasi/api.h ).
Unfortunately, shutdown
function has very strange and unintuitive semantics. It can work in two modes: SHUT_WR and SHUT_RD. (Well, there is also SHUT_RDWR, but let's ignore it for now.)
At first sight it seems that these two operations are symmetric. And reading of man page ( https://manpages.debian.org/unstable/manpages-dev/shutdown.2.en.html ) seems to support such understanding.
Unfortunately, this is not true! I discovered (using experiments on Linux) that this is absolutely false. SHUT_WR actually changes state of whole connection: it sends FIN packet and transitions the connection to so-called half-closed state. SHUT_RD does nothing. Simply nothing. It informs local TCP stack (residing in kernel) that starting from this moment all incoming data should be silently ignored. But SHUT_RD doesn't send anything to other side, i. e. it doesn't change connection state in any way! So, these are completely different operations and they are not symmetric.
Okay, what if we call shutdown
with SHUT_RD
and then SHUT_WR
? It seems that we should get sum of effects of SHUT_RD
and SHUT_WR
. But, again, this is not true! In addition to SHUT_RD and SHUT_WR effects we also get this new effect: starting from this moment we send RST if remote side sends us something!
Caveats apply:
- My tests was performed on Linux
- It is possible (but probability is small) that I did my tests wrong
- All tests was performed when the connection is not yet in half-closed state, FIN (and RST) is never send in any direction, and
shutdown
was not called on any side yet
If you want I can send you my test code (in C).
So, what to do? I see two variants:
- Keep wasm's shutdown semantics same as UNIX's. I. e. with all its annoyances. But in this case at very least, please, document very carefully all these UNIX's surprises
- Invent some another, more natural semantics. We want to break free from UNIX legacy, right?
Also, I can write many similar annoyances about many other wasm calls. For example, I can write a lot of similar about read
call and many others
We do want to break free from UNIX legacy, however there are multiple ways one might do this.
One option would be to design a "better BSD sockets", which might fix things like these shutdown
semantics, read
annoyances, and everything else. I think there are some steps we can take in this direction, and I definitely agree that we should document things. However making significant changes to the behavior of shutdown
or read
or other fundamental operations faces is "uphill both ways". Many WASI implementations will be built on top of existing operating system APIs, and are limited to what those operating system APIs can do. And on the other side, many applications and non-WASI network peers are expecting the traditional socket behavior.
And on the other hand, if we really want to break free from UNIX legacy, we should also break free from the idea that BSD sockets are the primary network abstraction.
WASI is actively working on interfaces like wasi-http and even higher-level APIs like wasi-keyvalue which support network communication without exposing any form of socket. There's also been interest in the WASI community in exploring TAPS, which is a new API being designed by the IETF to replace BSD sockets. Perhaps with WASI's virtualizability features, it may eventually be possible to provide a virtual sockets implementation on top of a hypothetical future TAPS API, which would be a path for us to continue to support existing applications while breaking free from sockets at the host level.
Consequently, I think WASI should pursue the sockets interface largely "as is", with improvements and documentation, though without changing fundamental network behavior. At the same time, we should also avoid baking in sockets as the primary abstraction to the degree that UNIX did, and look to other APIs to be the path forward for addressing the fundamental limitations of sockets.
Might want to take a look at how plan9, the proposed Unix successor that was built into inferno did things. There was no berkeley sockets api, rather it was and still is more Unix like than Unix with its virtual /net/ file system in the root directory where applications, programs and utilities would declare a lock on a resource. For example, /net/https/443/DNS-ICAAN:#547 for the internet browser to claim a lock on that resource. Or with modern security practices the firewall would claim the process and the applications that needed to use /net/HTTPS/443/* and every system utility/program/app that wanted to use HTTPS would use the firewalls API through a system of ownership and borrowing. Although if you choose to use Berkeley sockets I would worry about interoperability with non Unix systems that have the Berkeley sockets API but implement it slightly differently or use a different method, like plan 9.
The original question about shutdown
here appears answered. If someone wishes to propose a Plan 9-like network API, please file a new issue!
@sunfishcode , current documentation for shutdown
in tcp.wit
( https://github.com/WebAssembly/wasi-cli/blob/6ae82617096e83e6606047736e84ac397b788631/wit/deps/sockets/tcp.wit#L351 ) is self-contradictory.
It includes phrase "both
: Same effect as receive
& send
combined" and in the same time refers to Linux's man page ( https://man7.org/linux/man-pages/man2/shutdown.2.html ). Either this phrase, either the link should be removed. Let me describe why. I see two possibilities:
- Reference implementation for WASI on Linux implements
shutdown
as thin wrapper aroundshutdown
provided by the system. But then phrase "both
: Same effect asreceive
&send
combined" is wrong. As I said before,SHUT_RDWR
sends RST when any data arrives - Reference implementation for WASI on Linux implements
shutdown
different way from systemshutdown
. But then link to Linux man page is bad idea, because this implies thatshutdown
will work the same way as system'sshutdown
I didn't tested WASI reference implementation, so I don't know which of this variants is true, but either way current tcp.wit
is wrong and should be fixed
@safinaskar Ah, thanks for pointing that out. I think the answer is, we want WASI's shutdown
to be able to implemented as a thin wrapper around the host shutdown
, so it sounds like we should change the spec to reflect that. @badeend does that sound reasonable?
In case it makes a difference: Since this issue was opened, the documentation for shutdown
has changed. The current version reads:
Initiate a graceful shutdown.
receive
: The socket is not expecting to receive any data from
the peer. Theinput-stream
associated with this socket will be
closed. Any data still in the receive queue at time of calling
this method will be discarded.send
: The socket has no more data to send to the peer. Theoutput-stream
associated with this socket will be closed and a FIN packet will be sent.both
: Same effect asreceive
&send
combined.This function is idempotent. Shutting a down a direction more than once
has no effect and returnsok
.
I think this is both the least surprising and the most cross-platform interpretation. If a WASI implementation does not behave this way, I'd consider that a bug. IIRC, all OSes already work this way by default and Linux is the odd duck here in that they basically ignore SHUT_RD. A while ago I opened bytecodealliance/wasmtime#7749 to fix this, but after a few back-and-forths within the PR I've largely forgotten about it :P
It includes phrase "both: Same effect as receive & send combined" and in the same time refers to Linux's man page ( https://man7.org/linux/man-pages/man2/shutdown.2.html ). Either this phrase, either the link should be removed.
The links are for reference only and the documented WASI behavior takes precedence in case of ambiguities.
We could document and warn implementors about Linux' non-standard behavior, though.