Skip to content

Commit f7ca433

Browse files
stuartmorgan-ggoderbauer
authored andcommitted
Reland "Improve C++ plugin lifetime handling" (flutter#17570)
Relands flutter#17489 with a fix for the unit test flake. The previous unit test relied on the new instance not being created at the same memory address, which isn't guaranteed.
1 parent 2157698 commit f7ca433

File tree

11 files changed

+210
-10
lines changed

11 files changed

+210
-10
lines changed

shell/platform/common/cpp/client_wrapper/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,6 @@ executable("client_wrapper_unittests") {
6565
# https://github.com/flutter/flutter/issues/41414.
6666
"//third_party/dart/runtime:libdart_jit",
6767
]
68+
69+
defines = [ "FLUTTER_DESKTOP_LIBRARY" ]
6870
}

shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_H_
66
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_H_
77

8+
#include <map>
89
#include <memory>
910
#include <set>
1011
#include <string>
@@ -69,6 +70,53 @@ class Plugin {
6970
virtual ~Plugin() = default;
7071
};
7172

73+
// A singleton to own PluginRegistrars. This is intended for use in plugins,
74+
// where there is no higher-level object to own a PluginRegistrar that can
75+
// own plugin instances and ensure that they live as long as the engine they
76+
// are registered with.
77+
class PluginRegistrarManager {
78+
public:
79+
static PluginRegistrarManager* GetInstance();
80+
81+
// Prevent copying.
82+
PluginRegistrarManager(PluginRegistrarManager const&) = delete;
83+
PluginRegistrarManager& operator=(PluginRegistrarManager const&) = delete;
84+
85+
// Returns a plugin registrar wrapper of type T, which must be a kind of
86+
// PluginRegistrar, creating it if necessary. The returned registrar will
87+
// live as long as the underlying FlutterDesktopPluginRegistrarRef, so
88+
// can be used to own plugin instances.
89+
//
90+
// Calling this multiple times for the same registrar_ref with different
91+
// template types results in undefined behavior.
92+
template <class T>
93+
T* GetRegistrar(FlutterDesktopPluginRegistrarRef registrar_ref) {
94+
auto insert_result =
95+
registrars_.emplace(registrar_ref, std::make_unique<T>(registrar_ref));
96+
auto& registrar_pair = *(insert_result.first);
97+
FlutterDesktopRegistrarSetDestructionHandler(registrar_pair.first,
98+
OnRegistrarDestroyed);
99+
return static_cast<T*>(registrar_pair.second.get());
100+
}
101+
102+
// Destroys all registrar wrappers created by the manager.
103+
//
104+
// This is intended primarily for use in tests.
105+
void Reset() { registrars_.clear(); }
106+
107+
private:
108+
PluginRegistrarManager();
109+
110+
using WrapperMap = std::map<FlutterDesktopPluginRegistrarRef,
111+
std::unique_ptr<PluginRegistrar>>;
112+
113+
static void OnRegistrarDestroyed(FlutterDesktopPluginRegistrarRef registrar);
114+
115+
WrapperMap* registrars() { return &registrars_; }
116+
117+
WrapperMap registrars_;
118+
};
119+
72120
} // namespace flutter
73121

74122
#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_H_

shell/platform/common/cpp/client_wrapper/plugin_registrar.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ void BinaryMessengerImpl::SetMessageHandler(const std::string& channel,
138138
ForwardToHandler, message_handler);
139139
}
140140

141-
// PluginRegistrar:
141+
// ===== PluginRegistrar =====
142142

143143
PluginRegistrar::PluginRegistrar(FlutterDesktopPluginRegistrarRef registrar)
144144
: registrar_(registrar) {
@@ -157,4 +157,20 @@ void PluginRegistrar::EnableInputBlockingForChannel(
157157
FlutterDesktopRegistrarEnableInputBlocking(registrar_, channel.c_str());
158158
}
159159

160+
// ===== PluginRegistrarManager =====
161+
162+
// static
163+
PluginRegistrarManager* PluginRegistrarManager::GetInstance() {
164+
static PluginRegistrarManager* instance = new PluginRegistrarManager();
165+
return instance;
166+
}
167+
168+
PluginRegistrarManager::PluginRegistrarManager() = default;
169+
170+
// static
171+
void PluginRegistrarManager::OnRegistrarDestroyed(
172+
FlutterDesktopPluginRegistrarRef registrar) {
173+
GetInstance()->registrars()->erase(registrar);
174+
}
175+
160176
} // namespace flutter

