Skip to content

Commit 9c01821

Browse files
committed
Add support for reverse callbacks to ThreadPool
1 parent 5b4bb8c commit 9c01821

2 files changed

Lines changed: 93 additions & 34 deletions

File tree

‎generate/templates/manual/include/thread_pool.h‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@ class ThreadPool {
88
typedef void (*Callback) (void *);
99
struct Work {
1010
Callback workCallback;
11-
Callback loopCallback;
11+
Callback completionCallback;
1212
void *data;
1313

14-
Work(Callback workCallback, Callback loopCallback, void *data)
15-
: workCallback(workCallback), loopCallback(loopCallback), data(data) {
14+
Work(Callback workCallback, Callback completionCallback, void *data)
15+
: workCallback(workCallback), completionCallback(completionCallback), data(data) {
16+
}
17+
};
18+
19+
struct ReverseCall {
20+
Callback reverseCallback;
21+
void *data;
22+
23+
ReverseCall(Callback reverseCallback, void *data)
24+
: reverseCallback(reverseCallback), data(data) {
1625
}
1726
};
1827

@@ -23,14 +32,21 @@ class ThreadPool {
2332
int workInProgressCount;
2433

2534
// completion callbacks to be performed on the loop
26-
std::queue<Work> loopQueue;
27-
uv_mutex_t loopMutex;
28-
uv_async_t loopAsync;
35+
std::queue<Work> completionQueue;
36+
uv_mutex_t completionMutex;
37+
uv_async_t completionAsync;
38+
39+
// async callback made from the threadpool, executed in the loop
40+
std::queue<ReverseCall> reverseQueue;
41+
uv_mutex_t reverseMutex;
42+
uv_async_t reverseAsync;
2943

3044
static void RunEventQueue(void *threadPool);
3145
void RunEventQueue();
32-
static void RunLoopCallbacks(uv_async_t* handle);
33-
void RunLoopCallbacks();
46+
static void RunCompletionCallbacks(uv_async_t* handle);
47+
void RunCompletionCallbacks();
48+
static void RunReverseCallbacks(uv_async_t *handle);
49+
void RunReverseCallbacks();
3450
public:
3551
// Initializes thread pool and spins up the requested number of threads
3652
// The provided loop will be used for completion callbacks, whenever
@@ -39,7 +55,10 @@ class ThreadPool {
3955
// Queues work on the thread pool, followed by completion call scheduled
4056
// on the loop provided in the constructor.
4157
// QueueWork should be called on the loop provided in the constructor.
42-
void QueueWork(Callback workCallback, Callback loopCallback, void *data);
58+
void QueueWork(Callback workCallback, Callback completionCallback, void *data);
59+
// Queues a callback on the loop provided in the constructor
60+
// these block the calling thread's execution until the callback completes
61+
void ExecuteReverseCallback(Callback reverseCallback, void *data);
4362
};
4463

4564
#endif

‎generate/templates/manual/src/thread_pool.cc‎

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) {
44
uv_mutex_init(&workMutex);
55
uv_sem_init(&workSemaphore, 0);
66

7-
uv_async_init(loop, &loopAsync, RunLoopCallbacks);
8-
loopAsync.data = this;
9-
uv_unref((uv_handle_t *)&loopAsync);
10-
uv_mutex_init(&loopMutex);
7+
uv_async_init(loop, &completionAsync, RunCompletionCallbacks);
8+
completionAsync.data = this;
9+
uv_unref((uv_handle_t *)&completionAsync);
10+
uv_mutex_init(&completionMutex);
11+
12+
uv_async_init(loop, &reverseAsync, RunReverseCallbacks);
13+
reverseAsync.data = this;
14+
uv_mutex_init(&reverseMutex);
1115

1216
workInProgressCount = 0;
1317

@@ -17,17 +21,31 @@ ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) {
1721
}
1822
}
1923

20-
void ThreadPool::QueueWork(Callback workCallback, Callback loopCallback, void *data) {
24+
void ThreadPool::QueueWork(Callback workCallback, Callback completionCallback, void *data) {
2125
uv_mutex_lock(&workMutex);
2226
// there is work on the thread pool - reference the handle so
2327
// node doesn't terminate
24-
uv_ref((uv_handle_t *)&loopAsync);
25-
workQueue.push(Work(workCallback, loopCallback, data));
28+
uv_ref((uv_handle_t *)&completionAsync);
29+
workQueue.push(Work(workCallback, completionCallback, data));
2630
workInProgressCount++;
2731
uv_mutex_unlock(&workMutex);
2832
uv_sem_post(&workSemaphore);
2933
}
3034

35+
void ThreadPool::ExecuteReverseCallback(Callback reverseCallback, void *data) {
36+
// push the callback into the queue
37+
uv_mutex_lock(&reverseMutex);
38+
ReverseCall reverseCall(reverseCallback, data);
39+
bool queueWasEmpty = reverseQueue.empty();
40+
reverseQueue.push(reverseCall);
41+
// we only trigger RunReverseCallbacks via the reverseAsync handle if the queue
42+
// was empty. Otherwise, we depend on RunReverseCallbacks to re-trigger itself
43+
if (queueWasEmpty) {
44+
uv_async_send(&reverseAsync);
45+
}
46+
uv_mutex_unlock(&reverseMutex);
47+
}
48+
3149
void ThreadPool::RunEventQueue(void *threadPool) {
3250
static_cast<ThreadPool *>(threadPool)->RunEventQueue();
3351
}
@@ -46,40 +64,62 @@ void ThreadPool::RunEventQueue() {
4664
(*work.workCallback)(work.data);
4765

4866
// schedule the callback on the loop
49-
uv_mutex_lock(&loopMutex);
50-
loopQueue.push(work);
51-
uv_mutex_unlock(&loopMutex);
52-
uv_async_send(&loopAsync);
67+
uv_mutex_lock(&completionMutex);
68+
completionQueue.push(work);
69+
uv_mutex_unlock(&completionMutex);
70+
uv_async_send(&completionAsync);
5371
}
5472
}
5573

56-
void ThreadPool::RunLoopCallbacks(uv_async_t* handle) {
57-
static_cast<ThreadPool *>(handle->data)->RunLoopCallbacks();
74+
void ThreadPool::RunCompletionCallbacks(uv_async_t* handle) {
75+
static_cast<ThreadPool *>(handle->data)->RunCompletionCallbacks();
5876
}
5977

60-
void ThreadPool::RunLoopCallbacks() {
78+
void ThreadPool::RunCompletionCallbacks() {
6179
// uv_async_send can coalesce calls, so we are not guaranteed one
62-
// RunLoopCallbacks per uv_async_send call
63-
// so we always process the entire loopQueue
80+
// RunCompletionCallbacks per uv_async_send call
81+
// so we always process the entire completionQueue
6482
int callbacksCompleted = 0;
65-
uv_mutex_lock(&loopMutex);
66-
while(!loopQueue.empty()) {
67-
Work work = loopQueue.front();
68-
loopQueue.pop();
69-
uv_mutex_unlock(&loopMutex);
83+
uv_mutex_lock(&completionMutex);
84+
while(!completionQueue.empty()) {
85+
Work work = completionQueue.front();
86+
completionQueue.pop();
87+
uv_mutex_unlock(&completionMutex);
7088
// perform the queued loop callback
71-
(*work.loopCallback)(work.data);
89+
(*work.completionCallback)(work.data);
7290
callbacksCompleted++;
73-
uv_mutex_lock(&loopMutex);
91+
uv_mutex_lock(&completionMutex);
7492
}
75-
uv_mutex_unlock(&loopMutex);
93+
uv_mutex_unlock(&completionMutex);
7694

7795
uv_mutex_lock(&workMutex);
7896
// if there is no ongoing work / completion processing, node doesn't need
7997
// to be prevented from terminating
8098
workInProgressCount -= callbacksCompleted;
8199
if(!workInProgressCount) {
82-
uv_unref((uv_handle_t *)&loopAsync);
100+
uv_unref((uv_handle_t *)&completionAsync);
83101
}
84102
uv_mutex_unlock(&workMutex);
85103
}
104+
105+
void ThreadPool::RunReverseCallbacks(uv_async_t* handle) {
106+
static_cast<ThreadPool *>(handle->data)->RunReverseCallbacks();
107+
}
108+
109+
void ThreadPool::RunReverseCallbacks() {
110+
// get the next callback to run
111+
uv_mutex_lock(&reverseMutex);
112+
ReverseCall reverseCall = reverseQueue.front();
113+
uv_mutex_unlock(&reverseMutex);
114+
115+
// execute callback
116+
(*reverseCall.reverseCallback)(reverseCall.data);
117+
118+
// pop the queue, and if necessary, re-trigger RunReverseCallbacks
119+
uv_mutex_lock(&reverseMutex);
120+
reverseQueue.pop();
121+
if (!reverseQueue.empty()) {
122+
uv_async_send(&reverseAsync);
123+
}
124+
uv_mutex_unlock(&reverseMutex);
125+
}

0 commit comments

Comments
 (0)