3 ms·
The complexity I have in mind is roughly that the TCP state machine isn't quite the cartesian product of two opposed unidirectional state machines, but it could
by tonyg 3y ago
The complexity I have in mind is roughly that the TCP state machine isn't quite the cartesian product of two opposed unidirectional state machines, but it could be. (No proof of this, only a TCP implementation based on the idea. I really must sort the question out rigorously one of these days.)
Re the reliability: the same is true of pipes. You can't assume `mail` will take the message, even with pipes, because, just as with TCP, there's no feedback channel. The disk could have filled up after `mail` took the final bytes from its input, for example.
Your point about application-level acknowledgements is 100% spot on, then, but pipes need application-level acks too, in much the same circumstances as TCP would, for just the same reasons. Even pipes make a distributed system.
Not all protocols need application-level acks, though. In the `echo | mail` example, you do get feedback that the process exited abnormally, but this is feedback to the supervisor (here, the shell), not to the preceding process in the pipeline. The same could (and should) be arranged for a TCP-based link between `echo` and `mail`. The failure-signalling takes on some of the responsibilities than an app-level ack would have discharged. Pipes vs sockets makes no difference.
Your point about the difficulty of constructing the necessary back-channels for app-level acks is also spot on. If one were to reformulate a transport layer in terms of simplex channels, one would want to retain an easy way to configure a pair of them as a single duplex channel. This is as true of pipes as of sockets. A nice design would allow capability-passing as well as regular message sending: then, you'd open an outbound stream to a server, and as your first message, you'd send a capability for the write-end of a matching inbound stream. Modular session establishment. Permits a bunch of other interaction patterns, too, such as session handoff, redirection, continuation-passing, load-balancing etc. Richer topological possibilities even than SCTP and QUIC. If capability-passing was in TCP, it'd have been trivially handled by the NAT, too, so FTP wouldn't have been broken. Ah well, never mind.
All that said, Dan isn't taking aim at TCP-the-protocol here. (He does, elsewhere.) Instead, the focus is the sockets API. The API could easily have involved two unidirectional fds instead of just one, without changing the protocol, or how easy/difficult it is to establish a connection. It'd just expose a read cap and a write cap instead of a single mixed capability. It'd be a small change that'd eliminate a bunch of special-casing. I think he has a good point; it's a shame we can't get there from here...
- rstuart4133 3y ago> The API could easily have involved two unidirectional fds instead of just one, without changing the protocol, or how easy/difficult it is to establish a connection. I can't see how it's an improvement. The pipe code is invariably: pipe(fds); close(fd[0]); /* Close the one we don't need. */ If you don't want one direction of a socket the code is: connect(soc, ...); shutdown(soc, ...); /* Close the one we don't need. */ Dan's complaint is quote "The BSD socket designers introduced a shutdown(fd,1) system call to send FIN through a TCP connection. This is exactly the sort of device-specific garbage that UNIX fds were designed to avoid." However the code for pipes and sockets near identical bar the names. Just to re-enforce how similar they are, if you don't call "close()" for pipes, his core complaint that nothing exits because "the same fd is still open in the consume-data program" happens in the pipe case. This makes me think of hobgoblins and minds.
- tonyg 3y agoThe problem is you can't statically know, in e.g. Dan's `generate-data`, which to call: close() or shutdown(). You have to reflect on stdout to discover what kind of resource you have. So there's a downside to the inconsistency. Is there a corresponding upside I'm not seeing? Cause if not, perhaps all else equal, in which case I'd rather have the static simplification.
- rstuart4133 3y ago> The problem is you can't statically know, in e.g. Dan's `generate-data`, which to call: close() or shutdown(). True. But code like this is invariably a 3 step process - setup the invariants, loop (generate-data), then close down. The steps above happen in the open, which means `generate-data` doesn't need to know what to call. The Dan's pipeline gave is a good example of this as any. It is the shell that does the open step, so it's the thing that does the open, so it creates the pipes and does the close() of the unwanted end. Thus Dan `generate-data` does not need to know whether it is sending over s pipe or TCP connection. In fact bash accepts /dev/tcp/host/port syntax. I presume it already does the required shutdown as part of that of setting it up. Which demonstrates that in real life `generate-data` in indeed blissfully unaware of whether it's a pipe or socket. Finally, notice the open step is always going to be different, so this additional work of setting it up different is not a big imposition. It's has to be different because it has to connect the two ends, and that is going to be different depending on whether they are local or remote.