shell/platform/common/cpp/client_wrapper/plugin_registrar_unittests.cc

Lines changed: 96 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,56 @@ class TestApi : public testing::StubFlutterApi {
3333
return message_engine_result;
3434
}
3535

36-
// Called for FlutterDesktopMessengerSetCallback.
3736
void MessengerSetCallback(const char* channel,
3837
FlutterDesktopMessageCallback callback,
3938
void* user_data) override {
40-
last_callback_set_ = callback;
39+
last_message_callback_set_ = callback;
40+
}
41+
42+
void RegistrarSetDestructionHandler(
43+
FlutterDesktopOnRegistrarDestroyed callback) override {
44+
last_destruction_callback_set_ = callback;
4145
}
4246

4347
const uint8_t* last_data_sent() { return last_data_sent_; }
44-
FlutterDesktopMessageCallback last_callback_set() {
45-
return last_callback_set_;
48+
FlutterDesktopMessageCallback last_message_callback_set() {
49+
return last_message_callback_set_;
50+
}
51+
FlutterDesktopOnRegistrarDestroyed last_destruction_callback_set() {
52+
return last_destruction_callback_set_;
4653
}
4754

4855
private:
4956
const uint8_t* last_data_sent_ = nullptr;
50-
FlutterDesktopMessageCallback last_callback_set_ = nullptr;
57+
FlutterDesktopMessageCallback last_message_callback_set_ = nullptr;
58+
FlutterDesktopOnRegistrarDestroyed last_destruction_callback_set_ = nullptr;
59+
};
60+
61+
// A PluginRegistrar whose destruction can be watched for by tests.
62+
class TestPluginRegistrar : public PluginRegistrar {
63+
public:
64+
explicit TestPluginRegistrar(FlutterDesktopPluginRegistrarRef core_registrar)
65+
: PluginRegistrar(core_registrar) {}
66+
67+
virtual ~TestPluginRegistrar() {
68+
if (destruction_callback_) {
69+
destruction_callback_();
70+
}
71+
}
72+
73+
void SetDestructionCallback(std::function<void()> callback) {
74+
destruction_callback_ = std::move(callback);
75+
}
76+
77+
private:
78+
std::function<void()> destruction_callback_;
5179
};
5280

5381
} // namespace
5482

