-
Notifications
You must be signed in to change notification settings - Fork 49
Make helper-thread spawn failures non-fatal #311
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2257,8 +2257,15 @@ | |
| sanitizeStackForVM(vm()); | ||
|
|
||
| Locker locker { *m_threadLock }; | ||
| if (!m_requests.isEmpty()) | ||
| if (!m_requests.isEmpty()) { | ||
| m_threadCondition->notifyOne(locker); | ||
| if (!m_thread->hasUnderlyingThread(locker)) [[unlikely]] { | ||
| // The collector thread couldn't spawn (pthread_create EAGAIN at a | ||
| // pids/thread limit). Take the conn back so the mutator drives the | ||
| // collection; otherwise waitForCollector() would park forever. | ||
| m_worldState.exchangeOr(mutatorHasConnBit); | ||
| } | ||
|
Check failure on line 2267 in Source/JavaScriptCore/heap/Heap.cpp
|
||
|
Comment on lines
+2262
to
+2267
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Re-setting Extended reasoning...What the bug isThe new fallback in Path 1: infinite spin in
|
||
| } | ||
| ParkingLot::unparkAll(&m_worldState); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -366,6 +366,12 @@ void VMTraps::initializeSignals() | |
| if (!Options::usePollingTraps()) { | ||
| ASSERT(Options::useJIT()); | ||
| SignalSender::initializeSignals(); | ||
| // Create the signal-sender WorkQueue (and its backing thread) now, | ||
| // while the process still has thread-budget headroom, instead of on | ||
| // the first fireTrap() which may arrive after workers have consumed | ||
| // every slot of a cgroup pids.max budget. RunLoop::create() cannot | ||
| // recover from a failed Thread::create. | ||
| queue(); | ||
|
Comment on lines
+369
to
+374
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -n 'WorkQueue::create|RunLoop::create|Thread::create' Source/WTF Source/JavaScriptCoreRepository: oven-sh/WebKit Length of output: 6531 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== VMTraps.cpp excerpt ==\n'
sed -n '330,390p' Source/JavaScriptCore/runtime/VMTraps.cpp
printf '\n== WorkQueue.cpp excerpt ==\n'
sed -n '1,240p' Source/WTF/wtf/WorkQueue.cpp
printf '\n== generic WorkQueue implementation ==\n'
sed -n '1,140p' Source/WTF/wtf/generic/WorkQueueGeneric.cpp
printf '\n== RunLoop.cpp excerpt ==\n'
sed -n '1,170p' Source/WTF/wtf/RunLoop.cpp
printf '\n== Threading.cpp create signature area ==\n'
sed -n '300,380p' Source/WTF/wtf/Threading.cppRepository: oven-sh/WebKit Length of output: 18641 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [
Path("Source/JavaScriptCore/runtime/VMTraps.cpp"),
Path("Source/WTF/wtf/WorkQueue.cpp"),
Path("Source/WTF/wtf/generic/WorkQueueGeneric.cpp"),
Path("Source/WTF/wtf/RunLoop.cpp"),
]:
text = p.read_text()
print(f"\n== {p} contains create paths ==")
for i, line in enumerate(text.splitlines(), 1):
if "create(" in line or "Thread::create" in line or "RunLoop::create" in line or "WorkQueue::create" in line:
print(f"{i}: {line}")
PYRepository: oven-sh/WebKit Length of output: 1197 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== WorkQueue / RunLoop fallible APIs ==\n'
rg -n '\btryCreate\b|Failed to create thread|RELEASE_ASSERT_WITH_MESSAGE\(thread' Source/WTF Source/JavaScriptCore
printf '\n== VMTraps uses queue() or signal sender ==\n'
rg -n 'VMTraps::queue|SignalSender|fireTrap\(' Source/JavaScriptCore/runtime/VMTraps.cpp Source/JavaScriptCore/runtimeRepository: oven-sh/WebKit Length of output: 48829 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n 'initializeSignals\(' Source/JavaScriptCore
sed -n '660,760p' Source/JavaScriptCore/runtime/VM.cpp
sed -n '1,220p' Source/JavaScriptCore/runtime/VM.hRepository: oven-sh/WebKit Length of output: 10152 Make VM trap queue creation fallible.
🤖 Prompt for AI Agents |
||
| } | ||
| #endif | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -180,7 +180,7 @@ | |
| break; | ||
| } | ||
|
|
||
| Thread::create( | ||
| RefPtr<Thread> thread = Thread::tryCreate( | ||
| name(), | ||
| [=, this] () { | ||
| if (verbose) | ||
|
|
@@ -245,7 +245,15 @@ | |
| } | ||
| RELEASE_ASSERT(result == WorkResult::Continue); | ||
| } | ||
| }, m_threadType, Thread::defaultQOS, Thread::defaultSchedulingPolicy, stackSpec)->detach(); | ||
| }, m_threadType, Thread::defaultQOS, Thread::defaultSchedulingPolicy, stackSpec); | ||
| if (!thread) { | ||
| // pthread_create/_beginthreadex refused (e.g. EAGAIN at a thread/pids | ||
| // limit). Stay in the no-underlying-thread state so the next notify | ||
| // retries instead of bringing the whole process down. | ||
| m_hasUnderlyingThread = false; | ||
| return; | ||
| } | ||
|
Check failure on line 255 in Source/WTF/wtf/AutomaticThread.cpp
|
||
|
Comment on lines
+249
to
+255
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Swallowing the spawn failure here can hang Extended reasoning...What the bug isThe PR's premise for Code path
When a plan is enqueued in that state, while (m_numberOfActiveThreads < targetNumThreads) {
m_planEnqueued->notifyOne(locker);
m_numberOfActiveThreads++;
}
Why nothing rescues itThe plan sits in
ImpactBefore this PR the process would Step-by-step proof
FixMirror the collector-thread fallback the PR already added in |
||
| thread->detach(); | ||
| } | ||
|
|
||
| void AutomaticThread::threadDidStart() | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -313,6 +313,13 @@ | |||||
| } | ||||||
|
|
||||||
| Ref<Thread> Thread::create(ASCIILiteral name, Function<void()>&& entryPoint, ThreadType threadType, QOS qos, SchedulingPolicy schedulingPolicy, StackAllocationSpecification stackSpec) | ||||||
| { | ||||||
| RefPtr<Thread> thread = tryCreate(name, WTF::move(entryPoint), threadType, qos, schedulingPolicy, stackSpec); | ||||||
| RELEASE_ASSERT_WITH_MESSAGE(thread, "Failed to create thread '%s'", name.characters()); | ||||||
| return thread.releaseNonNull(); | ||||||
| } | ||||||
|
|
||||||
| RefPtr<Thread> Thread::tryCreate(ASCIILiteral name, Function<void()>&& entryPoint, ThreadType threadType, QOS qos, SchedulingPolicy schedulingPolicy, StackAllocationSpecification stackSpec) | ||||||
| { | ||||||
| WTF::initialize(); | ||||||
|
|
||||||
|
|
@@ -327,8 +334,11 @@ | |||||
| if (maybeSize) | ||||||
| stackSpec = StackAllocationSpecification::RequestSize(maybeSize.value()); | ||||||
| } | ||||||
| bool success = thread->establishHandle(context.get(), stackSpec, qos, schedulingPolicy); | ||||||
| RELEASE_ASSERT(success); | ||||||
| if (!thread->establishHandle(context.get(), stackSpec, qos, schedulingPolicy)) { | ||||||
| // Undo the speculative +1 for Thread::entryPoint; the locker's Ref drops the rest. | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Correct the refcount cleanup comment.
Suggested wording- // Undo the speculative +1 for Thread::entryPoint; the locker's Ref drops the rest.
+ // Undo the speculative +1 for Thread::entryPoint; the local Ref drops the rest.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| context->deref(); | ||||||
| return nullptr; | ||||||
| } | ||||||
|
Check failure on line 341 in Source/WTF/wtf/Threading.cpp
|
||||||
|
Comment on lines
+337
to
+341
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 On Windows, when Extended reasoning...What the bug is
if (m_handle != INVALID_HANDLE_VALUE)
CloseHandle(m_handle);When Code path that triggers itIn the new if (!thread->establishHandle(context.get(), stackSpec, qos, schedulingPolicy)) {
context->deref();
return nullptr;
}On Windows, Step-by-step proof
Why existing code doesn't prevent itBefore this PR, step 5 was ImpactThis is UB on the exact scenario the PR is designed to survive on Windows ( FixGive #if OS(WINDOWS)
PlatformThreadHandle m_handle { INVALID_HANDLE_VALUE };
#else
PlatformThreadHandle m_handle { };
#endifor set |
||||||
|
|
||||||
| #if HAVE(STACK_BOUNDS_FOR_NEW_THREAD) | ||||||
| thread->m_stack = StackBounds::newThreadStackBounds(thread->m_handle); | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -134,10 +134,14 @@ class WTF_CAPABILITY("is current") Thread : public ThreadSafeRefCountedAndCanMak | |||||||||
| static dispatch_qos_class_t dispatchQOSClass(QOS); | ||||||||||
| #endif | ||||||||||
|
|
||||||||||
| // Returns nullptr if thread creation failed. | ||||||||||
| // The thread name must be a literal since on some platforms it's passed in to the thread. | ||||||||||
| // Aborts if the OS refuses to create the thread (resource limit, etc.). | ||||||||||
| WTF_EXPORT_PRIVATE static Ref<Thread> create(ASCIILiteral threadName, Function<void()>&&, ThreadType = ThreadType::Unknown, QOS = defaultQOS, SchedulingPolicy = defaultSchedulingPolicy, StackAllocationSpecification = { }); | ||||||||||
|
|
||||||||||
| // Returns nullptr if thread creation failed. The caller still owns entryPoint | ||||||||||
| // resources in that case (the moved Function is destroyed, its captures drop). | ||||||||||
|
Comment on lines
+141
to
+142
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Clarify ownership after failed thread creation.
Suggested wording- // Returns nullptr if thread creation failed. The caller still owns entryPoint
- // resources in that case (the moved Function is destroyed, its captures drop).
+ // Returns nullptr if thread creation failed. The entryPoint is consumed even
+ // on failure; the moved Function and its captures are destroyed.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| WTF_EXPORT_PRIVATE static RefPtr<Thread> tryCreate(ASCIILiteral threadName, Function<void()>&&, ThreadType = ThreadType::Unknown, QOS = defaultQOS, SchedulingPolicy = defaultSchedulingPolicy, StackAllocationSpecification = { }); | ||||||||||
|
|
||||||||||
| // Returns Thread object. | ||||||||||
| static Thread& currentSingleton(); | ||||||||||
|
|
||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Clear the stopped state when reclaiming the connection.
releaseAccessSlow()setsstoppedBitat Lines [2218-2225] before callingfinishRelinquishingConn(). On thread-creation failure, this code restores onlymutatorHasConnBit;acquireAccessSlow()checksstoppedBitfirst and parks, leaving no collector thread available to clear it. The fallback can therefore deadlock the mutator. Restore the connection and undo the stop state established by that handoff before unparking.🤖 Prompt for AI Agents