Skip to content

Commit 9ba263c

Browse files
fix: resolve timer bugs causing lost timers and resource leaks
Fixed five bugs in Timers.cpp: - Inverted EAGAIN retry condition causing timer loss when pipe buffer full - File descriptor leak (fd_[1] never closed in Destroy) - deletedTimers_ accumulation for immediate timers cleared before callback - Data race on stopped flag (now atomic) - Non-EAGAIN write errors incorrectly treated as success Additional improvements: - Clean up deletedTimers_ when timer cancelled during EAGAIN retry - Guard isBufferFull reset to only occur on actual write success
1 parent a4931b1 commit 9ba263c

File tree

2 files changed

+19
-6
lines changed

2 files changed

+19
-6
lines changed

test-app/runtime/src/main/cpp/Timers.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,14 @@ void Timers::addTask(std::shared_ptr<TimerTask> task) {
8989
bool needsScheduling = true;
9090
if (!isBufferFull.load() && task->dueTime_ <= now) {
9191
auto result = write(fd_[1], &task->id_, sizeof(int));
92-
if (result != -1 || errno != EAGAIN) {
92+
if (result != -1) {
93+
// Wrote successfully to the pipe; no need to schedule in sortedTimers_
9394
needsScheduling = false;
94-
} else {
95+
} else if (errno == EAGAIN) {
96+
// Pipe is full; mark buffer as full and fall back to sortedTimers_
9597
isBufferFull = true;
9698
}
99+
// For other errors: keep needsScheduling = true to avoid dropping timer
97100
}
98101
if (needsScheduling) {
99102
{
@@ -156,11 +159,16 @@ void Timers::threadLoop() {
156159
auto result = write(fd_[1], &timer->id, sizeof(int));
157160
if (result == -1 && errno == EAGAIN) {
158161
isBufferFull = true;
159-
while (!stopped && deletedTimers_.find(timer->id) != deletedTimers_.end() &&
162+
while (!stopped && deletedTimers_.find(timer->id) == deletedTimers_.end() &&
160163
write(fd_[1], &timer->id, sizeof(int)) == -1 && errno == EAGAIN) {
161164
bufferFull.wait(lk);
162165
}
163-
} else if (isBufferFull.load() &&
166+
// If the timer was cleared while we were waiting for buffer space,
167+
// clean up deletedTimers_ to avoid leaking the timer ID.
168+
if (deletedTimers_.find(timer->id) != deletedTimers_.end()) {
169+
deletedTimers_.erase(timer->id);
170+
}
171+
} else if (result != -1 && isBufferFull.load() &&
164172
(sortedTimers_.empty() || sortedTimers_.at(0)->dueTime > now)) {
165173
// we had a successful write and the next timer is not due
166174
// mark the buffer as free to re-enable the setTimeout with 0 optimization
@@ -189,6 +197,7 @@ void Timers::Destroy() {
189197
auto mainLooper = Runtime::GetMainLooper();
190198
ALooper_removeFd(mainLooper, fd_[0]);
191199
close(fd_[0]);
200+
close(fd_[1]);
192201
timerMap_.clear();
193202
ALooper_release(looper_);
194203
}
@@ -319,6 +328,10 @@ int Timers::PumpTimerLoopCallback(int fd, int events, void *data) {
319328
}
320329

321330

331+
} else {
332+
// Timer was cleared before callback ran - clean up deletedTimers_
333+
std::lock_guard<std::mutex> lock(thiz->mutex);
334+
thiz->deletedTimers_.erase(timerId);
322335
}
323336
thiz->bufferFull.notify_one();
324337
return 1;
@@ -332,4 +345,4 @@ void Timers::InitStatic(v8::Isolate* isolate, v8::Local<v8::ObjectTemplate> glob
332345

333346
};
334347

335-
NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::InitStatic);
348+
NODE_BINDING_PER_ISOLATE_INIT_OBJ(timers, tns::Timers::InitStatic);

test-app/runtime/src/main/cpp/Timers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ namespace tns {
128128
std::condition_variable bufferFull;
129129
std::mutex mutex;
130130
std::thread watcher_;
131-
bool stopped = false;
131+
std::atomic<bool> stopped = false;
132132
};
133133

134134
}

0 commit comments

Comments
 (0)