WebAssembly / wasi-sockets

WASI API proposal for managing sockets
Other
248 stars 22 forks source link

Subscribing to accept #91

Closed guybedford closed 10 months ago

guybedford commented 10 months ago

Is it expected that accept can be subscribed to?

If so I'd value some clarification on what the state of the subscribe pollable should be at the end of finishListen? Resolved or unresolved?

guybedford commented 10 months ago

Here is my interpretation of the state machine, please let me know if this sounds correct:

Listen states:

Is it valid to ever implement accept as blocking? Or should the subscribe pollable always be a requirement for a fully async implementation (the initial implementation in JCO was naively blocking).

badeend commented 10 months ago

Just to be sure; did you see the Asynchronous APIs section in the readme?


LISTEN_FINISH: Listen finish is called. unresolved pollable

It might be the terminology messing with me, but there is no distinct LISTEN_FINISH phase that needs to be awaited. After LISTEN_START (and also LISTEN_READY in your case), there is only "LISTENING". And only when a socket is Listening, you can call accept.

Is it valid to ever implement accept as blocking?

No. If accept were to block, guest programs would run into problems when for example they'd like to accept connections on multiple ports.

Is it expected that accept can be subscribed to?

Yes :) And just to be clear; accept itself can't be "subscribed" to. Poll-ability is a property of the socket itself, not any specific operation. My mental model of the situation is more like this:

interface Pollable { // NOT a resource on its own
    ready(): boolean;
    // ...
}

class TcpSocket implements Pollable {
    #activeOperation: Promise | null;

    startConnect(...) {
        if (#activeOperation !== null) { err }

        #activeOperation = nativeSocket.connect(...);
    }

    finishConnect(...) {
        if (#activeOperation === null || /*#activeOperation !== a connect operation */) { err }

        if (#activeOperation.isPending) {
            return WouldBlock;
        }

        #activeOperation = null;
    }

    ready() {
        return #activeOperation === null || #activeOperation.isPending == false;
    }
}

Hope that helps.

guybedford commented 10 months ago

Thanks for the clarifications here again, the example helps a lot. I've had to onboard into the implementation details at a later stage hence my beginner questions, but I hope that the feedback does at least help to understand implementation specification knowledge gaps from the perspective of a novice.

Again, I'm glad I asked the basic question - since bind is a synchronous operation we implemented it in the finishBind method. But it seems like connect should be implemented in the startConnect method not in finishConnect as we had done. I will make this change in JCO as well.

guybedford commented 10 months ago

I do think there needs to be an explicit LISTEN_FINISH at least from the perspective of the pollable state machine before LISTENING, because the pollable must be unresolved in LISTENING not resolved. Otherwise how could the pollable transition back into an unresolved state for accepts?

Edit: The short version here being that calling finishConnect resets the pollable to an unready state, but does the same apply to other calls like finishBind? Is the pollable in a ready state after that call? This is the distinction I'm seeking clarification on in the initial issue.

guybedford commented 10 months ago

From discussions offline I wanted to summarize the answer here:

guybedford commented 10 months ago

After discussing this further with @elliottt and also looking at the Wasmtime implementation, we came to the conclusion that what I described in https://github.com/WebAssembly/wasi-sockets/issues/91#issuecomment-1887625192 might not actually be what is happening in Wasmtime. Instead it appears like Wasmtime is:

Still not 100% sure either way, I'm just trying to get this one completely clear for implementation work.