Skip to content

Commit 23d5e9b

Browse files
chkuang-ga-maurice
authored andcommitted
Fix deadlock when callback is executing.
Reduce the scope when CallbackEntry::mutex_ is locked. Also change CallbackQueue to keep a list of SharedPtr instead of raw pointer of CallbackEntry to prevent it from being deleted while CallbackEntry::Execute() is running. PiperOrigin-RevId: 292236040
1 parent 8978ba5 commit 23d5e9b

File tree

1 file changed

+42
-26
lines changed

1 file changed

+42
-26
lines changed

app/src/callback.cc

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <list>
2020

21+
#include "app/memory/shared_ptr.h"
2122
#include "app/src/log.h"
2223
#include "app/src/mutex.h"
2324
#include "app/src/semaphore.h"
@@ -32,7 +33,7 @@ namespace callback {
3233

3334
class CallbackEntry;
3435

35-
class CallbackQueue : public std::list<CallbackEntry*> {
36+
class CallbackQueue : public std::list<SharedPtr<CallbackEntry>> {
3637
public:
3738
CallbackQueue() {}
3839
~CallbackQueue() {}
@@ -51,7 +52,7 @@ class CallbackEntry {
5152
// callback_mutex_ is used to enforce a critical section for callback
5253
// execution and destruction.
5354
CallbackEntry(Callback* callback, Mutex* callback_mutex)
54-
: callback_(callback), mutex_(callback_mutex) {}
55+
: callback_(callback), mutex_(callback_mutex), executing_(false) {}
5556

5657
// Destroy the callback. This blocks if the callback is currently
5758
// executing.
@@ -61,35 +62,47 @@ class CallbackEntry {
6162
// Returns true if a callback was associated with this entry and was executed,
6263
// false otherwise.
6364
bool Execute() {
64-
bool executed = false;
65-
MutexLock lock(*mutex_);
66-
if (callback_) {
67-
callback_->Run();
68-
// Note: The implementation of BlockingCallback below relies on the
69-
// callback being disabled after being run. If that changes, please
70-
// make sure to also update BlockingCallback.
71-
DisableCallback();
72-
executed = true;
65+
{
66+
MutexLock lock(*mutex_);
67+
if (!callback_) return false;
68+
executing_ = true;
7369
}
74-
return executed;
70+
71+
callback_->Run();
72+
73+
{
74+
MutexLock lock(*mutex_);
75+
executing_ = false;
76+
}
77+
78+
// Note: The implementation of BlockingCallback below relies on the
79+
// callback being disabled after being run. If that changes, please
80+
// make sure to also update BlockingCallback.
81+
DisableCallback();
82+
83+
return true;
7584
}
7685

7786
// Remove the callback method from this entry.
7887
bool DisableCallback() {
79-
MutexLock lock(*mutex_);
80-
if (callback_) {
81-
delete callback_;
88+
Callback* callback_to_delete = nullptr;
89+
{
90+
MutexLock lock(*mutex_);
91+
if (executing_ || !callback_) return false;
92+
callback_to_delete = callback_;
8293
callback_ = nullptr;
83-
return true;
8494
}
85-
return false;
95+
delete callback_to_delete;
96+
return true;
8697
}
8798

8899
private:
89100
// Callback to call from PollCallbacks().
90101
Callback* callback_;
91-
// Mutex that is held when modifying callback_.
102+
// Mutex that is held when modifying callback_ and executing_.
92103
Mutex* mutex_;
104+
// A flag set to true when callback_ is about to be called.
105+
bool executing_;
93106
};
94107

95108
// Dispatches a queue of callbacks.
@@ -106,18 +119,18 @@ class CallbackDispatcher {
106119
remaining_callbacks);
107120
}
108121
while (!queue_.empty()) {
109-
delete queue_.back();
122+
queue_.back().reset();
110123
queue_.pop_back();
111124
}
112125
}
113126

114127
// Add a callback to the dispatch queue returning a reference
115128
// to the entry which can be optionally be removed prior to dispatch.
116129
void* AddCallback(Callback* callback) {
117-
CallbackEntry* entry = new CallbackEntry(callback, &execution_mutex_);
130+
auto entry = MakeShared<CallbackEntry>(callback, &execution_mutex_);
118131
MutexLock lock(*queue_.mutex());
119132
queue_.push_back(entry);
120-
return entry;
133+
return entry.get();
121134
}
122135

123136
// Remove the callback reference from the specified entry.
@@ -137,16 +150,19 @@ class CallbackDispatcher {
137150
int DispatchCallbacks() {
138151
int dispatched = 0;
139152
Mutex* queue_mutex = queue_.mutex();
140-
MutexLock lock(*queue_mutex);
153+
queue_mutex->Acquire();
141154
while (!queue_.empty()) {
142-
CallbackEntry* callback_entry = queue_.front();
155+
// Make a copy of the SharedPtr in case FlushCallbacks() is called
156+
// currently.
157+
SharedPtr<CallbackEntry> callback_entry = queue_.front();
143158
queue_.pop_front();
144159
queue_mutex->Release();
145160
callback_entry->Execute();
146161
dispatched++;
147162
queue_mutex->Acquire();
148-
delete callback_entry;
163+
callback_entry.reset();
149164
}
165+
queue_mutex->Release();
150166
return dispatched;
151167
}
152168

@@ -155,7 +171,7 @@ class CallbackDispatcher {
155171
int flushed = 0;
156172
MutexLock lock(*queue_.mutex());
157173
while (!queue_.empty()) {
158-
delete queue_.front();
174+
queue_.front().reset();
159175
queue_.pop_front();
160176
flushed++;
161177
}
@@ -299,7 +315,7 @@ void RemoveCallback(void* callback_reference) {
299315
// remove the CallbackEntry from the queue so we don't need an additional
300316
// Terminate() here to decrement the reference count that was added by
301317
// AddCallback().
302-
static_cast<CallbackEntry*>(callback_reference)->DisableCallback();
318+
g_callback_dispatcher->DisableCallback(callback_reference);
303319
Terminate(false);
304320
}
305321
}

0 commit comments

Comments
 (0)