blob: d4bddee30b2b080c8f07adaeb88b0d9e87367a52 [file] [log] [blame]
// Copyright 2016 The Chromium 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/tonic/dart_isolate_reloader.h"
#include <utility>
#include "flutter/tonic/dart_dependency_catcher.h"
#include "flutter/tonic/dart_library_loader.h"
#include "flutter/tonic/dart_library_provider.h"
#include "flutter/tonic/dart_state.h"
#include "flutter/glue/drain_data_pipe_job.h"
#include "flutter/glue/thread.h"
#include "lib/ftl/logging.h"
#include "lib/ftl/synchronization/monitor.h"
#include "lib/tonic/converter/dart_converter.h"
#include "lib/tonic/logging/dart_error.h"
#include "lib/tonic/scopes/dart_api_scope.h"
#include "lib/tonic/scopes/dart_isolate_scope.h"
using tonic::ToDart;
using tonic::StdStringToDart;
using tonic::StdStringFromDart;
namespace blink {
// As each source file is loaded, a LoadResult is queued to be processed on the
// isolate's thread. A LoadResult contains the payload or an error message.
class DartIsolateReloader::LoadResult {
public:
// Successful load result.
LoadResult(intptr_t tag,
const std::string& url,
const std::string& library_url,
const std::string& resolved_url,
std::vector<char> payload)
: success_(true),
tag_(tag),
url_(url),
library_url_(library_url),
resolved_url_(resolved_url),
payload_(std::move(payload)) {
FTL_DCHECK(success());
}
// Error load result.
LoadResult(intptr_t tag,
const std::string& url,
const std::string& library_url,
const std::string& error)
: success_(false),
tag_(tag),
url_(url),
library_url_(library_url),
error_(error) {}
bool success() const { return success_; }
Dart_Handle Finish() {
if (!success()) {
return StdStringToDart(error_);
}
Dart_Handle uri = StdStringToDart(url_);
Dart_Handle resolved_uri = Dart_Null();
if (!resolved_url_.empty()) {
resolved_uri = StdStringToDart(resolved_url_);
}
Dart_Handle library = Dart_Null();
if (!library_url_.empty()) {
library = Dart_LookupLibrary(StdStringToDart(library_url_));
}
Dart_Handle source = Dart_NewStringFromUTF8(
reinterpret_cast<const uint8_t*>(payload_.data()), payload_.size());
Dart_Handle result = Dart_Null();
switch (tag_) {
case Dart_kImportTag:
result = Dart_LoadLibrary(uri, resolved_uri, source, 0, 0);
break;
case Dart_kSourceTag:
result = Dart_LoadSource(library, uri, resolved_uri, source, 0, 0);
break;
case Dart_kScriptTag:
result = Dart_LoadScript(uri, resolved_uri, source, 0, 0);
break;
}
if (Dart_IsError(result)) {
return result;
}
return Dart_Null();
}
private:
bool success_;
intptr_t tag_;
std::string url_;
std::string library_url_;
std::string resolved_url_;
std::string error_;
std::vector<char> payload_;
};
DartIsolateReloader::DartIsolateReloader(DartLibraryProvider* library_provider)
: thread_("DartIsolateReloader"),
library_provider_(library_provider),
load_error_(Dart_Null()),
pending_requests_(0) {
FTL_CHECK(thread_.Start());
}
DartIsolateReloader::~DartIsolateReloader() {}
void DartIsolateReloader::SendRequest(Dart_LibraryTag tag,
Dart_Handle url,
Dart_Handle library_url) {
ftl::RefPtr<ftl::TaskRunner> runner = thread_.task_runner();
std::string url_string = StdStringFromDart(url);
std::string library_url_string = StdStringFromDart(library_url);
ftl::MonitorLocker locker(&monitor_);
// Post a task to the worker thread. This task will request the I/O and
// post a LoadResult to be processed once complete.
runner->PostTask([this, tag, url_string, library_url_string]() {
RequestTask(library_provider_, this, static_cast<intptr_t>(tag), url_string,
library_url_string);
});
pending_requests_++;
}
void DartIsolateReloader::PostResult(std::unique_ptr<LoadResult> load_result) {
ftl::MonitorLocker locker(&monitor_);
pending_requests_--;
load_results_.push(std::move(load_result));
locker.Signal();
}
// As each source file is requested, a LoadRequest is queued to be processed on
// worker thread.
class DartIsolateReloader::LoadRequest {
public:
LoadRequest(DartLibraryProvider* library_provider,
DartIsolateReloader* isolate_reloader,
intptr_t tag,
const std::string& url,
const std::string& library_url)
: isolate_reloader_(isolate_reloader),
tag_(tag),
url_(url),
library_url_(library_url) {
auto stream = library_provider->GetLibraryAsStream(url_);
OnStreamAvailable(std::move(stream.handle), std::move(stream.resolved_url));
}
protected:
void OnStreamAvailable(mojo::ScopedDataPipeConsumerHandle handle,
std::string resolved_url) {
if (!handle.is_valid()) {
std::unique_ptr<DartIsolateReloader::LoadResult> result(
new DartIsolateReloader::LoadResult(
tag_, url_, library_url_,
"File " + url_ + " could not be read."));
FTL_LOG(ERROR) << "Load failed for " << url_;
isolate_reloader_->PostResult(std::move(result));
// We are finished with this request.
delete this;
return;
}
resolved_url_ = std::move(resolved_url);
drainer_.reset(new glue::DrainDataPipeJob(
std::move(handle), [this](std::vector<char> buffer) {
std::unique_ptr<DartIsolateReloader::LoadResult> result(
new DartIsolateReloader::LoadResult(
tag_, url_, library_url_, resolved_url_, std::move(buffer)));
isolate_reloader_->PostResult(std::move(result));
// We are finished with this request.
delete this;
}));
}
private:
DartIsolateReloader* isolate_reloader_;
intptr_t tag_;
std::string url_;
std::string library_url_;
std::string resolved_url_;
std::vector<uint8_t> buffer_;
std::unique_ptr<glue::DrainDataPipeJob> drainer_;
};
void DartIsolateReloader::RequestTask(DartLibraryProvider* library_provider,
DartIsolateReloader* isolate_reloader,
intptr_t tag,
const std::string& url,
const std::string& library_url) {
FTL_DCHECK(isolate_reloader);
FTL_DCHECK(library_provider);
FTL_DCHECK(tag > 0);
// Construct a new LoadRequest. The pointer is dropped here because
// the request deletes itself on success and failure.
new LoadRequest(library_provider, isolate_reloader, tag, url, library_url);
}
void DartIsolateReloader::HandleLoadResultLocked(LoadResult* load_result) {
if (load_error_ != Dart_Null()) {
// Already have a sticky error. Just drop this result.
return;
}
// Drop the lock temporarily around the call to Finish because it may
// trigger a recursive call into the tag handler.
monitor_.Exit();
Dart_Handle error_or_null = load_result->Finish();
monitor_.Enter();
if (!Dart_IsNull(error_or_null)) {
// Set sticky error.
load_error_ = error_or_null;
}
}
void DartIsolateReloader::ProcessResultQueueLocked() {
while (load_results_.size() > 0) {
// Grab the first load result.
std::unique_ptr<LoadResult> result = std::move(load_results_.front());
load_results_.pop();
HandleLoadResultLocked(result.get());
}
}
bool DartIsolateReloader::IsCompleteLocked() {
return (pending_requests_ == 0) && load_results_.empty();
}
bool DartIsolateReloader::BlockUntilComplete() {
ftl::MonitorLocker locker(&monitor_);
while (true) {
ProcessResultQueueLocked();
if (IsCompleteLocked()) {
break;
}
// Wait to be notified about new I/O results.
locker.Wait();
}
return !Dart_IsNull(load_error_);
}
Dart_Handle DartIsolateReloader::HandleLibraryTag(Dart_LibraryTag tag,
Dart_Handle library,
Dart_Handle url) {
if (tag == Dart_kCanonicalizeUrl) {
// Pass through to actual tag handler.
return DartLibraryLoader::HandleLibraryTag(tag, library, url);
}
DartState* dart_state = DartState::Current();
FTL_DCHECK(dart_state);
DartIsolateReloader* isolate_reloader = dart_state->isolate_reloader();
// The first call into the tag handler ends up blocking the calling thread
// until the entire reload has completed. All other calls into the
// tag handler schedule requests and return immediately.
const bool blocking_call = (tag == Dart_kScriptTag);
if (!isolate_reloader) {
// The first call into this tag handler must be for the script.
FTL_DCHECK(tag == Dart_kScriptTag);
// Associate the reloader with the isolate. The reloader is owned
// by the dart_state.
dart_state->set_isolate_reloader(
std::unique_ptr<DartIsolateReloader>(new DartIsolateReloader(
dart_state->library_loader().library_provider())));
// Get a pointer to the reloader.
isolate_reloader = dart_state->isolate_reloader();
// Switch the tag handler.
Dart_SetLibraryTagHandler(DartIsolateReloader::HandleLibraryTag);
} else {
// We should not see another request for the script.
FTL_DCHECK(tag != Dart_kScriptTag);
}
// Issue I/O request.
isolate_reloader->SendRequest(tag, url, (library != Dart_Null())
? Dart_LibraryUrl(library)
: Dart_Null());
if (blocking_call) {
// Block and process LoadResults until the load is complete.
const bool load_error = isolate_reloader->BlockUntilComplete();
// Grab the (possibly null) load error from the reloader before its gone.
Dart_Handle result = isolate_reloader->load_error_;
// The reloader will be deleted once we call set_isolate_reloader below.
isolate_reloader = nullptr;
// Disassociate reloader from the isolate, this causes it to be deleted.
dart_state->set_isolate_reloader(nullptr);
Dart_SetLibraryTagHandler(DartLibraryLoader::HandleLibraryTag);
if (load_error) {
// If we hit an error, return the load error.
return result;
} else {
// Finalize loading.
result = Dart_FinalizeLoading(true);
if (Dart_IsError(result)) {
// If we hit an error, return the load error.
return result;
}
}
}
return Dart_Null();
}
} // namespace blink