sindresorhus / ora

Elegant terminal spinner
MIT License
9.08k stars 269 forks source link

Stdin becomes paused at close only when using `StdinDiscarder` with TTY #209

Open clavin opened 2 years ago

clavin commented 2 years ago

When ora uses its StdinDiscarder, stdin will be paused when the discarder is stopped (on all platforms except windows). This is due to a historic, undocumented implementation detail of node:readline, where calling readline.close() causes the input stream (stdin) to be paused. Thus, this propagates up to StdinDiscarder when it calls close on its readline handle.

I feel like this behavior seems like a bug in ora, unintentionally present due to the side-effect from node:readline. It feels inconsistent that stdin would be paused at the very end of ora's lifecycle only on the condition of using a tty and the StdinDiscarder. I would expect stdin to remain unpaused at the end of ora's lifecycle regardless of those conditions.

sindresorhus commented 2 years ago

Reopened because of https://github.com/sindresorhus/ora/issues/211

clavin commented 2 years ago

Just to document it, here's another possible solution I tried that ended up a dead end:

Since readline pauses the input stream it's given, I thought why not try giving it a proxy stream of stdin that we can dispose of at the end of StdinDiscarder's lifetime, i.e. using pipe to flow stdin through a PassThrough stream and providing that as the input to readline.

                   [input proxy]
+-------+         +-------------+
| stdin | ------> | PassThrough |
+-------+         +-------------+
                         |
                         v
                       input
                    +----------+
                    | readline |
                    +----------+
                       output
                         |
                         v
+--------+     +------------------+
| stdout | <-- | BufferListStream |
+--------+     +------------------+

That sort of works, but there's an important caveat: when the proxy stream is discarded, either by unpipeing it from stdin or by calling destroy on it, the upstream stdin can become paused if there are no other pipes attached to it. That is, if you have an on('data', ...) event handler on stdin, stdin will become paused; however, if you are still pipeing stdin to another stream and reading from that, stdin will not become paused. The practical difference between unpipe and destory here is that unpipe will try to pause immediately, whereas destroy will try to pause on the next tick.

Ultimately, I decided this solution was too finicky to contribute as it is not significantly better than just calling resume on stdin in user code.