| diff --git a/BUILD.gn b/BUILD.gn |
| index edbeb6803..e280ae901 100644 |
| --- a/BUILD.gn |
| +++ b/BUILD.gn |
| @@ -98,6 +98,7 @@ group("flutter") { |
| "//flutter/shell/platform/embedder:embedder_proctable_unittests", |
| "//flutter/shell/platform/embedder:embedder_unittests", |
| "//flutter/testing:testing_unittests", |
| + "//flutter/third_party/tonic/tests:tonic_unittests", |
| "//flutter/third_party/txt:txt_unittests", |
| ] |
| |
| diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter |
| index f13271aa9..6b84f6a83 100755 |
| --- a/ci/licenses_golden/licenses_flutter |
| +++ b/ci/licenses_golden/licenses_flutter |
| @@ -1463,6 +1463,8 @@ FILE: ../../../flutter/third_party/tonic/dart_persistent_value.cc |
| FILE: ../../../flutter/third_party/tonic/dart_persistent_value.h |
| FILE: ../../../flutter/third_party/tonic/dart_state.cc |
| FILE: ../../../flutter/third_party/tonic/dart_state.h |
| +FILE: ../../../flutter/third_party/tonic/dart_weak_persistent_value.cc |
| +FILE: ../../../flutter/third_party/tonic/dart_weak_persistent_value.h |
| FILE: ../../../flutter/third_party/tonic/dart_wrappable.cc |
| FILE: ../../../flutter/third_party/tonic/dart_wrappable.h |
| FILE: ../../../flutter/third_party/tonic/dart_wrapper_info.h |
| diff --git a/lib/ui/painting/image_decoder.cc b/lib/ui/painting/image_decoder.cc |
| index d6c4a668f..b6ffb20f5 100644 |
| --- a/lib/ui/painting/image_decoder.cc |
| +++ b/lib/ui/painting/image_decoder.cc |
| @@ -224,12 +224,13 @@ void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor, |
| FML_DCHECK(callback); |
| FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); |
| |
| - // Always service the callback on the UI thread. |
| - auto result = [callback, ui_runner = runners_.GetUITaskRunner()]( |
| + // Always service the callback (and cleanup the descriptor) on the UI thread. |
| + auto result = [callback, descriptor, ui_runner = runners_.GetUITaskRunner()]( |
| SkiaGPUObject<SkImage> image, |
| fml::tracing::TraceFlow flow) { |
| - ui_runner->PostTask(fml::MakeCopyable( |
| - [callback, image = std::move(image), flow = std::move(flow)]() mutable { |
| + ui_runner->PostTask( |
| + fml::MakeCopyable([callback, descriptor, image = std::move(image), |
| + flow = std::move(flow)]() mutable { |
| // We are going to terminate the trace flow here. Flows cannot |
| // terminate without a base trace. Add one explicitly. |
| TRACE_EVENT0("flutter", "ImageDecodeCallback"); |
| diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc |
| index c17a784c5..8a7653f0b 100644 |
| --- a/lib/ui/painting/image_encoding.cc |
| +++ b/lib/ui/painting/image_encoding.cc |
| @@ -35,9 +35,7 @@ enum ImageByteFormat { |
| kPNG, |
| }; |
| |
| -void FinalizeSkData(void* isolate_callback_data, |
| - Dart_WeakPersistentHandle handle, |
| - void* peer) { |
| +void FinalizeSkData(void* isolate_callback_data, void* peer) { |
| SkData* buffer = reinterpret_cast<SkData*>(peer); |
| buffer->unref(); |
| } |
| diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc |
| index b834957a9..8a6215110 100644 |
| --- a/runtime/dart_isolate.cc |
| +++ b/runtime/dart_isolate.cc |
| @@ -981,6 +981,11 @@ void DartIsolate::AddIsolateShutdownCallback(const fml::closure& closure) { |
| } |
| |
| void DartIsolate::OnShutdownCallback() { |
| + tonic::DartState* state = tonic::DartState::Current(); |
| + if (state != nullptr) { |
| + state->SetIsShuttingDown(); |
| + } |
| + |
| { |
| tonic::DartApiScope api_scope; |
| Dart_Handle sticky_error = Dart_GetStickyError(); |
| diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc |
| index ae9849a5a..b0c61a1b5 100644 |
| --- a/shell/platform/embedder/embedder.cc |
| +++ b/shell/platform/embedder/embedder.cc |
| @@ -1922,8 +1922,7 @@ FlutterEngineResult FlutterEnginePostDartObject( |
| dart_object.value.as_external_typed_data.data = buffer; |
| dart_object.value.as_external_typed_data.peer = peer; |
| dart_object.value.as_external_typed_data.callback = |
| - +[](void* unused_isolate_callback_data, |
| - Dart_WeakPersistentHandle unused_handle, void* peer) { |
| + +[](void* unused_isolate_callback_data, void* peer) { |
| auto typed_peer = reinterpret_cast<ExternalTypedDataPeer*>(peer); |
| typed_peer->trampoline(typed_peer->user_data); |
| delete typed_peer; |
| diff --git a/shell/platform/fuchsia/dart_runner/dart_runner.cc b/shell/platform/fuchsia/dart_runner/dart_runner.cc |
| index 76d85405a..415d2885c 100644 |
| --- a/shell/platform/fuchsia/dart_runner/dart_runner.cc |
| +++ b/shell/platform/fuchsia/dart_runner/dart_runner.cc |
| @@ -86,6 +86,10 @@ void IsolateShutdownCallback(void* isolate_group_data, void* isolate_data) { |
| tonic::DartMicrotaskQueue::GetForCurrentThread()->Destroy(); |
| async_loop_quit(loop); |
| } |
| + |
| + auto state = |
| + static_cast<std::shared_ptr<tonic::DartState>*>(isolate_group_data); |
| + state->get()->SetIsShuttingDown(); |
| } |
| |
| void IsolateGroupCleanupCallback(void* isolate_group_data) { |
| diff --git a/testing/run_tests.py b/testing/run_tests.py |
| index 06b8256d0..8bdd114f4 100755 |
| --- a/testing/run_tests.py |
| +++ b/testing/run_tests.py |
| @@ -132,6 +132,8 @@ def RunCCTests(build_dir, filter): |
| |
| RunEngineExecutable(build_dir, 'runtime_unittests', filter, shuffle_flags) |
| |
| + RunEngineExecutable(build_dir, 'tonic_unittests', filter, shuffle_flags) |
| + |
| if not IsWindows(): |
| # https://github.com/flutter/flutter/issues/36295 |
| RunEngineExecutable(build_dir, 'shell_unittests', filter, shuffle_flags) |
| diff --git a/third_party/tonic/BUILD.gn b/third_party/tonic/BUILD.gn |
| index f5bedda8d..9a6abda8b 100644 |
| --- a/third_party/tonic/BUILD.gn |
| +++ b/third_party/tonic/BUILD.gn |
| @@ -35,6 +35,8 @@ source_set("tonic") { |
| "dart_persistent_value.h", |
| "dart_state.cc", |
| "dart_state.h", |
| + "dart_weak_persistent_value.cc", |
| + "dart_weak_persistent_value.h", |
| "dart_wrappable.cc", |
| "dart_wrappable.h", |
| "dart_wrapper_info.h", |
| diff --git a/third_party/tonic/dart_state.cc b/third_party/tonic/dart_state.cc |
| index b711a2297..3f37685c5 100644 |
| --- a/third_party/tonic/dart_state.cc |
| +++ b/third_party/tonic/dart_state.cc |
| @@ -27,7 +27,8 @@ DartState::DartState(int dirfd, |
| message_handler_(new DartMessageHandler()), |
| file_loader_(new FileLoader(dirfd)), |
| message_epilogue_(message_epilogue), |
| - has_set_return_code_(false) {} |
| + has_set_return_code_(false), |
| + is_shutting_down_(false) {} |
| |
| DartState::~DartState() {} |
| |
| diff --git a/third_party/tonic/dart_state.h b/third_party/tonic/dart_state.h |
| index 845914937..1984a66bf 100644 |
| --- a/third_party/tonic/dart_state.h |
| +++ b/third_party/tonic/dart_state.h |
| @@ -5,6 +5,7 @@ |
| #ifndef LIB_TONIC_DART_STATE_H_ |
| #define LIB_TONIC_DART_STATE_H_ |
| |
| +#include <atomic> |
| #include <functional> |
| #include <memory> |
| |
| @@ -68,6 +69,9 @@ class DartState : public std::enable_shared_from_this<DartState> { |
| void SetReturnCodeCallback(std::function<void(uint32_t)> callback); |
| bool has_set_return_code() const { return has_set_return_code_; } |
| |
| + void SetIsShuttingDown() { is_shutting_down_ = true; } |
| + bool IsShuttingDown() { return is_shutting_down_; } |
| + |
| virtual void DidSetIsolate(); |
| |
| static Dart_Handle HandleLibraryTag(Dart_LibraryTag tag, |
| @@ -83,6 +87,7 @@ class DartState : public std::enable_shared_from_this<DartState> { |
| std::function<void(Dart_Handle)> message_epilogue_; |
| std::function<void(uint32_t)> set_return_code_callback_; |
| bool has_set_return_code_; |
| + std::atomic<bool> is_shutting_down_; |
| |
| protected: |
| TONIC_DISALLOW_COPY_AND_ASSIGN(DartState); |
| diff --git a/third_party/tonic/dart_weak_persistent_value.cc b/third_party/tonic/dart_weak_persistent_value.cc |
| new file mode 100644 |
| index 000000000..a2664d3e0 |
| --- /dev/null |
| +++ b/third_party/tonic/dart_weak_persistent_value.cc |
| @@ -0,0 +1,70 @@ |
| +// Copyright 2013 The Flutter Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "tonic/dart_weak_persistent_value.h" |
| + |
| +#include "tonic/dart_state.h" |
| +#include "tonic/scopes/dart_isolate_scope.h" |
| + |
| +namespace tonic { |
| + |
| +DartWeakPersistentValue::DartWeakPersistentValue() : handle_(nullptr) {} |
| + |
| +DartWeakPersistentValue::~DartWeakPersistentValue() { |
| + Clear(); |
| +} |
| + |
| +void DartWeakPersistentValue::Set(DartState* dart_state, |
| + Dart_Handle object, |
| + void* peer, |
| + intptr_t external_allocation_size, |
| + Dart_HandleFinalizer callback) { |
| + TONIC_DCHECK(is_empty()); |
| + dart_state_ = dart_state->GetWeakPtr(); |
| + handle_ = Dart_NewWeakPersistentHandle(object, peer, external_allocation_size, |
| + callback); |
| +} |
| + |
| +void DartWeakPersistentValue::Clear() { |
| + if (!handle_) { |
| + return; |
| + } |
| + |
| + auto dart_state = dart_state_.lock(); |
| + if (!dart_state) { |
| + // The DartVM that the handle used to belong to has been shut down and that |
| + // handle has already been deleted. |
| + handle_ = nullptr; |
| + return; |
| + } |
| + |
| + // The DartVM frees the handles during shutdown and calls the finalizers. |
| + // Freeing handles during shutdown would fail. |
| + if (!dart_state->IsShuttingDown()) { |
| + if (Dart_CurrentIsolateGroup()) { |
| + Dart_DeleteWeakPersistentHandle(handle_); |
| + } else { |
| + // If we are not on the mutator thread, this will fail. The caller must |
| + // ensure to be on the mutator thread. |
| + DartIsolateScope scope(dart_state->isolate()); |
| + Dart_DeleteWeakPersistentHandle(handle_); |
| + } |
| + } |
| + // If it's shutting down, the handle will be deleted already. |
| + |
| + dart_state_.reset(); |
| + handle_ = nullptr; |
| +} |
| + |
| +Dart_Handle DartWeakPersistentValue::Get() { |
| + auto dart_state = dart_state_.lock(); |
| + TONIC_DCHECK(dart_state); |
| + TONIC_DCHECK(!dart_state->IsShuttingDown()); |
| + if (!handle_) { |
| + return nullptr; |
| + } |
| + return Dart_HandleFromWeakPersistent(handle_); |
| +} |
| + |
| +} // namespace tonic |
| diff --git a/third_party/tonic/dart_weak_persistent_value.h b/third_party/tonic/dart_weak_persistent_value.h |
| new file mode 100644 |
| index 000000000..5f8aed5ee |
| --- /dev/null |
| +++ b/third_party/tonic/dart_weak_persistent_value.h |
| @@ -0,0 +1,48 @@ |
| +// Copyright 2013 The Flutter Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#ifndef LIB_TONIC_DART_WEAK_PERSISTENT_VALUE_H_ |
| +#define LIB_TONIC_DART_WEAK_PERSISTENT_VALUE_H_ |
| + |
| +#include <memory> |
| + |
| +#include "third_party/dart/runtime/include/dart_api.h" |
| +#include "tonic/common/macros.h" |
| + |
| +namespace tonic { |
| +class DartState; |
| + |
| +// DartWeakPersistentValue is a bookkeeping class to help pair calls to |
| +// Dart_NewWeakPersistentHandle with Dart_DeleteWeakPersistentHandle even in |
| +// the case if IsolateGroup shutdown. Consider using this class instead of |
| +// holding a Dart_PersistentHandle directly so that you don't leak the |
| +// Dart_WeakPersistentHandle. |
| +class DartWeakPersistentValue { |
| + public: |
| + DartWeakPersistentValue(); |
| + ~DartWeakPersistentValue(); |
| + |
| + Dart_WeakPersistentHandle value() const { return handle_; } |
| + bool is_empty() const { return handle_ == nullptr; } |
| + |
| + void Set(DartState* dart_state, |
| + Dart_Handle object, |
| + void* peer, |
| + intptr_t external_allocation_size, |
| + Dart_HandleFinalizer callback); |
| + void Clear(); |
| + Dart_Handle Get(); |
| + |
| + const std::weak_ptr<DartState>& dart_state() const { return dart_state_; } |
| + |
| + private: |
| + std::weak_ptr<DartState> dart_state_; |
| + Dart_WeakPersistentHandle handle_; |
| + |
| + TONIC_DISALLOW_COPY_AND_ASSIGN(DartWeakPersistentValue); |
| +}; |
| + |
| +} // namespace tonic |
| + |
| +#endif // LIB_TONIC_DART_WEAK_PERSISTENT_VALUE_H_ |
| diff --git a/third_party/tonic/dart_wrappable.cc b/third_party/tonic/dart_wrappable.cc |
| index 3bdfe3e6e..858215c11 100644 |
| --- a/third_party/tonic/dart_wrappable.cc |
| +++ b/third_party/tonic/dart_wrappable.cc |
| @@ -12,12 +12,17 @@ |
| namespace tonic { |
| |
| DartWrappable::~DartWrappable() { |
| - TONIC_CHECK(!dart_wrapper_); |
| + // Calls the destructor of dart_wrapper_ to delete WeakPersistentHandle. |
| } |
| |
| // TODO(dnfield): Delete this. https://github.com/flutter/flutter/issues/50997 |
| Dart_Handle DartWrappable::CreateDartWrapper(DartState* dart_state) { |
| - TONIC_DCHECK(!dart_wrapper_); |
| + if (!dart_wrapper_.is_empty()) { |
| + // Any previously given out wrapper must have been GCed. |
| + TONIC_DCHECK(Dart_IsNull(dart_wrapper_.Get())); |
| + dart_wrapper_.Clear(); |
| + } |
| + |
| const DartWrapperInfo& info = GetDartWrapperInfo(); |
| |
| Dart_PersistentHandle type = dart_state->class_library().GetClass(info); |
| @@ -36,14 +41,19 @@ Dart_Handle DartWrappable::CreateDartWrapper(DartState* dart_state) { |
| TONIC_DCHECK(!LogIfError(res)); |
| |
| this->RetainDartWrappableReference(); // Balanced in FinalizeDartWrapper. |
| - dart_wrapper_ = Dart_NewWeakPersistentHandle( |
| - wrapper, this, GetAllocationSize(), &FinalizeDartWrapper); |
| + dart_wrapper_.Set(dart_state, wrapper, this, GetAllocationSize(), |
| + &FinalizeDartWrapper); |
| |
| return wrapper; |
| } |
| |
| void DartWrappable::AssociateWithDartWrapper(Dart_Handle wrapper) { |
| - TONIC_DCHECK(!dart_wrapper_); |
| + if (!dart_wrapper_.is_empty()) { |
| + // Any previously given out wrapper must have been GCed. |
| + TONIC_DCHECK(Dart_IsNull(dart_wrapper_.Get())); |
| + dart_wrapper_.Clear(); |
| + } |
| + |
| TONIC_CHECK(!LogIfError(wrapper)); |
| |
| const DartWrapperInfo& info = GetDartWrapperInfo(); |
| @@ -54,26 +64,25 @@ void DartWrappable::AssociateWithDartWrapper(Dart_Handle wrapper) { |
| wrapper, kWrapperInfoIndex, reinterpret_cast<intptr_t>(&info)))); |
| |
| this->RetainDartWrappableReference(); // Balanced in FinalizeDartWrapper. |
| - dart_wrapper_ = Dart_NewWeakPersistentHandle( |
| - wrapper, this, GetAllocationSize(), &FinalizeDartWrapper); |
| + |
| + DartState* dart_state = DartState::Current(); |
| + dart_wrapper_.Set(dart_state, wrapper, this, GetAllocationSize(), |
| + &FinalizeDartWrapper); |
| } |
| |
| void DartWrappable::ClearDartWrapper() { |
| - TONIC_DCHECK(dart_wrapper_); |
| - Dart_Handle wrapper = Dart_HandleFromWeakPersistent(dart_wrapper_); |
| + TONIC_DCHECK(!dart_wrapper_.is_empty()); |
| + Dart_Handle wrapper = dart_wrapper_.Get(); |
| TONIC_CHECK(!LogIfError(Dart_SetNativeInstanceField(wrapper, kPeerIndex, 0))); |
| TONIC_CHECK( |
| !LogIfError(Dart_SetNativeInstanceField(wrapper, kWrapperInfoIndex, 0))); |
| - Dart_DeleteWeakPersistentHandle(dart_wrapper_); |
| - dart_wrapper_ = nullptr; |
| + dart_wrapper_.Clear(); |
| this->ReleaseDartWrappableReference(); |
| } |
| |
| void DartWrappable::FinalizeDartWrapper(void* isolate_callback_data, |
| - Dart_WeakPersistentHandle wrapper, |
| void* peer) { |
| DartWrappable* wrappable = reinterpret_cast<DartWrappable*>(peer); |
| - wrappable->dart_wrapper_ = nullptr; |
| wrappable->ReleaseDartWrappableReference(); // Balanced in CreateDartWrapper. |
| } |
| |
| diff --git a/third_party/tonic/dart_wrappable.h b/third_party/tonic/dart_wrappable.h |
| index a036abb85..f944dacef 100644 |
| --- a/third_party/tonic/dart_wrappable.h |
| +++ b/third_party/tonic/dart_wrappable.h |
| @@ -9,6 +9,7 @@ |
| #include "tonic/common/macros.h" |
| #include "tonic/converter/dart_converter.h" |
| #include "tonic/dart_state.h" |
| +#include "tonic/dart_weak_persistent_value.h" |
| #include "tonic/dart_wrapper_info.h" |
| #include "tonic/logging/dart_error.h" |
| |
| @@ -26,7 +27,7 @@ class DartWrappable { |
| kNumberOfNativeFields, |
| }; |
| |
| - DartWrappable() : dart_wrapper_(nullptr) {} |
| + DartWrappable() : dart_wrapper_(DartWeakPersistentValue()) {} |
| |
| // Subclasses that wish to expose a new interface must override this function |
| // and provide information about their wrapper. There is no need to call your |
| @@ -49,7 +50,9 @@ class DartWrappable { |
| Dart_Handle CreateDartWrapper(DartState* dart_state); |
| void AssociateWithDartWrapper(Dart_Handle wrappable); |
| void ClearDartWrapper(); // Warning: Might delete this. |
| - Dart_WeakPersistentHandle dart_wrapper() const { return dart_wrapper_; } |
| + Dart_WeakPersistentHandle dart_wrapper() const { |
| + return dart_wrapper_.value(); |
| + } |
| |
| protected: |
| virtual ~DartWrappable(); |
| @@ -59,11 +62,9 @@ class DartWrappable { |
| const tonic::DartWrapperInfo& wrapper_info); |
| |
| private: |
| - static void FinalizeDartWrapper(void* isolate_callback_data, |
| - Dart_WeakPersistentHandle wrapper, |
| - void* peer); |
| + static void FinalizeDartWrapper(void* isolate_callback_data, void* peer); |
| |
| - Dart_WeakPersistentHandle dart_wrapper_; |
| + DartWeakPersistentValue dart_wrapper_; |
| |
| TONIC_DISALLOW_COPY_AND_ASSIGN(DartWrappable); |
| }; |
| @@ -103,22 +104,36 @@ struct DartConverter< |
| typename std::enable_if< |
| std::is_convertible<T*, const DartWrappable*>::value>::type> { |
| static Dart_Handle ToDart(DartWrappable* val) { |
| - if (!val) |
| + if (!val) { |
| return Dart_Null(); |
| - if (Dart_WeakPersistentHandle wrapper = val->dart_wrapper()) |
| - return Dart_HandleFromWeakPersistent(wrapper); |
| + } |
| + if (Dart_WeakPersistentHandle wrapper = val->dart_wrapper()) { |
| + auto strong_handle = Dart_HandleFromWeakPersistent(wrapper); |
| + if (!Dart_IsNull(strong_handle)) { |
| + return strong_handle; |
| + } |
| + // After the weak referenced object has been GCed, the handle points to |
| + // Dart_Null(). Fall through create a new wrapper object. |
| + } |
| return val->CreateDartWrapper(DartState::Current()); |
| } |
| |
| static void SetReturnValue(Dart_NativeArguments args, |
| DartWrappable* val, |
| bool auto_scope = true) { |
| - if (!val) |
| + if (!val) { |
| Dart_SetReturnValue(args, Dart_Null()); |
| - else if (Dart_WeakPersistentHandle wrapper = val->dart_wrapper()) |
| - Dart_SetWeakHandleReturnValue(args, wrapper); |
| - else |
| - Dart_SetReturnValue(args, val->CreateDartWrapper(DartState::Current())); |
| + return; |
| + } else if (Dart_WeakPersistentHandle wrapper = val->dart_wrapper()) { |
| + auto strong_handle = Dart_HandleFromWeakPersistent(wrapper); |
| + if (!Dart_IsNull(strong_handle)) { |
| + Dart_SetReturnValue(args, strong_handle); |
| + return; |
| + } |
| + // After the weak referenced object has been GCed, the handle points to |
| + // Dart_Null(). Fall through create a new wrapper object. |
| + } |
| + Dart_SetReturnValue(args, val->CreateDartWrapper(DartState::Current())); |
| } |
| |
| static T* FromDart(Dart_Handle handle) { |
| diff --git a/third_party/tonic/tests/BUILD.gn b/third_party/tonic/tests/BUILD.gn |
| new file mode 100644 |
| index 000000000..359deb13b |
| --- /dev/null |
| +++ b/third_party/tonic/tests/BUILD.gn |
| @@ -0,0 +1,33 @@ |
| +# Copyright 2013 The Flutter Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +import("//flutter/testing/testing.gni") |
| + |
| +test_fixtures("tonic_fixtures") { |
| + dart_main = "fixtures/tonic_test.dart" |
| + fixtures = [] |
| +} |
| + |
| +executable("tonic_unittests") { |
| + testonly = true |
| + |
| + public_configs = [ "//flutter:export_dynamic_symbols" ] |
| + |
| + sources = [ |
| + "dart_state_unittest.cc", |
| + "dart_weak_persistent_handle_unittest.cc", |
| + ] |
| + |
| + public_deps = [ |
| + ":tonic_fixtures", |
| + "//flutter/runtime:libdart", |
| + "//flutter/runtime:runtime", |
| + "//flutter/testing", |
| + "//flutter/testing:fixture_test", |
| + "//third_party/dart/runtime:dart_api", |
| + "//third_party/googletest:gtest", |
| + ] |
| + |
| + deps = [ "../:tonic" ] |
| +} |
| diff --git a/third_party/tonic/tests/dart_state_unittest.cc b/third_party/tonic/tests/dart_state_unittest.cc |
| new file mode 100644 |
| index 000000000..3d053de40 |
| --- /dev/null |
| +++ b/third_party/tonic/tests/dart_state_unittest.cc |
| @@ -0,0 +1,61 @@ |
| +// Copyright 2013 The Flutter Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "flutter/common/task_runners.h" |
| +#include "flutter/runtime/dart_vm_lifecycle.h" |
| +#include "flutter/runtime/isolate_configuration.h" |
| +#include "flutter/testing/fixture_test.h" |
| + |
| +namespace flutter { |
| +namespace testing { |
| + |
| +using DartState = FixtureTest; |
| + |
| +TEST_F(DartState, IsShuttingDown) { |
| + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); |
| + auto settings = CreateSettingsForFixture(); |
| + auto vm_ref = DartVMRef::Create(settings); |
| + ASSERT_TRUE(vm_ref); |
| + auto vm_data = vm_ref.GetVMData(); |
| + ASSERT_TRUE(vm_data); |
| + TaskRunners task_runners(GetCurrentTestName(), // |
| + GetCurrentTaskRunner(), // |
| + GetCurrentTaskRunner(), // |
| + GetCurrentTaskRunner(), // |
| + GetCurrentTaskRunner() // |
| + ); |
| + auto isolate_configuration = |
| + IsolateConfiguration::InferFromSettings(settings); |
| + auto weak_isolate = DartIsolate::CreateRunningRootIsolate( |
| + vm_data->GetSettings(), // settings |
| + vm_data->GetIsolateSnapshot(), // isolate snapshot |
| + std::move(task_runners), // task runners |
| + nullptr, // window |
| + {}, // snapshot delegate |
| + {}, // hint freed delegate |
| + {}, // io manager |
| + {}, // unref queue |
| + {}, // image decoder |
| + "main.dart", // advisory uri |
| + "main", // advisory entrypoint |
| + DartIsolate::Flags{}, // flags |
| + settings.isolate_create_callback, // isolate create callback |
| + settings.isolate_shutdown_callback, // isolate shutdown callback |
| + "main", // dart entrypoint |
| + std::nullopt, // dart entrypoint library |
| + std::move(isolate_configuration) // isolate configuration |
| + ); |
| + auto root_isolate = weak_isolate.lock(); |
| + ASSERT_TRUE(root_isolate); |
| + |
| + tonic::DartState* dart_state = root_isolate.get(); |
| + ASSERT_FALSE(dart_state->IsShuttingDown()); |
| + |
| + ASSERT_TRUE(root_isolate->Shutdown()); |
| + |
| + ASSERT_TRUE(dart_state->IsShuttingDown()); |
| +} |
| + |
| +} // namespace testing |
| +} // namespace flutter |
| diff --git a/third_party/tonic/tests/dart_weak_persistent_handle_unittest.cc b/third_party/tonic/tests/dart_weak_persistent_handle_unittest.cc |
| new file mode 100644 |
| index 000000000..65d5e116c |
| --- /dev/null |
| +++ b/third_party/tonic/tests/dart_weak_persistent_handle_unittest.cc |
| @@ -0,0 +1,167 @@ |
| +// Copyright 2013 The Flutter Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "flutter/testing/dart_isolate_runner.h" |
| +#include "flutter/testing/fixture_test.h" |
| + |
| +namespace flutter { |
| +namespace testing { |
| + |
| +class DartWeakPersistentHandle : public FixtureTest { |
| + public: |
| + DartWeakPersistentHandle() |
| + : settings_(CreateSettingsForFixture()), |
| + vm_(DartVMRef::Create(settings_)) {} |
| + |
| + ~DartWeakPersistentHandle() = default; |
| + |
| + [[nodiscard]] bool RunWithEntrypoint(const std::string& entrypoint) { |
| + if (running_isolate_) { |
| + return false; |
| + } |
| + auto thread = CreateNewThread(); |
| + TaskRunners single_threaded_task_runner(GetCurrentTestName(), thread, |
| + thread, thread, thread); |
| + auto isolate = |
| + RunDartCodeInIsolate(vm_, settings_, single_threaded_task_runner, |
| + entrypoint, {}, GetFixturesPath()); |
| + if (!isolate || isolate->get()->GetPhase() != DartIsolate::Phase::Running) { |
| + return false; |
| + } |
| + |
| + running_isolate_ = std::move(isolate); |
| + return true; |
| + } |
| + |
| + [[nodiscard]] bool RunInIsolateScope(std::function<bool(void)> closure) { |
| + return running_isolate_->RunInIsolateScope(closure); |
| + } |
| + |
| + private: |
| + Settings settings_; |
| + DartVMRef vm_; |
| + std::unique_ptr<AutoIsolateShutdown> running_isolate_; |
| + FML_DISALLOW_COPY_AND_ASSIGN(DartWeakPersistentHandle); |
| +}; |
| + |
| +void NopFinalizer(void* isolate_callback_data, void* peer) {} |
| + |
| +TEST_F(DartWeakPersistentHandle, ClearImmediately) { |
| + auto weak_persistent_value = tonic::DartWeakPersistentValue(); |
| + |
| + fml::AutoResetWaitableEvent event; |
| + |
| + AddNativeCallback( |
| + "GiveObjectToNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| + auto handle = Dart_GetNativeArgument(args, 0); |
| + |
| + auto dart_state = tonic::DartState::Current(); |
| + ASSERT_TRUE(dart_state); |
| + ASSERT_TRUE(tonic::DartState::Current()); |
| + weak_persistent_value.Set(dart_state, handle, nullptr, 0, NopFinalizer); |
| + |
| + weak_persistent_value.Clear(); |
| + |
| + event.Signal(); |
| + })); |
| + |
| + ASSERT_TRUE(RunWithEntrypoint("callGiveObjectToNative")); |
| + |
| + event.Wait(); |
| +} |
| + |
| +TEST_F(DartWeakPersistentHandle, ClearLaterCc) { |
| + auto weak_persistent_value = tonic::DartWeakPersistentValue(); |
| + |
| + fml::AutoResetWaitableEvent event; |
| + |
| + AddNativeCallback( |
| + "GiveObjectToNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| + auto handle = Dart_GetNativeArgument(args, 0); |
| + |
| + auto dart_state = tonic::DartState::Current(); |
| + ASSERT_TRUE(dart_state); |
| + ASSERT_TRUE(tonic::DartState::Current()); |
| + weak_persistent_value.Set(dart_state, handle, nullptr, 0, NopFinalizer); |
| + |
| + // Do not clear handle immediately. |
| + |
| + event.Signal(); |
| + })); |
| + |
| + ASSERT_TRUE(RunWithEntrypoint("callGiveObjectToNative")); |
| + |
| + event.Wait(); |
| + |
| + ASSERT_TRUE(RunInIsolateScope([&weak_persistent_value]() -> bool { |
| + // Clear on initiative of native. |
| + weak_persistent_value.Clear(); |
| + return true; |
| + })); |
| +} |
| + |
| +TEST_F(DartWeakPersistentHandle, ClearLaterDart) { |
| + auto weak_persistent_value = tonic::DartWeakPersistentValue(); |
| + |
| + fml::AutoResetWaitableEvent event; |
| + |
| + AddNativeCallback( |
| + "GiveObjectToNative", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| + auto handle = Dart_GetNativeArgument(args, 0); |
| + |
| + auto dart_state = tonic::DartState::Current(); |
| + ASSERT_TRUE(dart_state); |
| + ASSERT_TRUE(tonic::DartState::Current()); |
| + weak_persistent_value.Set(dart_state, handle, nullptr, 0, NopFinalizer); |
| + |
| + // Do not clear handle immediately. |
| + })); |
| + |
| + AddNativeCallback("SignalDone", |
| + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| + // Clear on initiative of Dart. |
| + weak_persistent_value.Clear(); |
| + |
| + event.Signal(); |
| + })); |
| + |
| + ASSERT_TRUE(RunWithEntrypoint("testClearLater")); |
| + |
| + event.Wait(); |
| +} |
| + |
| +// Handle outside the test body scope so it survives until isolate shutdown. |
| +tonic::DartWeakPersistentValue global_weak_persistent_value = |
| + tonic::DartWeakPersistentValue(); |
| + |
| +TEST_F(DartWeakPersistentHandle, ClearOnShutdown) { |
| + fml::AutoResetWaitableEvent event; |
| + |
| + AddNativeCallback("GiveObjectToNative", |
| + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { |
| + auto handle = Dart_GetNativeArgument(args, 0); |
| + |
| + auto dart_state = tonic::DartState::Current(); |
| + ASSERT_TRUE(dart_state); |
| + ASSERT_TRUE(tonic::DartState::Current()); |
| + |
| + // The test is repeated, ensure the global var is |
| + // cleared before use. |
| + global_weak_persistent_value.Clear(); |
| + |
| + global_weak_persistent_value.Set( |
| + dart_state, handle, nullptr, 0, NopFinalizer); |
| + |
| + // Do not clear handle, so it is cleared on shutdown. |
| + |
| + event.Signal(); |
| + })); |
| + |
| + ASSERT_TRUE(RunWithEntrypoint("callGiveObjectToNative")); |
| + |
| + event.Wait(); |
| +} |
| + |
| +} // namespace testing |
| +} // namespace flutter |
| diff --git a/third_party/tonic/tests/fixtures/tonic_test.dart b/third_party/tonic/tests/fixtures/tonic_test.dart |
| new file mode 100644 |
| index 000000000..83e92d4dc |
| --- /dev/null |
| +++ b/third_party/tonic/tests/fixtures/tonic_test.dart |
| @@ -0,0 +1,25 @@ |
| +// Copyright 2013 The Flutter Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +void main() {} |
| + |
| +class SomeClass { |
| + int i; |
| + SomeClass(this.i); |
| +} |
| + |
| +void giveObjectToNative(Object someObject) native 'GiveObjectToNative'; |
| + |
| +void signalDone() native 'SignalDone'; |
| + |
| +@pragma('vm:entry-point') |
| +void callGiveObjectToNative() { |
| + giveObjectToNative(SomeClass(123)); |
| +} |
| + |
| +@pragma('vm:entry-point') |
| +void testClearLater() { |
| + giveObjectToNative(SomeClass(123)); |
| + signalDone(); |
| +} |
| diff --git a/third_party/tonic/typed_data/dart_byte_data.cc b/third_party/tonic/typed_data/dart_byte_data.cc |
| index dc312de3f..cfb07399f 100644 |
| --- a/third_party/tonic/typed_data/dart_byte_data.cc |
| +++ b/third_party/tonic/typed_data/dart_byte_data.cc |
| @@ -16,9 +16,7 @@ namespace { |
| // with a buffer allocated outside the Dart heap. |
| const int kExternalSizeThreshold = 1000; |
| |
| -void FreeFinalizer(void* isolate_callback_data, |
| - Dart_WeakPersistentHandle handle, |
| - void* peer) { |
| +void FreeFinalizer(void* isolate_callback_data, void* peer) { |
| free(peer); |
| } |
| |