Skip to content

cli: replace read_stream() with StreamRunner #5024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 1, 2023

Conversation

bastimeyer
Copy link
Member

@bastimeyer bastimeyer commented Dec 12, 2022

Replace read_stream() with a new StreamRunner class, refactor stream reads and output writes, as well as the progress thread data feeding, and move player polling into a separate thread which closes the stream once the player process gets terminated/killed.

This fixes the player polling issue, or rather the detection of its broken pipe while reading the stream, as stream read calls can stall for various reasons, e.g. due to segmented streams or the filtering of stream data which pauses the stream output and disables any timeouts.

  • Implement StreamRunner and PlayerPollThread classes in dedicated streamlink_cli.streamrunner module
  • Remove old streamlink_cli.main.read_stream() implementation
  • Keep the same log messages
  • Make StreamRunner.run() raise OSError on read/write error and catch OSErrors in main module where console.exit() gets called
  • Remove Progress.iter(), as it's not needed anymore
  • Add extensive tests with full code coverage

Resolves #4642
Replaces #4974
Follow-up of #5017

As described in #4642, this re-implements the heart of Streamlink's CLI, namely the main loop where the resolved stream gets read and where data gets written to the output. It is important that there are no issues in this new implementation, so before this gets merged, this needs to be double- and triple-checked. I've taken the time to write extensive tests which cover all code branches of the new implementation, but this doesn't mean that I didn't miss something.

As you can see in the code, there are a couple of TODO comments for stuff that should get changed and fixed in the future. For example, the error messages have not been changed in order to not introduce any breaking changes of the CLI output, which is one of the few parts of the CLI which is considered a "stable" interface for third party applications to use (e.g. the Twitch GUI).

The error handling itself also needs to get fixed at some point. This is basically kept from the old implementation, which simply called console.exit(), but it's been moved to different parts of the main module now where the StreamRunner gets initialized. The entire console.exit() stuff is pretty bad, which I've already commented on a few times, and it should be replaced with proper exception propagation and one single point where the CLI exits on any error, so error handling can be done gracefully and proper tests can be written.

The output implementations (unaffected by this PR) are also bad. The output instance which gets passed to the StreamRunner is either a PlayerOutput, FileOutput, or HTTPServer, but HTTPServer doesn't inherit the base Output class and its interface.


In regards to the new StreamRunner and PlayerPollThread, I've set the polling time to 0.5s, which should be fine.

The stream will get closed as soon as the player polling fails, even if the stream output is paused, which is the main intention of these changes. However, this doesn't mean that Streamlink will terminate immediately, because closing the stream can still take a bit depending on the stream implementation. For example, segmented streams like HLS can still wait until all segment downloads have finished. This needs to be fixed separately, if possible. The PR however is already a major improvement.

Rename `Progress.put()` to `Progress.write()`, to make it consistent
with other output related interfaces:
`PlayerOutput`, `FileOutput` and `HTTPServer`
Replace `read_stream()` with a new `StreamRunner` class, refactor stream
reads and output writes, as well as the progress thread data feeding,
and move player polling into a separate thread which closes the stream
once the player process gets terminated/killed.

This fixes the player polling issue, or rather the detection of its
broken pipe while reading the stream, as stream read calls can stall
for various reasons, e.g. due to segmented streams or the filtering
of stream data which pauses the stream output and disables any timeouts.

- Implement `StreamRunner` and `PlayerPollThread` classes
  in dedicated `streamlink_cli.streamrunner` module
- Remove old `streamlink_cli.main.read_stream()` implementation
- Keep the same log messages
- Make `StreamRunner.run()` raise `OSError` on read/write error and
  catch `OSError`s in main module where `console.exit()` gets called
- Remove `Progress.iter()`, as it's not needed anymore
- Add extensive tests with full code coverage
@back-to back-to merged commit b1c6d8b into streamlink:master Jan 1, 2023
@bastimeyer bastimeyer deleted the cli/streamrunner branch January 1, 2023 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cli: threaded read_stream() for undelayed process termination
2 participants