top | item 46659635

TTY and Buffering

46 points| mattrighetti | 1 month ago |mattrighetti.com

17 comments

order

lapsed_lisper|1 month ago

A while ago I stumbled across a technique for improving stream buffering that I wish more I/O library implementors knew about. Korn and Vo's sfio library (circa 1991) had a feature called "pooling", whereby distinct streams could be linked together. Any read or write operation to any stream in a pool implicitly synchronized all the other streams in the pool first. This way, when stdio and stderr were pooled, which was the default when both went to ttys, a write on stderr implicitly flushed stdout. I've implemented this feature for myself a couple times; it's fairly easy to do and basically eliminates the need to explicitly flush streams in client code.

Citation: https://archive.org/details/1991-proceedings-tech-conference... but note that the explanation of stream pools there is a little less precise and more general than really necessary. I believe that later versions of sfio simplified things somewhat, though I could be wrong. (I find their code fairly hard to read.)

Anyhow, ISTM a missed opportunity when new languages that don't actually use libc's routines for something reinvent POSIX's clunkier aspects.

Joker_vD|1 month ago

There are still I/O libraries that play with read/write buffers really fast & dirty, which C standard explicitly allows for with its "However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file" wording.

teddyh|1 month ago

> Surprisingly, Rust, as of now, uses line buffering for both TTYs and non-TTYs.

> The FIXME comment shows the Rust team acknowledges that ideally they should check if something is executed in TTYs or not and use LineWriter or BufWriter accordingly, but I guess this was not on their priority list.

This does not inspire confidence.

dwattttt|1 month ago

That's not forced behaviour. If you want to do something more interesting, you'd use the raw/unsynchronised handles:

  /// The returned handle has no external synchronization or buffering layered on top.
  const fn stdout_raw() -> StdoutRaw;

nanolith|1 month ago

In libc, you can use setvbuf to change the buffering mode.

amelius|1 month ago

How would a modern OS implement this?

geocar|1 month ago

> How would a modern OS implement this?

fwrite only buffers because write is slow.

make it so write isn't slow and you don't need userspace buffering!

Veserv|1 month ago

You do not need any OS changes, you just need a print library that does buffering correctly.

Buffering should basically always be: “Work or Time” based, either you buffered enough or enough time has passed. This is because you buffer when per-element latency starts bottlenecking your throughput.

If you have so little data that your throughput is not getting limited, then you should be flushing.

pocksuppet|1 month ago

Probably by not assuming terminals and byte streams any more. Terminal-by-default is a 20th-century-ism. Now you have screens with pixels. Without stdout, no need to know if stdout is a terminal.