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

Return immediately from run_app on web #4165

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

madsmtm
Copy link
Member

@madsmtm madsmtm commented Mar 17, 2025

Builds upon #4149.

Returning immediately from EventLoop::run_app on the web avoids using JavaScript exceptions, which is a huge hack that has compatibility issues, and doesn't work with the Exception Handling Proposal for WebAssembly.

This needs the application handler passed to run_app to be 'static, but that works better on iOS too anyhow (since you can't accidentally forget to pass in state that then wouldn't be dropped when terminating). This effectively reverts the decision in #3006, CC @kchibisov, do you recall if there was a deep motivation for doing that?

Since spawn_app (added in #2208) is now no longer necessary, I've removed it. This means that all the examples should work properly on web again.

  • Tested on all platforms changed
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality

Sorry, something went wrong.

@madsmtm madsmtm added S - enhancement Wouldn't this be the coolest? S - api Design and usability DS - ios DS - web labels Mar 17, 2025
@kchibisov
Copy link
Member

I think they worked properly as well? Just we had a hack to hold the exit? In general, the point was that you might have a code that does cleanup after the exit in a cross platform manner, and thus, doing an instant return doesn't make much sense from a cross platform behavior.

The iOS model is acceptable, since you close the app anyway after so. In general, I'm leaning towards special backends just not being a part of the regular run facility at all, so the difference is clearly stated for them.

@madsmtm
Copy link
Member Author

madsmtm commented Mar 17, 2025

I think they worked properly as well? Just we had a hack to hold the exit?

The hack was "throw an exception and cross fingers that Rust doesn't see it and run destructors". Not really what I'd call "worked properly", rather "terribly UB but worked because WASM doesn't yet support exceptions".

In general, the point was that you might have a code that does cleanup after the exit in a cross platform manner, and thus, doing an instant return doesn't make much sense from a cross platform behavior.

On the web and iOS currently, having cleanup after run_app is wrong. If using spawn_app, having cleanup after is even more wrong. If we want to provide a cross-platform API here, then the user just shouldn't do anything after run_app.

The iOS model is acceptable, since you close the app anyway after so. In general, I'm leaning towards special backends just not being a part of the regular run facility at all, so the difference is clearly stated for them.

My problem is that example code suffers when we do not provide a single API that everyone can reliably use (Android is special in that it cannot be a binary, but the others do not need to suffer from this). An alternative would be to provide EventLoop::run_or_spawn_app, but that seemed unnecessary to me.

Base automatically changed from madsmtm/drop-on-exit to master March 17, 2025 09:56
@madsmtm madsmtm marked this pull request as ready for review March 17, 2025 09:56
This avoids using JavaScript exceptions to support `EventLoop::run_app`
on the web, which is a huge hack, and doesn't work with the Exception
Handling Proposal for WebAssembly:
https://github.com/WebAssembly/exception-handling

This needs the application handler passed to `run_app` to be `'static`,
but that works better on iOS too anyhow (since you can't accidentally
forget to pass in state that then wouldn't be dropped when terminating).
@madsmtm madsmtm force-pushed the madsmtm/run-app-static branch from ca7e746 to 7cbc584 Compare March 17, 2025 09:56
@kchibisov
Copy link
Member

On the web and iOS currently, having cleanup after run_app is wrong. If using spawn_app, having cleanup after is even more wrong. If we want to provide a cross-platform API here, then the user just shouldn't do anything after run_app.

Yeah, but those targets are rather special, so I don't see an issue with them being treated via their own API. Web needs special code anyway to setup canvas and such. We may change it to the way you suggest, but I'm just worrying that it could be more surprising, since I'm already not much in favor that semantics do differ.

@madsmtm
Copy link
Member Author

madsmtm commented Mar 17, 2025

An alternative would be to provide a winit_main!(|| App::default()); macro that initializes the application and runs the event loop under default settings. This would probably also be a bit nicer on Android.


As a few data points, Bevy is already effectively merging run_app and spawn_app, so is Iced. Masonry/Xilem isn't using spawn_app, though they do support web (so they get the worse behaviour), same with ggez. eframe uses just run_app, but their web impl bypasses Winit anyhow.

My point is that all of these examples would benefit from this PR.

@madsmtm
Copy link
Member Author

madsmtm commented Mar 17, 2025

I'm just worrying that it could be more surprising, since I'm already not much in favor that semantics do differ.

I do somewhat agree here, the differing semantics are confusing (I've tried to remediate this with better documentation), though I guess my point is that to most users it probably won't matter (either they only support desktop, and then they don't care, or they also support web, in which case they'll have tested their app there and seen that it does indeed work as they expect).

Copy link
Member

@daxpedda daxpedda left a comment

Choose a reason for hiding this comment

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

I don't like this solution at all, but I think its the best we can do right now.
Thank you for doing this!

@@ -194,6 +194,8 @@ changelog entry.
- Removed `KeyEventExtModifierSupplement`, and made the fields `text_with_all_modifiers` and
`key_without_modifiers` public on `KeyEvent` instead.
- Move `window::Fullscreen` to `monitor::Fullscreen`.
- On web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- On web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.
- On Web, avoid throwing an exception in `EventLoop::run_app`, instead preferring to return to the caller.

if self.0.event_loop_recreation.get() {
crate::event_loop::EventLoopBuilder::allow_event_loop_recreation();
}
crate::event_loop::EventLoopBuilder::allow_event_loop_recreation();
Copy link
Member

@daxpedda daxpedda Mar 27, 2025

Choose a reason for hiding this comment

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

We should mention in the changelog that event loop recreation is now always allowed on Web.

@madsmtm
Copy link
Member Author

madsmtm commented Mar 28, 2025

I don't like this solution at all, but I think its the best we can do right now

Wondering, in the ideal world, how would you like Winit's cross-platform entry point to be?

@daxpedda
Copy link
Member

I don't like this solution at all, but I think its the best we can do right now

Wondering, in the ideal world, how would you like Winit's cross-platform entry point to be?

So in the future, with the stack-switching proposal, Web could actually block and we could have a returning function like with every other backend except iOS.

Because of iOS not able to return after the run_app() function, I believe the only truly cross-platform API we ever had was the non-returning one, which we removed in #2767 because of issues around Android (#2709). So my preference is actually to go back to that. We have briefly discussed this in today's meeting but couldn't come to a conclusion without an Android expert.

If we can't have a cross-platform API that behaves the same on every platform, my next preference is to just not have a cross-platform API. One argument against that is that users often just ignore the differences like this:

#[cfg(not(target_family = "wasm"))]
event_loop.run_app(App::default())?;
#[cfg(target_family = "wasm")]
event_loop.spawn_app(App::default());

So we agreed that if we can't find a truly cross-platform API, we will go ahead with this PR's proposal.

@madsmtm
Copy link
Member Author

madsmtm commented Mar 28, 2025

To expand on iOS: The only public API that Apple provide is UIApplicationMain.

I can think of several workarounds to return to the user's code anyhow upon termination, but all of them have costs that I don't think Winit should pay.

  1. Use private APIs. This may get the user's app rejected from being published to the App Store, and while that can probably be worked around with dlsym, it is brittle anyhow since, well, the APIs are private, so Apple might change them in the future.
  2. Panic inside on termination, and catch that panic outside. Haven't tested, might work depending on internals in how UIApplicationMain deals with non-Objective-C, non-C++ exceptions. Also dependent on Rust's unwind handling details.
  • Won't play nicely if UIKit expects to run code after invoking the user's termination handlers (e.g. the application could easily be marked by the OS as "crashed", and end up sending a report to the developer. I'm unsure here though).
  1. setjmp before UIApplicationMain, longjmp on termination. Same issues as above with code after termination, also won't run Apple's destructors.
  2. Maybe longjmp inside an exit signal handler? Seems very UB-prone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DS - ios DS - web S - api Design and usability S - enhancement Wouldn't this be the coolest?
Development

Successfully merging this pull request may close these issues.

None yet

3 participants