Skip to content

Disallow blocking on the main thread by default #9579

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 35 commits into from
Oct 31, 2019
Merged

Disallow blocking on the main thread by default #9579

merged 35 commits into from
Oct 31, 2019

Conversation

kripken
Copy link
Member

@kripken kripken commented Oct 5, 2019

If ALLOW_BLOCKING_ON_MAIN_THREAD is not set by the user, throw an error if pthread_join or pthread_cond_wait are called on the main browser thread, which is bad for responsiveness and at risk of deadlocks. If the error is shown it points to our docs, which explain the issue and mention the flag that can allow blocking at your own risk.

@kripken kripken requested a review from dschuff October 5, 2019 00:16
@dschuff
Copy link
Member

dschuff commented Oct 7, 2019

Thinking about this a little more, it might makes sense to get some more eyes/discussion on this issue before we land it, since it is potentially a fairly-disruptive change. Also it might make sense to have our hypothesized try_join feature available before we ask everyone to reconsider how they are managing their threads.

@sbc100
Copy link
Collaborator

sbc100 commented Oct 7, 2019

try_join is real (albeit non-portable) thing already: https://linux.die.net/man/3/pthread_tryjoin_np

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

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

I think this is a great idea.

I'm surprised how many tests rely on ALLOW_BLOCKING_ON_MAIN_THREAD.

Copy link
Member

@dschuff dschuff left a comment

Choose a reason for hiding this comment

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

Other than the part about ALLOW_BLOCKING_ON_MAIN THREAD, there's probably no reason to wait on this documentation though.

It's probably even worth expanding a bit more, to include e.g. to distinguish between the Module thread (or main runtime thread, or ??) vs the browser main thread?

@@ -790,7 +790,16 @@ var LibraryPThread = {
if (canceled == 2) throw 'Canceled!';
},

{{{ USE_LSAN ? 'emscripten_builtin_' : '' }}}pthread_join__deps: ['_cleanup_thread', '_pthread_testcancel_js', 'emscripten_main_thread_process_queued_calls', 'emscripten_futex_wait'],
emscripten_block_on_main_thread: function() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe a nit pick, but I don't really like the name of this function. To me it sounds like this blocks on the main thread, when it actually either aborts of does nothing.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree emscripten_block_on_main_thread is not the best, but I couldn't find a better one - ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure. The only thing I can think of would be emscripten_block_on_main_thread_or_die (in the same vain as functions like malloc_or_die), but I'm not sure if that's actually any better. Either way it's not that big of a deal in my opinion.

@kripken
Copy link
Member Author

kripken commented Oct 14, 2019

Thanks for the feedback, should be addressed in those commits. In particular this adds pthread_tryjoin_fd + docs, and adds docs on the browser vs application main thread etc.

@RReverser
Copy link
Collaborator

RReverser commented Oct 25, 2019

I was actually wondering - rather than disallowing blocking on the main thread and giving an escape hatch in form of a setting that still allows it, could we transform pthread_join and friends via Asyncify instead? This way the main browser thread wouldn't really be blocked, but the code would be still backwards-compatible and properly wait for thread to finish execution.

Such Asyncify transform should also be perfectly compatible with the new Atomics.waitAsync replacement of Atomics.wait which is intended specifically for such usage (asynchronous "blocking").

UPD: I've read a bit further and see you already mentioned similar option in the docs 🤦‍♂️

It really seems preferable to do this automatically IMO both from developer's perspective (no change to the code) and from browser/user perspective (no escape hatch to do real blocking).

@sbc100
Copy link
Collaborator

sbc100 commented Oct 25, 2019

@RReverser I agree that is good solution, but I think it should certainly be opt-in in it, at least by adding -s ASYNCIFY. ASYNCIFY adds a overhead and complexity and I think we want developers to opt into using and know what they are signing up for.

Mentioning this solution in the doc seems fine to me.

@RReverser
Copy link
Collaborator

ASYNCIFY adds a overhead and complexity

I'd argue not as much overhead as blocking the main thread. That is, I agree it should be an opt-in, but I'm suggesting it as a potentially better fallback instead of ALLOW_BLOCKING_ON_MAIN_THREAD, so that either way user doesn't end up blocking the actual main thread.

Mentioning this solution in the doc seems fine to me.

