Skip to content

Commit 4f55eae

Browse files
committed
Release handles and revoke callbacks when a GC runtime shuts down
Dart frees handles with a Finalizer, which never runs when an isolate group shuts down, so on a Flutter hot restart everything the old isolate created stayed alive: tray icons piled up (leanflutter/tray_manager#106). The core also kept calling the old isolate's NativeCallables, which died with it, and crashed on the next event that reached one. native_handle_finalize(void*) releases any handle for a native finalizer, always posted to the main thread. native_user_data_revoke(void*) marks a user_data whose runtime is gone: the generated glue checks revoked() before every callback call, a revoked async listener casts no vote, and its holder skips the release. The revocation set is never destroyed, since holders still let go during static destruction at exit.
1 parent 49fad9c commit 4f55eae

18 files changed

Lines changed: 112 additions & 8 deletions

‎src/capi/application_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ native_listener_id_t native_application_add_listener(native_application_event_ca
203203
try {
204204
return static_cast<native_listener_id_t>(nativeapi::Application::GetInstance().AddListener<nativeapi::ApplicationEvent>(
205205
[callback, holder](const nativeapi::ApplicationEvent& event) {
206+
if (holder->revoked()) return;
206207
native_application_event_t c_event = {};
207208
if (!to_c_application_event(event, &c_event)) {
208209
return;
@@ -222,6 +223,7 @@ native_listener_id_t native_application_add_listener_async(native_application_ev
222223
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
223224
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::ApplicationEvent>(nativeapi::Application::GetInstance(),
224225
[callback, registration](const nativeapi::ApplicationEvent& event) {
226+
if (registration->context->holder->revoked()) return;
225227
std::shared_ptr<nativeapi::EventRequest> request;
226228
if (const auto* typed = dynamic_cast<const nativeapi::ApplicationQuitRequestedEvent*>(&event)) request = typed->GetRequest();
227229
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;

‎src/capi/common_c.cpp‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
#include "common_c.h"
55
#include "event_delivery.h"
6+
#include "../foundation/dispatcher.h"
67
#include "../foundation/handle_table.h"
8+
#include "user_data.h"
79

810
bool native_event_delivery_is_active(native_event_delivery_t delivery) {
911
auto value = nativeapi::HandleTable::GetInstance().Resolve<nativeapi::capi::EventDelivery>(delivery);
@@ -17,3 +19,17 @@ bool native_event_delivery_complete(native_event_delivery_t delivery, bool accep
1719
table.Release(delivery);
1820
return true;
1921
}
22+
23+
void native_handle_finalize(void* handle) {
24+
const auto value = static_cast<nativeapi::HandleValue>(
25+
reinterpret_cast<uintptr_t>(handle));
26+
// Always posted, even from the main thread: destroying a platform object
27+
// off it is unsafe, and a runtime shutting down revokes its callbacks in
28+
// its other finalizers first, which the destruction may otherwise call.
29+
auto release = [value] { nativeapi::HandleTable::GetInstance().Release(value); };
30+
if (!nativeapi::RunOnMainThread(release) && !nativeapi::IsMainThreadDispatchSupported()) release();
31+
}
32+
33+
void native_user_data_revoke(void* user_data) {
34+
nativeapi::capi::RevokedUserData::Revoke(user_data);
35+
}

‎src/capi/common_c.h‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ FFI_PLUGIN_EXPORT bool native_event_delivery_is_active(native_event_delivery_t d
4040
/// Pass false on failure. Returns false for duplicate, stale or type-confused handles.
4141
FFI_PLUGIN_EXPORT bool native_event_delivery_complete(native_event_delivery_t delivery, bool accept);
4242

43+
/// Releases a handle of any type for a garbage collector's native finalizer,
44+
/// such as Dart's NativeFinalizer, which runs it on an arbitrary thread and
45+
/// also when the runtime shuts down (a Flutter hot restart, for one). `handle`
46+
/// is the handle's value cast to a pointer; the release itself runs on the
47+
/// main thread. Stale or invalid handles are ignored.
48+
FFI_PLUGIN_EXPORT void native_handle_finalize(void* handle);
49+
50+
/// Tells the core a binding's runtime is gone for this user_data, from a
51+
/// native finalizer like the one above: from now on it calls neither the
52+
/// callback that travels with it nor its release. Safe from any thread.
53+
/// A binding must not pass a revoked value again.
54+
FFI_PLUGIN_EXPORT void native_user_data_revoke(void* user_data);
55+
4356
#ifdef __cplusplus
4457
}
4558
#endif

‎src/capi/display_manager_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ native_listener_id_t native_display_manager_add_listener(native_display_event_ca
7272
try {
7373
return static_cast<native_listener_id_t>(nativeapi::DisplayManager::GetInstance().AddListener<nativeapi::DisplayEvent>(
7474
[callback, holder](const nativeapi::DisplayEvent& event) {
75+
if (holder->revoked()) return;
7576
native_display_event_t c_event = {};
7677
if (!to_c_display_event(event, &c_event)) {
7778
return;
@@ -91,6 +92,7 @@ native_listener_id_t native_display_manager_add_listener_async(native_display_ev
9192
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
9293
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::DisplayEvent>(nativeapi::DisplayManager::GetInstance(),
9394
[callback, registration](const nativeapi::DisplayEvent& event) {
95+
if (registration->context->holder->revoked()) return;
9496
std::shared_ptr<nativeapi::EventRequest> request;
9597
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
9698
native_event_delivery_t delivery = 0;

‎src/capi/drag_source_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ native_listener_id_t native_drag_source_add_listener(native_drag_source_t drag_s
207207
try {
208208
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::DragSourceEvent>(
209209
[callback, holder](const nativeapi::DragSourceEvent& event) {
210+
if (holder->revoked()) return;
210211
native_drag_source_event_t c_event = {};
211212
if (!to_c_drag_source_event(event, &c_event)) {
212213
return;
@@ -230,6 +231,7 @@ native_listener_id_t native_drag_source_add_listener_async(native_drag_source_t
230231
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
231232
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::DragSourceEvent>(*self,
232233
[callback, registration](const nativeapi::DragSourceEvent& event) {
234+
if (registration->context->holder->revoked()) return;
233235
std::shared_ptr<nativeapi::EventRequest> request;
234236
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
235237
native_event_delivery_t delivery = 0;

‎src/capi/drop_target_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ native_listener_id_t native_drop_target_add_listener(native_drop_target_t drop_t
114114
try {
115115
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::DropTargetEvent>(
116116
[callback, holder](const nativeapi::DropTargetEvent& event) {
117+
if (holder->revoked()) return;
117118
native_drop_target_event_t c_event = {};
118119
if (!to_c_drop_target_event(event, &c_event)) {
119120
return;
@@ -137,6 +138,7 @@ native_listener_id_t native_drop_target_add_listener_async(native_drop_target_t
137138
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
138139
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::DropTargetEvent>(*self,
139140
[callback, registration](const nativeapi::DropTargetEvent& event) {
141+
if (registration->context->holder->revoked()) return;
140142
std::shared_ptr<nativeapi::EventRequest> request;
141143
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
142144
native_event_delivery_t delivery = 0;

‎src/capi/keyboard_monitor_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ native_listener_id_t native_keyboard_monitor_add_listener(native_keyboard_monito
8888
try {
8989
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::KeyboardEvent>(
9090
[callback, holder](const nativeapi::KeyboardEvent& event) {
91+
if (holder->revoked()) return;
9192
native_keyboard_event_t c_event = {};
9293
if (!to_c_keyboard_event(event, &c_event)) {
9394
return;
@@ -111,6 +112,7 @@ native_listener_id_t native_keyboard_monitor_add_listener_async(native_keyboard_
111112
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
112113
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::KeyboardEvent>(*self,
113114
[callback, registration](const nativeapi::KeyboardEvent& event) {
115+
if (registration->context->holder->revoked()) return;
114116
std::shared_ptr<nativeapi::EventRequest> request;
115117
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
116118
native_event_delivery_t delivery = 0;

‎src/capi/menu_c.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ native_listener_id_t native_menu_item_add_listener(native_menu_item_t menu_item,
353353
try {
354354
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::MenuEvent>(
355355
[callback, holder](const nativeapi::MenuEvent& event) {
356+
if (holder->revoked()) return;
356357
native_menu_event_t c_event = {};
357358
if (!to_c_menu_event(event, &c_event)) {
358359
return;
@@ -376,6 +377,7 @@ native_listener_id_t native_menu_item_add_listener_async(native_menu_item_t menu
376377
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
377378
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::MenuEvent>(*self,
378379
[callback, registration](const nativeapi::MenuEvent& event) {
380+
if (registration->context->holder->revoked()) return;
379381
std::shared_ptr<nativeapi::EventRequest> request;
380382
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
381383
native_event_delivery_t delivery = 0;
@@ -711,6 +713,7 @@ native_listener_id_t native_menu_add_listener(native_menu_t menu, native_menu_ev
711713
try {
712714
return static_cast<native_listener_id_t>(self->AddListener<nativeapi::MenuEvent>(
713715
[callback, holder](const nativeapi::MenuEvent& event) {
716+
if (holder->revoked()) return;
714717
native_menu_event_t c_event = {};
715718
if (!to_c_menu_event(event, &c_event)) {
716719
return;
@@ -734,6 +737,7 @@ native_listener_id_t native_menu_add_listener_async(native_menu_t menu, native_m
734737
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
735738
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::MenuEvent>(*self,
736739
[callback, registration](const nativeapi::MenuEvent& event) {
740+
if (registration->context->holder->revoked()) return;
737741
std::shared_ptr<nativeapi::EventRequest> request;
738742
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
739743
native_event_delivery_t delivery = 0;

‎src/capi/notification_manager_c.cpp‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ native_listener_id_t native_notification_manager_add_listener(native_notificatio
8080
try {
8181
return static_cast<native_listener_id_t>(nativeapi::NotificationManager::GetInstance().AddListener<nativeapi::NotificationEvent>(
8282
[callback, holder](const nativeapi::NotificationEvent& event) {
83+
if (holder->revoked()) return;
8384
native_notification_event_t c_event = {};
8485
if (!to_c_notification_event(event, &c_event)) {
8586
return;
@@ -99,6 +100,7 @@ native_listener_id_t native_notification_manager_add_listener_async(native_notif
99100
auto registration = std::make_shared<nativeapi::capi::EventDeliveryRegistration>(holder);
100101
return static_cast<native_listener_id_t>(nativeapi::detail::EventListenerDispatch::AddListener<nativeapi::NotificationEvent>(nativeapi::NotificationManager::GetInstance(),
101102
[callback, registration](const nativeapi::NotificationEvent& event) {
103+
if (registration->context->holder->revoked()) return;
102104
std::shared_ptr<nativeapi::EventRequest> request;
103105
auto vote = request && request->IsCancelable() ? request->Defer() : nullptr;
104106
native_event_delivery_t delivery = 0;

‎src/capi/shortcut_c.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ native_shortcut_t native_shortcut_create_with_id_and_accelerator_and_callback(na
4242
try {
4343
std::function<void()> callback_cpp;
4444
if (callback) {
45-
callback_cpp = [callback, callback_holder]() { callback(callback_holder->get()); };
45+
callback_cpp = [callback, callback_holder]() { if (!callback_holder->revoked()) callback(callback_holder->get()); };
4646
}
4747
return nativeapi::HandleTable::GetInstance().Insert(
4848
std::make_shared<nativeapi::Shortcut>(id, std::string(accelerator ? accelerator : ""), callback_cpp));
@@ -168,7 +168,7 @@ void native_shortcut_set_callback(native_shortcut_t shortcut, native_shortcut_se
168168
try {
169169
std::function<void()> callback_cpp;
170170
if (callback) {
171-
callback_cpp = [callback, callback_holder]() { callback(callback_holder->get()); };
171+
callback_cpp = [callback, callback_holder]() { if (!callback_holder->revoked()) callback(callback_holder->get()); };
172172
}
173173
self->SetCallback(callback_cpp);
174174
return;

0 commit comments

Comments
 (0)