Skip to content
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

terminal-unix: fix infinite loop when read returns EIO #11805

Closed
wants to merge 1 commit into from

Conversation

nightfriendly
Copy link

This commit fixes #11795.

If parent terminal is closed while mpv is running, read(2) starts returning EIO, resulting in an infinite loop using 100% of the CPU.

This commit makes sure we don't loop needlessly if read(2) returns EIO.

Read this before you submit this pull request:
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md

Reading this link and following the rules will get your pull request reviewed
and merged faster. Nobody wants lazy pull requests.

@nightfriendly nightfriendly changed the title terminal-unix: fix infinite loop when read returs EIO terminal-unix: fix infinite loop when read returns EIO Jun 18, 2023
@@ -420,7 +420,7 @@ static void *terminal_thread(void *ptr)
break;
if (fds[1].revents) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the proper fix would be to fix this check. It should be if (fds[1].revents & POLLIN) and there should be another check if (fds[1].revents & POLLHUP) break; after this block.

This commit fixes mpv-player#11795.

If parent terminal is closed while mpv is running, read(2) starts
returning EIO, resulting in an infinite loop using 100% of the CPU.

This commit makes sure we don't loop needlessly if read(2) returns EIO.
@nightfriendly
Copy link
Author

Thank you very much for your review.

I updated the patch accordingly, but please note I am not very familiar with select/poll. I just want to help!

Copy link
Contributor

@N-R-K N-R-K left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't test the changes, but it looks fine.

Did you test if this fixes the issue for you?

@@ -427,6 +427,9 @@ static void *terminal_thread(void *ptr)
process_input(input_ctx, false);
}
}
if (fds[1].revents & POLLHUP) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mpv doesn't use braces for single line ifs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'd move this check after the other if (r == 0) process_input(...) line. Just in case there was still some input left in the buffer to process, since AFAIK POLLIN and POLLHUP can be true in parallel. E.g when there's still data left in the pipe, but the fd has been closed from the other end.

@N-R-K
Copy link
Contributor

N-R-K commented Jun 18, 2023

Oh and the commit message needs to be changed too. Since this no longer checks for EIO but instead checks for POLLHUP.

Copy link
Contributor

@N-R-K N-R-K left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had some time to test this. Some observations:

  1. I cannot reproduce the issue on linux
  2. Although it looked like poll was being used, in reality, mpv is using this weird polldev shim (which does some funny stuff, probably unrelated to this issue)
  3. The polldev shim, never sets POLLHUP so this fix probably doesn't work.

@N-R-K
Copy link
Contributor

N-R-K commented Jun 27, 2023

I cannot reproduce the issue on linux

Okay, seems like the EIO error happens on linux as well but it is happening periodically, not fast enough to cause 100% cpu usage (which is why I didn't notice it).

Does anyone have any issues with this? Out of all the read errors, EINTR seems to be the only one where we might not want to exit. Makes much more sense to do it this way.

             int retval = read(tty_in, &buf.b[buf.len], BUF_LEN - buf.len);
-            if (!retval || (retval == -1 && (errno == EBADF || errno == EINVAL)))
+            if (!retval || (retval == -1 && errno != EINTR))
                 break; // EOF/closed

P.S: maybe add EAGAIN too. Although the tty_in fd doesn't have non-blocking mode set so EAGAIN shouldn't ever happen, but I guess there isn't any harm in adding it just in case that changes in the future.

N-R-K added a commit to N-R-K/mpv that referenced this pull request Jul 1, 2023
the reason for checking `EBADF|EINVAL` specifically is unknown. but it's
clearly not working as intended since it can cause issues like mpv-player#11795.

instead of checking for "bad errors" check for known "good errors" where
we might not want to break out. this includes:

* EINTR: which can happen if read() is interrupted by some signal.
* EAGAIN: which happens if a non-blocking fd would block. `tty_in` is
  _not_ non-blocking, so EAGAIN should never occur here. but it's added
  just in case that changes in the future.

Fixes: mpv-player#11795
Closes: mpv-player#11805
@sfan5 sfan5 closed this in 2e7fcc5 Jul 8, 2023
@nightfriendly
Copy link
Author

Thank you very much @N-R-K.

I am sorry I was traveling and could not get back to this earlier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

High CPU usage when running in disowned mode
2 participants