Skip to content

Commit 0e182ee

Browse files
committed
Merge pull request #112506 from RandomShaper/less_locky_cmd_queue
CommandQueueMT: Reduce contention + Fix race conditions
2 parents 9f5143d + 4ba4558 commit 0e182ee

File tree

3 files changed

+34
-10
lines changed

3 files changed

+34
-10
lines changed

core/templates/command_queue_mt.h

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include "core/typedefs.h"
4040

4141
class CommandQueueMT {
42+
static const size_t MAX_COMMAND_SIZE = 1024;
43+
4244
struct CommandBase {
4345
bool sync = false;
4446
virtual void call() = 0;
@@ -105,6 +107,7 @@ class CommandQueueMT {
105107

106108
static const uint32_t DEFAULT_COMMAND_MEM_SIZE_KB = 64;
107109

110+
bool unique_flusher = false;
108111
BinaryMutex mutex;
109112
LocalVector<uint8_t> command_mem;
110113
ConditionVariable sync_cond_var;
@@ -154,29 +157,46 @@ class CommandQueueMT {
154157
}
155158

156159
void _flush() {
160+
MutexLock lock(mutex);
161+
157162
if (unlikely(flush_read_ptr)) {
158163
// Re-entrant call.
159164
return;
160165
}
161166

162-
MutexLock lock(mutex);
167+
char cmd_backup[MAX_COMMAND_SIZE];
163168

164169
while (flush_read_ptr < command_mem.size()) {
165170
uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr];
166-
flush_read_ptr += 8;
171+
flush_read_ptr += sizeof(uint64_t);
172+
167173
CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
168-
uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(lock);
169-
cmd->call();
170-
WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
174+
175+
// Protect against race condition between this thread
176+
// during the call to the command and other threads potentially
177+
// invalidating the pointer due to reallocs.
178+
memcpy(cmd_backup, (char *)cmd, size);
179+
180+
if (unique_flusher) {
181+
// A single thread will pump; the lock is only needed for the command queue itself.
182+
lock.temp_unlock();
183+
((CommandBase *)cmd_backup)->call();
184+
lock.temp_relock();
185+
} else {
186+
// At least we can unlock during WTP operations.
187+
uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(lock);
188+
((CommandBase *)cmd_backup)->call();
189+
WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id);
190+
}
171191

172192
// Handle potential realloc due to the command and unlock allowance.
173193
cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
174194

175195
if (unlikely(cmd->sync)) {
176196
sync_head++;
177-
lock.~MutexLock(); // Give an opportunity to awaiters right away.
197+
lock.temp_unlock(); // Give an opportunity to awaiters right away.
178198
sync_cond_var.notify_all();
179-
new (&lock) MutexLock(mutex);
199+
lock.temp_relock();
180200
// Handle potential realloc happened during unlock.
181201
cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]);
182202
}
@@ -210,20 +230,23 @@ class CommandQueueMT {
210230
void push(T *p_instance, M p_method, Args &&...p_args) {
211231
// Standard command, no sync.
212232
using CommandType = Command<T, M, false, Args...>;
233+
static_assert(sizeof(CommandType) <= MAX_COMMAND_SIZE);
213234
_push_internal<CommandType, false>(p_instance, p_method, std::forward<Args>(p_args)...);
214235
}
215236

216237
template <typename T, typename M, typename... Args>
217238
void push_and_sync(T *p_instance, M p_method, Args... p_args) {
218239
// Standard command, sync.
219240
using CommandType = Command<T, M, true, Args...>;
241+
static_assert(sizeof(CommandType) <= MAX_COMMAND_SIZE);
220242
_push_internal<CommandType, true>(p_instance, p_method, std::forward<Args>(p_args)...);
221243
}
222244

223245
template <typename T, typename M, typename R, typename... Args>
224246
void push_and_ret(T *p_instance, M p_method, R *r_ret, Args... p_args) {
225247
// Command with return value, sync.
226248
using CommandType = CommandRet<T, M, R, Args...>;
249+
static_assert(sizeof(CommandType) <= MAX_COMMAND_SIZE);
227250
_push_internal<CommandType, true>(p_instance, p_method, r_ret, std::forward<Args>(p_args)...);
228251
}
229252

@@ -252,7 +275,8 @@ class CommandQueueMT {
252275
pump_task_id = p_task_id;
253276
}
254277

255-
CommandQueueMT() {
278+
CommandQueueMT(bool p_unique_flusher = false) :
279+
unique_flusher(p_unique_flusher) {
256280
command_mem.reserve(DEFAULT_COMMAND_MEM_SIZE_KB * 1024);
257281
}
258282
};

modules/betsy/image_compress_betsy.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels);
103103
class BetsyCompressor : public Object {
104104
GDSOFTCLASS(BetsyCompressor, Object);
105105

106-
mutable CommandQueueMT command_queue;
106+
mutable CommandQueueMT command_queue = CommandQueueMT(true);
107107
bool exit = false;
108108
WorkerThreadPool::TaskID task_id = WorkerThreadPool::INVALID_TASK_ID;
109109

servers/rendering/rendering_server_default.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class RenderingServerDefault : public RenderingServer {
7474
uint64_t print_frame_profile_ticks_from = 0;
7575
uint32_t print_frame_profile_frame_count = 0;
7676

77-
mutable CommandQueueMT command_queue;
77+
mutable CommandQueueMT command_queue = CommandQueueMT(true);
7878

7979
Thread::ID server_thread = Thread::MAIN_ID;
8080
WorkerThreadPool::TaskID server_task_id = WorkerThreadPool::INVALID_TASK_ID;

0 commit comments

Comments
 (0)