5583
// Tests that the registrar returns a messenger that passes Send through to the
5684
// C API.
57-
TEST(MethodCallTest, MessengerSend) {
85+
TEST(PluginRegistrarTest, MessengerSend) {
5886
testing::ScopedStubFlutterApi scoped_api_stub(std::make_unique<TestApi>());
5987
auto test_api = static_cast<TestApi*>(scoped_api_stub.stub());
6088

@@ -70,7 +98,7 @@ TEST(MethodCallTest, MessengerSend) {
7098

7199
// Tests that the registrar returns a messenger that passes callback
72100
// registration and unregistration through to the C API.
73-
TEST(MethodCallTest, MessengerSetMessageHandler) {
101+
TEST(PluginRegistrarTest, MessengerSetMessageHandler) {
74102
testing::ScopedStubFlutterApi scoped_api_stub(std::make_unique<TestApi>());
75103
auto test_api = static_cast<TestApi*>(scoped_api_stub.stub());
76104

@@ -85,11 +113,70 @@ TEST(MethodCallTest, MessengerSetMessageHandler) {
85113
const size_t message_size,
86114
BinaryReply reply) {};
87115
messenger->SetMessageHandler(channel_name, std::move(binary_handler));
88-
EXPECT_NE(test_api->last_callback_set(), nullptr);
116+
EXPECT_NE(test_api->last_message_callback_set(), nullptr);
89117

90118
// Unregister.
91119
messenger->SetMessageHandler(channel_name, nullptr);
92-
EXPECT_EQ(test_api->last_callback_set(), nullptr);
120+
EXPECT_EQ(test_api->last_message_callback_set(), nullptr);
121+
}
122+
123+
// Tests that the registrar manager returns the same instance when getting
124+
// the wrapper for the same reference.
125+
TEST(PluginRegistrarTest, ManagerSameInstance) {
126+
PluginRegistrarManager* manager = PluginRegistrarManager::GetInstance();
127+
manager->Reset();
128+
129+
testing::ScopedStubFlutterApi scoped_api_stub(std::make_unique<TestApi>());
130+
131+
auto dummy_registrar_handle =
132+
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1);
133+
134+
EXPECT_EQ(manager->GetRegistrar<PluginRegistrar>(dummy_registrar_handle),
135+
manager->GetRegistrar<PluginRegistrar>(dummy_registrar_handle));
136+
}
137+
138+
// Tests that the registrar manager returns different objects for different
139+
// references.
140+
TEST(PluginRegistrarTest, ManagerDifferentInstances) {
141+
PluginRegistrarManager* manager = PluginRegistrarManager::GetInstance();
142+
manager->Reset();
143+
144+
testing::ScopedStubFlutterApi scoped_api_stub(std::make_unique<TestApi>());
145+
146+
auto dummy_registrar_handle_a =
147+
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1);
148+
auto dummy_registrar_handle_b =
149+
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(2);
150+
151+
EXPECT_NE(manager->GetRegistrar<PluginRegistrar>(dummy_registrar_handle_a),
152+
manager->GetRegistrar<PluginRegistrar>(dummy_registrar_handle_b));
153+
}
154+
155+
// Tests that the registrar manager deletes wrappers when the underlying
156+
// reference is destroyed.
157+
TEST(PluginRegistrarTest, ManagerRemovesOnDestruction) {
158+
PluginRegistrarManager* manager = PluginRegistrarManager::GetInstance();
159+
manager->Reset();
160+
161+
testing::ScopedStubFlutterApi scoped_api_stub(std::make_unique<TestApi>());
162+
auto test_api = static_cast<TestApi*>(scoped_api_stub.stub());
163+
164+
auto dummy_registrar_handle =
165+
reinterpret_cast<FlutterDesktopPluginRegistrarRef>(1);
166+
auto* wrapper =
167+
manager->GetRegistrar<TestPluginRegistrar>(dummy_registrar_handle);
168+
169+
// Simulate destruction of the reference, and ensure that the wrapper
170+
// is destroyed.
171+
EXPECT_NE(test_api->last_destruction_callback_set(), nullptr);
172+
bool destroyed = false;
173+
wrapper->SetDestructionCallback([&destroyed]() { destroyed = true; });
174+
test_api->last_destruction_callback_set()(dummy_registrar_handle);
175+
EXPECT_EQ(destroyed, true);
176+
177+
// Requesting the wrapper should now create a new object.
178+
EXPECT_NE(manager->GetRegistrar<TestPluginRegistrar>(dummy_registrar_handle),
179+
nullptr);
93180
}
94181

95182
} // namespace flutter

shell/platform/common/cpp/client_wrapper/testing/stub_flutter_api.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger(
4444
return reinterpret_cast<FlutterDesktopMessengerRef>(1);
4545
}
4646

47+
void FlutterDesktopRegistrarSetDestructionHandler(
48+
FlutterDesktopPluginRegistrarRef registrar,
49+
FlutterDesktopOnRegistrarDestroyed callback) {
50+
if (s_stub_implementation) {
51+
s_stub_implementation->RegistrarSetDestructionHandler(callback);
52+
}
53+
}
54+
4755
void FlutterDesktopRegistrarEnableInputBlocking(
4856
FlutterDesktopPluginRegistrarRef registrar,
4957
const char* channel) {

shell/platform/common/cpp/client_wrapper/testing/stub_flutter_api.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ class StubFlutterApi {
3434

3535
virtual ~StubFlutterApi() {}
3636

37+
// Called for FlutterDesktopRegistrarSetDestructionHandler.
38+
virtual void RegistrarSetDestructionHandler(
39+
FlutterDesktopOnRegistrarDestroyed callback) {}
40+
3741
// Called for FlutterDesktopRegistrarEnableInputBlocking.
3842
virtual void RegistrarEnableInputBlocking(const char* channel) {}
3943

shell/platform/common/cpp/public/flutter_plugin_registrar.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,19 @@ extern "C" {
1818
// Opaque reference to a plugin registrar.
1919
typedef struct FlutterDesktopPluginRegistrar* FlutterDesktopPluginRegistrarRef;
2020

21+
// Function pointer type for registrar destruction callback.
22+
typedef void (*FlutterDesktopOnRegistrarDestroyed)(
23+
FlutterDesktopPluginRegistrarRef);
24+
2125
// Returns the engine messenger associated with this registrar.
2226
FLUTTER_EXPORT FlutterDesktopMessengerRef
2327
FlutterDesktopRegistrarGetMessenger(FlutterDesktopPluginRegistrarRef registrar);
2428

29+
// Registers a callback to be called when the plugin registrar is destroyed.
30+
FLUTTER_EXPORT void FlutterDesktopRegistrarSetDestructionHandler(
31+
FlutterDesktopPluginRegistrarRef registrar,
32+
FlutterDesktopOnRegistrarDestroyed callback);
33+
2534
// Enables input blocking on the given channel.
2635
//
2736
// If set, then the Flutter window will disable input callbacks

shell/platform/glfw/flutter_glfw.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ struct FlutterDesktopPluginRegistrar {
118118

119119
// The handle for the window associated with this registrar.
120120
FlutterDesktopWindow* window;
121+
122+
// Callback to be called on registrar destruction.
123+
FlutterDesktopOnRegistrarDestroyed destruction_handler;
121124
};
122125

123126
// State associated with the messenger used to communicate with the engine.
@@ -682,6 +685,11 @@ FlutterDesktopWindowControllerRef FlutterDesktopCreateWindow(
682685
}
683686

684687
void FlutterDesktopDestroyWindow(FlutterDesktopWindowControllerRef controller) {
688+
FlutterDesktopPluginRegistrarRef registrar =
689+
controller->plugin_registrar.get();
690+
if (registrar->destruction_handler) {
691+
registrar->destruction_handler(registrar);
692+
}
685693
FlutterEngineShutdown(controller->engine);
686694
delete controller;
687695
}
@@ -832,6 +840,12 @@ FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger(
832840
return registrar->messenger.get();
833841
}
834842

843+
void FlutterDesktopRegistrarSetDestructionHandler(
844+
FlutterDesktopPluginRegistrarRef registrar,
845+
FlutterDesktopOnRegistrarDestroyed callback) {
846+
registrar->destruction_handler = callback;
847+
}
848+
835849
FlutterDesktopWindowRef FlutterDesktopRegistrarGetWindow(
836850
FlutterDesktopPluginRegistrarRef registrar) {
837851
return registrar->window;

shell/platform/windows/flutter_windows.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger(
248248
return registrar->messenger.get();
249249
}
250250

251+
void FlutterDesktopRegistrarSetDestructionHandler(
252+
FlutterDesktopPluginRegistrarRef registrar,
253+
FlutterDesktopOnRegistrarDestroyed callback) {
254+
registrar->destruction_handler = callback;
255+
}
256+
251257
FlutterDesktopViewRef FlutterDesktopRegistrarGetView(
252258
FlutterDesktopPluginRegistrarRef registrar) {
253259
return registrar->window;

shell/platform/windows/win32_flutter_window.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ Win32FlutterWindow::Win32FlutterWindow(int width, int height) {
1515

1616
Win32FlutterWindow::~Win32FlutterWindow() {
1717
DestroyRenderSurface();
18+
if (plugin_registrar_ && plugin_registrar_->destruction_handler) {
19+
plugin_registrar_->destruction_handler(plugin_registrar_.get());
20+
}
1821
}
1922

2023
FlutterDesktopViewControllerRef Win32FlutterWindow::CreateWin32FlutterWindow(

0 commit comments

Comments
 (0)