Skip to content

Commit 0cacc1f

Browse files
addaleaxcodebytere
authored andcommitted
src: add interrupts to Environments/Workers
Allow doing what V8’s `v8::Isolate::RequestInterrupt()` does for V8. This also works when there is no JS code currently executing. PR-URL: #31386 Refs: openjs-foundation/summit#240 Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent f8c45b2 commit 0cacc1f

File tree

4 files changed

+74
-0
lines changed

4 files changed

+74
-0
lines changed

src/env-inl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,18 @@ void Environment::SetImmediateThreadsafe(Fn&& cb) {
809809
uv_async_send(&task_queues_async_);
810810
}
811811

812+
template <typename Fn>
813+
void Environment::RequestInterrupt(Fn&& cb) {
814+
auto callback = std::make_unique<NativeImmediateCallbackImpl<Fn>>(
815+
std::move(cb), false);
816+
{
817+
Mutex::ScopedLock lock(native_immediates_threadsafe_mutex_);
818+
native_immediates_interrupts_.Push(std::move(callback));
819+
}
820+
uv_async_send(&task_queues_async_);
821+
RequestInterruptFromV8();
822+
}
823+
812824
Environment::NativeImmediateCallback::NativeImmediateCallback(bool refed)
813825
: refed_(refed) {}
814826

src/env.cc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ Environment::Environment(IsolateData* isolate_data,
388388
}
389389

390390
Environment::~Environment() {
391+
if (interrupt_data_ != nullptr) *interrupt_data_ = nullptr;
392+
391393
isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback(
392394
BuildEmbedderGraph, this);
393395

@@ -654,11 +656,29 @@ void Environment::AtExit(void (*cb)(void* arg), void* arg) {
654656
at_exit_functions_.push_front(ExitCallback{cb, arg});
655657
}
656658

659+
void Environment::RunAndClearInterrupts() {
660+
while (native_immediates_interrupts_.size() > 0) {
661+
NativeImmediateQueue queue;
662+
{
663+
Mutex::ScopedLock lock(native_immediates_threadsafe_mutex_);
664+
queue.ConcatMove(std::move(native_immediates_interrupts_));
665+
}
666+
DebugSealHandleScope seal_handle_scope(isolate());
667+
668+
while (std::unique_ptr<NativeImmediateCallback> head = queue.Shift())
669+
head->Call(this);
670+
}
671+
}
672+
657673
void Environment::RunAndClearNativeImmediates(bool only_refed) {
658674
TraceEventScope trace_scope(TRACING_CATEGORY_NODE1(environment),
659675
"RunAndClearNativeImmediates", this);
660676
size_t ref_count = 0;
661677

678+
// Handle interrupts first. These functions are not allowed to throw
679+
// exceptions, so we do not need to handle that.
680+
RunAndClearInterrupts();
681+
662682
// It is safe to check .size() first, because there is a causal relationship
663683
// between pushes to the threadsafe and this function being called.
664684
// For the common case, it's worth checking the size first before establishing
@@ -698,6 +718,27 @@ void Environment::RunAndClearNativeImmediates(bool only_refed) {
698718
ToggleImmediateRef(false);
699719
}
700720

721+
void Environment::RequestInterruptFromV8() {
722+
if (interrupt_data_ != nullptr) return; // Already scheduled.
723+
724+
// The Isolate may outlive the Environment, so some logic to handle the
725+
// situation in which the Environment is destroyed before the handler runs
726+
// is required.
727+
interrupt_data_ = new Environment*(this);
728+
729+
isolate()->RequestInterrupt([](Isolate* isolate, void* data) {
730+
std::unique_ptr<Environment*> env_ptr { static_cast<Environment**>(data) };
731+
Environment* env = *env_ptr;
732+
if (env == nullptr) {
733+
// The Environment has already been destroyed. That should be okay; any
734+
// callback added before the Environment shuts down would have been
735+
// handled during cleanup.
736+
return;
737+
}
738+
env->interrupt_data_ = nullptr;
739+
env->RunAndClearInterrupts();
740+
}, interrupt_data_);
741+
}
701742

702743
void Environment::ScheduleTimer(int64_t duration_ms) {
703744
if (started_cleanup_) return;

src/env.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,12 @@ class Environment : public MemoryRetainer {
11801180
template <typename Fn>
11811181
// This behaves like SetImmediate() but can be called from any thread.
11821182
inline void SetImmediateThreadsafe(Fn&& cb);
1183+
// This behaves like V8's Isolate::RequestInterrupt(), but also accounts for
1184+
// the event loop (i.e. combines the V8 function with SetImmediate()).
1185+
// The passed callback may not throw exceptions.
1186+
// This function can be called from any thread.
1187+
template <typename Fn>
1188+
inline void RequestInterrupt(Fn&& cb);
11831189
// This needs to be available for the JS-land setImmediate().
11841190
void ToggleImmediateRef(bool ref);
11851191

@@ -1428,8 +1434,12 @@ class Environment : public MemoryRetainer {
14281434
NativeImmediateQueue native_immediates_;
14291435
Mutex native_immediates_threadsafe_mutex_;
14301436
NativeImmediateQueue native_immediates_threadsafe_;
1437+
NativeImmediateQueue native_immediates_interrupts_;
14311438

14321439
void RunAndClearNativeImmediates(bool only_refed = false);
1440+
void RunAndClearInterrupts();
1441+
Environment** interrupt_data_ = nullptr;
1442+
void RequestInterruptFromV8();
14331443
static void CheckImmediate(uv_check_t* handle);
14341444

14351445
// Use an unordered_set, so that we have efficient insertion and removal.

src/node_worker.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ class Worker : public AsyncWrap {
4343
tracker->TrackField("parent_port", parent_port_);
4444
}
4545

46+
template <typename Fn>
47+
inline bool RequestInterrupt(Fn&& cb);
48+
4649
SET_MEMORY_INFO_NAME(Worker)
4750
SET_SELF_SIZE(Worker)
4851

@@ -123,6 +126,14 @@ class Worker : public AsyncWrap {
123126
friend class WorkerThreadData;
124127
};
125128

129+
template <typename Fn>
130+
bool Worker::RequestInterrupt(Fn&& cb) {
131+
Mutex::ScopedLock lock(mutex_);
132+
if (env_ == nullptr) return false;
133+
env_->RequestInterrupt(std::move(cb));
134+
return true;
135+
}
136+
126137
} // namespace worker
127138
} // namespace node
128139

0 commit comments

Comments
 (0)