|
4 | 4 | #include <cstddef> |
5 | 5 | #include <cstdint> |
6 | 6 | #include <cstdio> |
| 7 | +#include <map> |
| 8 | +#include <mutex> |
7 | 9 | #include <thread> |
8 | 10 |
|
9 | 11 | #include "cppgc/allocation.h" |
@@ -3010,6 +3012,144 @@ v8::StartupData v8__SnapshotCreator__CreateBlob( |
3010 | 3012 | return self->CreateBlob(function_code_handling); |
3011 | 3013 | } |
3012 | 3014 |
|
| 3015 | +// Rust-side callbacks for trait-based CustomPlatform (PlatformImpl trait). |
| 3016 | +// Each callback corresponds to a C++ virtual method on TaskRunner or Platform. |
| 3017 | +// `context` is a pointer to the Rust Box<dyn PlatformImpl>. |
| 3018 | +extern "C" { |
| 3019 | +void v8__Platform__CustomPlatform__BASE__PostTask(void* context, void* isolate); |
| 3020 | +void v8__Platform__CustomPlatform__BASE__PostNonNestableTask(void* context, |
| 3021 | + void* isolate); |
| 3022 | +void v8__Platform__CustomPlatform__BASE__PostDelayedTask( |
| 3023 | + void* context, void* isolate, double delay_in_seconds); |
| 3024 | +void v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask( |
| 3025 | + void* context, void* isolate, double delay_in_seconds); |
| 3026 | +void v8__Platform__CustomPlatform__BASE__PostIdleTask(void* context, |
| 3027 | + void* isolate); |
| 3028 | +void v8__Platform__CustomPlatform__BASE__DROP(void* context); |
| 3029 | +} |
| 3030 | + |
| 3031 | +// TaskRunner wrapper that intercepts all PostTask* virtual methods, forwards |
| 3032 | +// tasks to the default platform's queue, and notifies Rust via the |
| 3033 | +// corresponding PlatformImpl trait method. |
| 3034 | +class CustomTaskRunner final : public v8::TaskRunner { |
| 3035 | + public: |
| 3036 | + CustomTaskRunner(std::shared_ptr<v8::TaskRunner> wrapped, void* context, |
| 3037 | + v8::Isolate* isolate) |
| 3038 | + : wrapped_(std::move(wrapped)), context_(context), isolate_(isolate) {} |
| 3039 | + |
| 3040 | + bool IdleTasksEnabled() override { return wrapped_->IdleTasksEnabled(); } |
| 3041 | + bool NonNestableTasksEnabled() const override { |
| 3042 | + return wrapped_->NonNestableTasksEnabled(); |
| 3043 | + } |
| 3044 | + bool NonNestableDelayedTasksEnabled() const override { |
| 3045 | + return wrapped_->NonNestableDelayedTasksEnabled(); |
| 3046 | + } |
| 3047 | + |
| 3048 | + protected: |
| 3049 | + void PostTaskImpl(std::unique_ptr<v8::Task> task, |
| 3050 | + const v8::SourceLocation& location) override { |
| 3051 | + wrapped_->PostTask(std::move(task), location); |
| 3052 | + v8__Platform__CustomPlatform__BASE__PostTask(context_, |
| 3053 | + static_cast<void*>(isolate_)); |
| 3054 | + } |
| 3055 | + void PostNonNestableTaskImpl(std::unique_ptr<v8::Task> task, |
| 3056 | + const v8::SourceLocation& location) override { |
| 3057 | + wrapped_->PostNonNestableTask(std::move(task), location); |
| 3058 | + v8__Platform__CustomPlatform__BASE__PostNonNestableTask( |
| 3059 | + context_, static_cast<void*>(isolate_)); |
| 3060 | + } |
| 3061 | + void PostDelayedTaskImpl(std::unique_ptr<v8::Task> task, |
| 3062 | + double delay_in_seconds, |
| 3063 | + const v8::SourceLocation& location) override { |
| 3064 | + wrapped_->PostDelayedTask(std::move(task), delay_in_seconds, location); |
| 3065 | + v8__Platform__CustomPlatform__BASE__PostDelayedTask( |
| 3066 | + context_, static_cast<void*>(isolate_), |
| 3067 | + delay_in_seconds > 0 ? delay_in_seconds : 0.0); |
| 3068 | + } |
| 3069 | + void PostNonNestableDelayedTaskImpl( |
| 3070 | + std::unique_ptr<v8::Task> task, double delay_in_seconds, |
| 3071 | + const v8::SourceLocation& location) override { |
| 3072 | + wrapped_->PostNonNestableDelayedTask(std::move(task), delay_in_seconds, |
| 3073 | + location); |
| 3074 | + v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask( |
| 3075 | + context_, static_cast<void*>(isolate_), |
| 3076 | + delay_in_seconds > 0 ? delay_in_seconds : 0.0); |
| 3077 | + } |
| 3078 | + void PostIdleTaskImpl(std::unique_ptr<v8::IdleTask> task, |
| 3079 | + const v8::SourceLocation& location) override { |
| 3080 | + wrapped_->PostIdleTask(std::move(task), location); |
| 3081 | + v8__Platform__CustomPlatform__BASE__PostIdleTask( |
| 3082 | + context_, static_cast<void*>(isolate_)); |
| 3083 | + } |
| 3084 | + |
| 3085 | + private: |
| 3086 | + std::shared_ptr<v8::TaskRunner> wrapped_; |
| 3087 | + void* context_; |
| 3088 | + v8::Isolate* isolate_; |
| 3089 | +}; |
| 3090 | + |
| 3091 | +// Platform subclass that wraps each isolate's TaskRunner to notify Rust |
| 3092 | +// when foreground tasks are posted. Follows the inspector API pattern. |
| 3093 | +// |
| 3094 | +// NotifyIsolateShutdown is NOT intercepted here because it is not virtual |
| 3095 | +// on DefaultPlatform — V8's free function does static_cast<DefaultPlatform*> |
| 3096 | +// and calls it directly, bypassing any override. Isolate cleanup must be |
| 3097 | +// handled on the Rust side (e.g. in the isolate's Drop impl). |
| 3098 | +class CustomPlatform : public v8::platform::DefaultPlatform { |
| 3099 | + using IdleTaskSupport = v8::platform::IdleTaskSupport; |
| 3100 | + |
| 3101 | + public: |
| 3102 | + CustomPlatform(int thread_pool_size, IdleTaskSupport idle_task_support, |
| 3103 | + bool unprotected, void* context) |
| 3104 | + : DefaultPlatform(thread_pool_size, idle_task_support), |
| 3105 | + unprotected_(unprotected), |
| 3106 | + context_(context) {} |
| 3107 | + |
| 3108 | + // SAFETY: The platform is single-owner (via unique_ptr). The destructor |
| 3109 | + // runs after all isolates have been disposed and no more task runner |
| 3110 | + // callbacks can fire, so DROP does not race with other callbacks. |
| 3111 | + ~CustomPlatform() override { |
| 3112 | + v8__Platform__CustomPlatform__BASE__DROP(context_); |
| 3113 | + } |
| 3114 | + |
| 3115 | + std::shared_ptr<v8::TaskRunner> GetForegroundTaskRunner( |
| 3116 | + v8::Isolate* isolate, v8::TaskPriority priority) override { |
| 3117 | + auto original = DefaultPlatform::GetForegroundTaskRunner(isolate, priority); |
| 3118 | + std::lock_guard<std::mutex> lock(mutex_); |
| 3119 | + auto key = std::make_pair(isolate, priority); |
| 3120 | + auto it = runners_.find(key); |
| 3121 | + if (it != runners_.end()) { |
| 3122 | + auto runner = it->second.lock(); |
| 3123 | + if (runner) return runner; |
| 3124 | + } |
| 3125 | + auto custom = |
| 3126 | + std::make_shared<CustomTaskRunner>(original, context_, isolate); |
| 3127 | + runners_[key] = custom; |
| 3128 | + return custom; |
| 3129 | + } |
| 3130 | + |
| 3131 | + // When unprotected, disable thread-isolated allocations (same as |
| 3132 | + // UnprotectedDefaultPlatform). Required when isolates may be created on |
| 3133 | + // threads other than the one that called v8::V8::Initialize (e.g. worker |
| 3134 | + // threads in Deno). |
| 3135 | + v8::ThreadIsolatedAllocator* GetThreadIsolatedAllocator() override { |
| 3136 | + if (unprotected_) return nullptr; |
| 3137 | + return DefaultPlatform::GetThreadIsolatedAllocator(); |
| 3138 | + } |
| 3139 | + |
| 3140 | + private: |
| 3141 | + bool unprotected_; |
| 3142 | + void* context_; |
| 3143 | + std::mutex mutex_; |
| 3144 | + // weak_ptr so runners are kept alive only while V8 holds a reference. |
| 3145 | + // When V8 drops its shared_ptr (e.g. on isolate shutdown), the weak_ptr |
| 3146 | + // expires and a fresh wrapper is created if GetForegroundTaskRunner is |
| 3147 | + // called again. This avoids preventing cleanup of the underlying runner. |
| 3148 | + std::map<std::pair<v8::Isolate*, v8::TaskPriority>, |
| 3149 | + std::weak_ptr<CustomTaskRunner>> |
| 3150 | + runners_; |
| 3151 | +}; |
| 3152 | + |
3013 | 3153 | class UnprotectedDefaultPlatform : public v8::platform::DefaultPlatform { |
3014 | 3154 | using IdleTaskSupport = v8::platform::IdleTaskSupport; |
3015 | 3155 | using InProcessStackDumping = v8::platform::InProcessStackDumping; |
@@ -3063,6 +3203,21 @@ v8::Platform* v8__Platform__NewSingleThreadedDefaultPlatform( |
3063 | 3203 | .release(); |
3064 | 3204 | } |
3065 | 3205 |
|
| 3206 | +v8::Platform* v8__Platform__NewCustomPlatform(int thread_pool_size, |
| 3207 | + bool idle_task_support, |
| 3208 | + bool unprotected, void* context) { |
| 3209 | + if (thread_pool_size < 1) { |
| 3210 | + thread_pool_size = std::thread::hardware_concurrency(); |
| 3211 | + } |
| 3212 | + thread_pool_size = std::max(std::min(thread_pool_size, 16), 1); |
| 3213 | + return std::make_unique<CustomPlatform>( |
| 3214 | + thread_pool_size, |
| 3215 | + idle_task_support ? v8::platform::IdleTaskSupport::kEnabled |
| 3216 | + : v8::platform::IdleTaskSupport::kDisabled, |
| 3217 | + unprotected, context) |
| 3218 | + .release(); |
| 3219 | +} |
| 3220 | + |
3066 | 3221 | bool v8__Platform__PumpMessageLoop(v8::Platform* platform, v8::Isolate* isolate, |
3067 | 3222 | bool wait_for_work) { |
3068 | 3223 | return v8::platform::PumpMessageLoop( |
|
0 commit comments