Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reland: Move asset opening to background thread, fix dart persistent value destruction #40183

Merged
merged 14 commits into from
Mar 9, 2023
Merged
10 changes: 10 additions & 0 deletions fml/concurrent_message_loop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,14 @@ void ConcurrentTaskRunner::PostTask(const fml::closure& task) {
task();
}

bool ConcurrentMessageLoop::RunsTasksOnCurrentThread() {
std::scoped_lock lock(tasks_mutex_);
for (const auto& worker_thread_id : worker_thread_ids_) {
if (worker_thread_id == std::this_thread::get_id()) {
return true;
}
}
return false;
}

} // namespace fml
2 changes: 2 additions & 0 deletions fml/concurrent_message_loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class ConcurrentMessageLoop

void PostTaskToAllWorkers(const fml::closure& task);

bool RunsTasksOnCurrentThread();

private:
friend ConcurrentTaskRunner;

Expand Down
7 changes: 6 additions & 1 deletion lib/ui/painting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6294,7 +6294,12 @@ class ImmutableBuffer extends NativeFieldWrapperClass1 {
final ImmutableBuffer instance = ImmutableBuffer._(0);
return _futurize((_Callback<int> callback) {
return instance._initFromAsset(encodedKey, callback);
}).then((int length) => instance.._length = length);
}).then((int length) {
if (length == -1) {
throw Exception('Asset not found');
}
return instance.._length = length;
});
}

/// Create a buffer from the file with [path].
Expand Down
87 changes: 65 additions & 22 deletions lib/ui/painting/immutable_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Dart_Handle ImmutableBuffer::init(Dart_Handle buffer_handle,
return Dart_Null();
}

