Skip to content

Commit 440d1b9

Browse files
committed
Make work contract cold paths use locks, hot paths use atomics.
1 parent d84848a commit 440d1b9

File tree

4 files changed

+83
-306
lines changed

4 files changed

+83
-306
lines changed

src/Concurrency/WorkContractGroup.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,15 @@ namespace Concurrency {
206206
ENTROPY_ASSERT(mainThreadSelectingCount == 0, "WorkContractGroup destroyed with threads still in selectForMainThreadExecution");
207207

208208
// Then notify the concurrency provider to remove us from active groups
209+
// CRITICAL: Read provider without holding lock to avoid ABBA deadlock
210+
IConcurrencyProvider* provider = nullptr;
209211
{
210212
std::unique_lock<std::shared_mutex> lock(_concurrencyProviderMutex);
211-
if (_concurrencyProvider) {
212-
_concurrencyProvider->notifyGroupDestroyed(this);
213-
}
213+
provider = _concurrencyProvider;
214+
}
215+
216+
if (provider) {
217+
provider->notifyGroupDestroyed(this);
214218
}
215219
}
216220

src/Concurrency/WorkContractGroup.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <thread>
3030
#include <condition_variable>
3131
#include <mutex>
32+
#include <shared_mutex>
3233
#include <optional>
3334
#include <limits>
3435

@@ -176,9 +177,22 @@ namespace Concurrency {
176177
/**
177178
* @brief Destructor ensures all work is stopped and completed
178179
*
179-
* Calls stop() to prevent new work selection, then wait() to ensure
180-
* all executing work completes. Finally notifies the concurrency provider
181-
* (if any) that the group is being destroyed.
180+
* Follows a strict destruction protocol to prevent deadlocks:
181+
* 1. Calls stop() to prevent new work selection
182+
* 2. Calls wait() to ensure all executing work completes
183+
* 3. Unschedules and releases all remaining contracts
184+
* 4. Reads concurrency provider pointer WITHOUT holding mutex lock
185+
* 5. Calls notifyGroupDestroyed() to inform provider of destruction
186+
*
187+
* CRITICAL: The provider notification is made without holding the group's
188+
* concurrency provider mutex to prevent ABBA deadlock with WorkService.
189+
* Any deviation from this protocol may result in deadlock during destruction.
190+
*
191+
* The provider will then:
192+
* - Remove this group from its internal lists
193+
* - Call setConcurrencyProvider(nullptr) to clear the back-reference
194+
*
195+
* This ensures proper bidirectional cleanup without lock ordering issues.
182196
*/
183197
~WorkContractGroup();
184198

0 commit comments

Comments
 (0)