For start it would need to be implemented (AFAIK it's not yet).

@kripken
Copy link
Member Author

kripken commented Oct 28, 2019

@RReverser interesting idea, yeah, Asyncify integration sounds worth looking into.

A slightly-weird idea might even be to create two wasm files, one with Asyncify and one without, and the Asyncify one would only be used on the main thread where it is needed... anyhow, something to think about in followups.

@kripken
Copy link
Member Author

kripken commented Oct 28, 2019

@surma Sounds good, updated to just warn by default. We can consider flipping the flag added here to make it an error later on.

@kripken kripken merged commit 3dc4011 into incoming Oct 31, 2019
@kripken kripken deleted the pj branch October 31, 2019 22:13
@gabrielcuvillier
Copy link
Contributor

gabrielcuvillier commented Nov 27, 2019

@kripken , actually, it looks like the warning only appears when compiling in debug mode (that is, with ASSERTIONS=1) and not in any other modes (when optimizations are set, ASSERTIONS=0).

Maybe the intent would have been to display the message in all cases, otherwise people might never see it !
Reason is simple: most developers will not be really aware of the existence of the ALLOW_BLOCKING_ON_MAIN_THREAD flag because it is unset by default (for good reason of course,to prevent annoying breaks), BUT it is possible developers are never working in debug mode when working with Emscripten - in particular if it is a project that have already been worked out, or simply by developer habit (my case: I only use debugging when compiling on Native platform. On the Web, -O0 -g3 -s ASYNCIFY=1 binaries are simply too big !).

So this new flag might not bring any new information :/

I am just saying this because I was wondering why I did not see the warning, even though I knew I did dangerous things on purpose :)

Just a suggestion

@kripken
Copy link
Member Author

kripken commented Nov 27, 2019

Hmm, that seems odd - the code is

  emscripten_check_blocking_allowed: function() {
#if ASSERTIONS
    assert(ENVIRONMENT_IS_WEB);
    warnOnce('Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
#endif
#if !ALLOW_BLOCKING_ON_MAIN_THREAD
    abort('Blocking on the main thread is not allowed by default. See https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread');
#endif
  },

That should abort in a release build. Is that code not even reached for you?

@kripken
Copy link
Member Author

kripken commented Nov 27, 2019

(also that warnOnce looks redundant, maybe we should remove it...)

@gabrielcuvillier
Copy link
Contributor

No, for some reason that is not reached! So maybe there is a bug (?)

In my code, ASSERTIONS are turned off (due to -O2/-Os build), and ALLOW_BLOCKING_ON_MAIN_THREADS is not set (and so, according to settings.js, it is set to "1").
When doing some pthread_join from browser thread, I don't have any warning message nor an abort. It is only if I add ALLOW_BLOCKING_ON_MAIN_THREADS=0 that I have the abort.

But this behavior looks like to be consistent with the code. If ASSERTIONS=0, then no warning messages, if ALLOW_BLOCKING_ON_MAIN_THREAD=1, then no abort.

Or I am missing something ?

@kripken
Copy link
Member Author

kripken commented Nov 27, 2019

Oh, no, that is the intended behavior: by default we do allow blocking (we decided it was too disruptive to disallow by default). And also by default we do not show extra warnings when ASSERTIONS are off (in production builds people don't want those, usually).

@gabrielcuvillier
Copy link
Contributor

gabrielcuvillier commented Nov 27, 2019

Ok, got it. Then that's what my suggestion was about: to enable the warning even on production builds (so that developers not working in Debug have an opportunity to see it at least once), but still letting them disabling the warning if they judge it is OK for their use case. So I imagined having the default value of the flag to be something like =2 (show the warning in all cases, even on production, but do not abort).

Well, nothing dramatic, I can live without this :)

@kripken
Copy link
Member Author

kripken commented Nov 27, 2019

Maybe we should have different levels of ASSERTIONS, yeah... but in general, I think it's a good idea to test with ASSERTIONS builds, even if not with actual debug builds. Those builds are almost 100% optimized, but may show useful warnings and errors. Perhaps we should also document this some more...

@gabrielcuvillier
Copy link
Contributor

Ok, that's right. I'll now enable emscripten ASSERTIONS on non-debug builds, only disabling them for production. Definitively looks like to be good practice.

belraquib pushed a commit to belraquib/emscripten that referenced this pull request Dec 23, 2020
Add a new ALLOW_BLOCKING_ON_MAIN_THREAD option, set to
1 by default. If unset, it will throw when blocking on the main thread.
If 1 as in the default case, we warn in the console about possible
issues and link to the docs about that.

This ignores things like mutexes which are usually brief, and checks
for pthread_join or pthread_cond_wait operations, which tend to be
longer and run the risk of Web-specific proxying-related deadlocks.

Add pthread_tryjoin_np which does a non-blocking join, which always
works on the main thread.

Improve the docs in this area.
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.

7 participants