Repository navigation
Conversation
…chedules work clearQueues() destroyed the queued tasks while holding schedulingMutex_. If a task's callback captured an object whose destructor schedules a task, scheduleTask() tried to lock the same non-recursive mutex on the same thread and the JS thread deadlocked. This happens when a task throws and handleTaskError() clears the queues. Swap the queue out under the lock and destroy the drained tasks after the lock is released. Fixes react#59204. The fix was proposed by @fregster in the issue. Co-authored-by: fregster <630468+fregster@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hi @hcbylmz! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D126662928. |
|
This pull request was successfully merged by @hcbylmz in 3181abc When will my fix make it into a release? | How to file a pick request? |
Summary:
Fixes #59204.
RuntimeScheduler_Modern::clearQueues()destroys the queued tasks while it holdsschedulingMutex_. A task owns its callback, so this also destroys everything the callback captured. If one of those captures schedules a task in its destructor,scheduleTask()tries to lock the same non-recursive mutex on the same thread, and the JS thread deadlocks.clearQueues()runs fromhandleTaskError(), so a single uncaught error in a task is enough to trigger it when another queued task has such a capture.The fix is the one @fregster proposed in the issue: swap the queue out under the lock, and let the drained tasks be destroyed after the lock is released. A task scheduled from one of those destructors goes into the now-empty queue and still runs.
Changelog:
[GENERAL] [FIXED] - Fix deadlock in
RuntimeSchedulerwhen a task destructor schedules work while the queues are cleared after an errorTest Plan:
Added
clearQueuesAllowsTaskDestructorsToScheduleTaskstoRuntimeSchedulerTest. It queues a task whose callback captures an object that schedules a task when destroyed, then runs a task that throws.std::shared_mutex::lockcalled fromRuntimeScheduler_Modern::scheduleTask, called fromTask::~Task, called fromclearQueues→handleTaskError.RuntimeSchedulerTesttests pass.I ran the tests on macOS (arm64) with a standalone CMake + googletest build of
RuntimeSchedulerTest.cppand its dependencies, linked against the Hermes macOS framework.🤖 Generated with Claude Code