Dart_Handle ImmutableBuffer::initFromAsset(Dart_Handle buffer_handle,
Dart_Handle ImmutableBuffer::initFromAsset(Dart_Handle raw_buffer_handle,
Dart_Handle asset_name_handle,
Dart_Handle callback_handle) {
UIDartState::ThrowIfUIOperationsProhibited();
Expand All @@ -62,21 +62,60 @@ Dart_Handle ImmutableBuffer::initFromAsset(Dart_Handle buffer_handle,
std::string asset_name = std::string{reinterpret_cast<const char*>(chars),
static_cast<size_t>(asset_length)};

std::shared_ptr<AssetManager> asset_manager = UIDartState::Current()
->platform_configuration()
->client()
->GetAssetManager();
std::unique_ptr<fml::Mapping> data = asset_manager->GetAsMapping(asset_name);
if (data == nullptr) {
return tonic::ToDart("Asset not found");
}
auto* dart_state = UIDartState::Current();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto* buffer_callback_ptr =
new tonic::DartPersistentValue(dart_state, callback_handle);
auto* buffer_handle_ptr =
new tonic::DartPersistentValue(dart_state, raw_buffer_handle);
auto asset_manager = UIDartState::Current()
->platform_configuration()
->client()
->GetAssetManager();

auto size = data->GetSize();
const void* bytes = static_cast<const void*>(data->GetMapping());
auto sk_data = MakeSkDataWithCopy(bytes, size);
auto buffer = fml::MakeRefCounted<ImmutableBuffer>(sk_data);
buffer->AssociateWithDartWrapper(buffer_handle);
tonic::DartInvoke(callback_handle, {tonic::ToDart(size)});
auto ui_task = fml::MakeCopyable(
[buffer_callback_ptr, buffer_handle_ptr](const sk_sp<SkData>& sk_data,
size_t buffer_size) mutable {
std::unique_ptr<tonic::DartPersistentValue> buffer_handle(
buffer_handle_ptr);
std::unique_ptr<tonic::DartPersistentValue> buffer_callback(
buffer_callback_ptr);

auto dart_state = buffer_callback->dart_state().lock();
if (!dart_state) {
return;
}
tonic::DartState::Scope scope(dart_state);

if (!sk_data) {
// -1 is used as a sentinel that the file could not be opened.
tonic::DartInvoke(buffer_callback->Get(), {tonic::ToDart(-1)});
return;
}
auto buffer = fml::MakeRefCounted<ImmutableBuffer>(sk_data);
buffer->AssociateWithDartWrapper(buffer_handle->Get());
tonic::DartInvoke(buffer_callback->Get(), {tonic::ToDart(buffer_size)});
});

dart_state->GetConcurrentTaskRunner()->PostTask(
[asset_name = std::move(asset_name),
asset_manager = std::move(asset_manager),
ui_task_runner = std::move(ui_task_runner), ui_task] {
std::unique_ptr<fml::Mapping> mapping =
asset_manager->GetAsMapping(asset_name);

sk_sp<SkData> sk_data;
size_t buffer_size = 0;
if (mapping != nullptr) {
buffer_size = mapping->GetSize();
const void* bytes = static_cast<const void*>(mapping->GetMapping());
sk_data = MakeSkDataWithCopy(bytes, buffer_size);
}
ui_task_runner->PostTask(
[sk_data = std::move(sk_data), ui_task = ui_task, buffer_size]() {
ui_task(sk_data, buffer_size);
});
});
return Dart_Null();
}

Expand All @@ -101,20 +140,24 @@ Dart_Handle ImmutableBuffer::initFromFile(Dart_Handle raw_buffer_handle,

auto* dart_state = UIDartState::Current();
auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner();
auto buffer_callback =
std::make_unique<tonic::DartPersistentValue>(dart_state, callback_handle);
auto buffer_handle = std::make_unique<tonic::DartPersistentValue>(
dart_state, raw_buffer_handle);
auto* buffer_callback_ptr =
new tonic::DartPersistentValue(dart_state, callback_handle);
auto* buffer_handle_ptr =
new tonic::DartPersistentValue(dart_state, raw_buffer_handle);

auto ui_task = fml::MakeCopyable(
[buffer_callback = std::move(buffer_callback),
buffer_handle = std::move(buffer_handle)](const sk_sp<SkData>& sk_data,
size_t buffer_size) mutable {
[buffer_callback_ptr, buffer_handle_ptr](const sk_sp<SkData>& sk_data,
size_t buffer_size) mutable {
std::unique_ptr<tonic::DartPersistentValue> buffer_handle(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked into this, and it's actually ok to destruct the tonic::DartPersistentValue outside of a DartState::Scope. The DartPersistentValue holds a weak reference to the DartState used during construction and will enter that DartState's scope to delete the wrapped value.

So these unique_ptrs should be moved to the top of the function in order to ensure that they are not leaked if it returns early.

buffer_handle_ptr);
std::unique_ptr<tonic::DartPersistentValue> buffer_callback(
buffer_callback_ptr);
auto dart_state = buffer_callback->dart_state().lock();
if (!dart_state) {
return;
}
tonic::DartState::Scope scope(dart_state);

if (!sk_data) {
// -1 is used as a sentinel that the file could not be opened.
tonic::DartInvoke(buffer_callback->Get(), {tonic::ToDart(-1)});
Expand Down
8 changes: 8 additions & 0 deletions shell/common/fixtures/shell_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,11 @@ Future<void> testPluginUtilitiesCallbackHandle() async {
}
notifyNativeBool(true);
}

@pragma('vm:entry-point')
Future<void> testThatAssetLoadingHappensOnWorkerThread() async {
try {
await ImmutableBuffer.fromAsset('DoesNotExist');
} catch (err) { /* Do nothing */ }
notifyNative();
}
70 changes: 70 additions & 0 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "flutter/flow/layers/layer_raster_cache_item.h"
#include "flutter/flow/layers/platform_view_layer.h"
#include "flutter/flow/layers/transform_layer.h"
#include "flutter/fml/backtrace.h"
#include "flutter/fml/command_line.h"
#include "flutter/fml/dart/dart_converter.h"
#include "flutter/fml/make_copyable.h"
Expand Down Expand Up @@ -194,6 +195,42 @@ class TestAssetResolver : public AssetResolver {
AssetResolver::AssetResolverType type_;
};

class ThreadCheckingAssetResolver : public AssetResolver {
public:
explicit ThreadCheckingAssetResolver(
std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop)
: concurrent_loop_(std::move(concurrent_loop)) {}

// |AssetResolver|
bool IsValid() const override { return true; }

// |AssetResolver|
bool IsValidAfterAssetManagerChange() const override { return true; }

// |AssetResolver|
AssetResolverType GetType() const {
return AssetResolverType::kApkAssetProvider;
}

// |AssetResolver|
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override {
if (asset_name == "FontManifest.json") {
// This file is loaded directly by the engine.
return nullptr;
}
mapping_requests.push_back(asset_name);
EXPECT_TRUE(concurrent_loop_->RunsTasksOnCurrentThread())
<< fml::BacktraceHere();
return nullptr;
}

mutable std::vector<std::string> mapping_requests;

private:
std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_loop_;
};

static bool ValidateShell(Shell* shell) {
if (!shell) {
return false;
Expand Down Expand Up @@ -3805,6 +3842,39 @@ TEST_F(ShellTest, SpawnWorksWithOnError) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}

TEST_F(ShellTest, ImmutableBufferLoadsAssetOnBackgroundThread) {
Settings settings = CreateSettingsForFixture();
auto task_runner = CreateNewThread();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);

fml::CountDownLatch latch(1);
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));

// Create the surface needed by rasterizer
PlatformViewNotifyCreated(shell.get());

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("testThatAssetLoadingHappensOnWorkerThread");
auto asset_manager = configuration.GetAssetManager();
auto test_resolver = std::make_unique<ThreadCheckingAssetResolver>(
shell->GetDartVM()->GetConcurrentMessageLoop());
auto leaked_resolver = test_resolver.get();
asset_manager->PushBack(std::move(test_resolver));

RunEngine(shell.get(), std::move(configuration));
PumpOneFrame(shell.get());

latch.Wait();

EXPECT_EQ(leaked_resolver->mapping_requests[0], "DoesNotExist");

PlatformViewNotifyDestroyed(shell.get());
DestroyShell(std::move(shell), task_runners);
}

TEST_F(ShellTest, PictureToImageSync) {
#if !SHELL_ENABLE_GL
// This test uses the GL backend.
Expand Down