WebAssembly / WASI

WebAssembly System Interface
Other
4.84k stars 251 forks source link

UNIX's "shutdown" has very strange semantics. Do you really want to keep UNIX's legacy? #547

Closed safinaskar closed 6 months ago

safinaskar commented 1 year ago

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:

If you want I can send you my test code (in C).

So, what to do? I see two variants:

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

sunfishcode commented 1 year ago

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.

RealJosephKnapp commented 10 months ago

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:https://github.com/WebAssembly/WASI/issues/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.

sunfishcode commented 6 months ago

The original question about shutdown here appears answered. If someone wishes to propose a Plan 9-like network API, please file a new issue!

safinaskar commented 6 months ago

@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:

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

sunfishcode commented 6 months ago

@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?

badeend commented 6 months ago

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. The input-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. The output-stream associated with this socket will be closed and a FIN packet will be sent.
  • both: Same effect as receive & send combined.

This function is idempotent. Shutting a down a direction more than once has no effect and returns ok.

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 https://github.com/bytecodealliance/wasmtime/pull/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.

badeend commented 6 months ago

We could document and warn implementors about Linux' non-standard behavior, though.