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

Capture emulator stdout during startup #1687

Merged
merged 2 commits into from
Mar 14, 2024

Conversation

rmartin16
Copy link
Member

@rmartin16 rmartin16 commented Mar 8, 2024

Changes

  • An attempt to resolve Android emulator does not open until CTRL+C is pressed during "Starting emulator..." #1573
  • One user in Discord has reported that reading stdout resolves the issue
  • Introduces PopenOutputStreamer for encapsulating the output stream Thread and adds the ability to capture instead of print output
  • Adds Subprocess.stream_output_non_blocking() alongside the existing Subprocess.steam_output() to facilitate callers streaming (and conditionally capturing output) while doing other tasks

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

@rmartin16 rmartin16 force-pushed the yield-streaming branch 2 times, most recently from 16c45fe to ca860db Compare March 8, 2024 19:36
@rmartin16 rmartin16 changed the title [POC] Capture emulator stdout during startup Capture emulator stdout during startup Mar 8, 2024
@rmartin16 rmartin16 force-pushed the yield-streaming branch 6 times, most recently from 4549cb9 to 64023bf Compare March 13, 2024 15:31
@rmartin16 rmartin16 marked this pull request as ready for review March 13, 2024 15:56
- Capture Android emulator output during its startup
Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

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

Broadly makes sense (I think...); a couple of nitpicks inline, but on the whole this looks like a good refactoring of a complex part of the streaming code.

The only other issue I noticed is a weird one - the code clearly has 100% coverage, but I can't see how the capture_output = False case is being exercised. I'm guessing it's being tested implicitly by another test somewhere, but given it's at the core of how this works, it would be worth having an explicit test.

To make sure I'm understanding what is going on here - the key to the problem is that the old Popen usage wasn't ever invoking readline(), which apparently causes Windows to lock up and not produce output. The code now always uses a background thread to poll and collect stdout (not just for the stream_output() case). The Android usage previously only used communicate() to gather output in the case of an error, and otherwise ignored output. The new implementation does continuous readline() polling to get output as it is generated, which should (hopefully 🤞) unblock Windows.

Have I got that right?

@@ -1412,6 +1410,8 @@ def start_emulator(
self.tools.logger.info(general_error_msg)

raise
finally:
emulator_streamer.stop_flag.set()
Copy link
Member

Choose a reason for hiding this comment

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

I'd be inclined to wrap this as a streamer.stop() method (or similar), rather than reaching into the internal implementation so if we ever have more complex "kill the streamer" logic, we don't miss anything that is required.

Copy link
Member Author

Choose a reason for hiding this comment

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

That makes sense; although, I'm inclined to call it streamer.request_stop() because readline() is blocking and as such, we can't guarantee setting this flag will actually ever cause the thread to exit (....you know, if its stuck in readline()).

Copy link
Member

Choose a reason for hiding this comment

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

Sure the exact name doesn't really concern me, it's more the conceptual wrapping of the behavior, rather than reaching into internals. Also stop() could easily be confused for a method on the core thread API (it doesn't AFAICT, but stop() would be an obvious conceptual match for run()... but it won't behave that way).

Copy link
Member Author

Choose a reason for hiding this comment

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

RE: stopping threads

I always loved this blog post from raymond chen....although, he seemingly removed it...

https://web.archive.org/web/20190108101649/https://blogs.msdn.microsoft.com/oldnewthing/20150814-00/?p=91811

:param label: A description of the content being streamed; used for to provide
context in logging messages.
:param popen_process: A running Popen process with output to print
:param capture_output:
Copy link
Member

Choose a reason for hiding this comment

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

Missing a description here

src/briefcase/integrations/subprocess.py Show resolved Hide resolved
src/briefcase/integrations/android_sdk.py Show resolved Hide resolved
@rmartin16
Copy link
Member Author

The only other issue I noticed is a weird one - the code clearly has 100% coverage, but I can't see how the capture_output = False case is being exercised. I'm guessing it's being tested implicitly by another test somewhere, but given it's at the core of how this works, it would be worth having an explicit test.

Would you consider the test_output() test in tests/integrations/subprocess/test_PopenOutputStreamer.py to cover this case? Or maybe something more?

To make sure I'm understanding what is going on here - the key to the problem is that the old Popen usage wasn't ever invoking readline(), which apparently causes Windows to lock up and not produce output. The code now always uses a background thread to poll and collect stdout (not just for the stream_output() case). The Android usage previously only used communicate() to gather output in the case of an error, and otherwise ignored output. The new implementation does continuous readline() polling to get output as it is generated, which should (hopefully 🤞) unblock Windows.

Have I got that right?

Right....the theory is if we read stdout from the emulator, it'll stop blocking it from properly opening for users.

More broadly, this provides the option of creating a Popen and instead of only being able to call Subprocess.stream_output() to read stdout while blocking, there's now Subprocess.stream_output_non_blocking() to (obviously) not block while reading stdout.

@freakboy3742
Copy link
Member

The only other issue I noticed is a weird one - the code clearly has 100% coverage, but I can't see how the capture_output = False case is being exercised. I'm guessing it's being tested implicitly by another test somewhere, but given it's at the core of how this works, it would be worth having an explicit test.

Would you consider the test_output() test in tests/integrations/subprocess/test_PopenOutputStreamer.py to cover this case? Or maybe something more?

Ah - that was the case I was missing. It's relying on the default value of the argument.

Have I got that right?

Right....the theory is if we read stdout from the emulator, it'll stop blocking it from properly opening for users.

👍

More broadly, this provides the option of creating a Popen and instead of only being able to call Subprocess.stream_output() to read stdout while blocking, there's now Subprocess.stream_output_non_blocking() to (obviously) not block while reading stdout.

Yeah - that's a nice API addition. Not sure if we'll have any use for it, but it's a nice club to have in the bag just in case, and the rest of the refactoring around adding that API are all nice improvements as well.

Copy link
Member

@freakboy3742 freakboy3742 left a comment

Choose a reason for hiding this comment

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

All looks good... now to cross our fingers and hope it actually fixes the windows emulator lockup problem 🤞

@freakboy3742 freakboy3742 merged commit 622d9b8 into beeware:main Mar 14, 2024
51 checks passed
@rmartin16 rmartin16 deleted the yield-streaming branch March 14, 2024 05:35
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.

Android emulator does not open until CTRL+C is pressed during "Starting emulator..."
2 participants