Make ServiceRuntime::LoadModule asynchornize-able.
[chromium-blink-merge.git] / ppapi / native_client / src / trusted / plugin / plugin.cc
blobd3a5c5984550a1804da0beea0b28a458cca5a738
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ppapi/native_client/src/trusted/plugin/plugin.h"
7 #include <sys/stat.h>
8 #include <sys/types.h>
10 #include <string>
12 #include "native_client/src/include/nacl_base.h"
13 #include "native_client/src/include/nacl_macros.h"
14 #include "native_client/src/include/nacl_scoped_ptr.h"
15 #include "native_client/src/include/nacl_string.h"
16 #include "native_client/src/include/portability.h"
17 #include "native_client/src/include/portability_io.h"
18 #include "native_client/src/include/portability_string.h"
19 #include "native_client/src/public/nacl_file_info.h"
20 #include "native_client/src/shared/platform/nacl_check.h"
21 #include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
22 #include "native_client/src/trusted/nonnacl_util/sel_ldr_launcher.h"
23 #include "native_client/src/trusted/service_runtime/nacl_error_code.h"
25 #include "ppapi/c/pp_errors.h"
26 #include "ppapi/c/private/ppb_nacl_private.h"
27 #include "ppapi/cpp/dev/url_util_dev.h"
28 #include "ppapi/cpp/module.h"
30 #include "ppapi/native_client/src/trusted/plugin/nacl_entry_points.h"
31 #include "ppapi/native_client/src/trusted/plugin/nacl_subprocess.h"
32 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
33 #include "ppapi/native_client/src/trusted/plugin/service_runtime.h"
34 #include "ppapi/native_client/src/trusted/plugin/utility.h"
36 namespace plugin {
38 namespace {
40 // Up to 20 seconds
41 const int64_t kTimeSmallMin = 1; // in ms
42 const int64_t kTimeSmallMax = 20000; // in ms
43 const uint32_t kTimeSmallBuckets = 100;
45 } // namespace
47 void Plugin::ShutDownSubprocesses() {
48 PLUGIN_PRINTF(("Plugin::ShutDownSubprocesses (this=%p)\n",
49 static_cast<void*>(this)));
50 PLUGIN_PRINTF(("Plugin::ShutDownSubprocesses (%s)\n",
51 main_subprocess_.detailed_description().c_str()));
53 // Shut down service runtime. This must be done before all other calls so
54 // they don't block forever when waiting for the upcall thread to exit.
55 main_subprocess_.Shutdown();
57 PLUGIN_PRINTF(("Plugin::ShutDownSubprocess (this=%p, return)\n",
58 static_cast<void*>(this)));
61 void Plugin::HistogramTimeSmall(const std::string& name,
62 int64_t ms) {
63 if (ms < 0) return;
64 uma_interface_.HistogramCustomTimes(name,
65 ms,
66 kTimeSmallMin, kTimeSmallMax,
67 kTimeSmallBuckets);
70 bool Plugin::LoadNaClModuleFromBackgroundThread(
71 PP_FileHandle file_handle,
72 NaClSubprocess* subprocess,
73 const SelLdrStartParams& params) {
74 CHECK(!pp::Module::Get()->core()->IsMainThread());
75 ServiceRuntime* service_runtime =
76 new ServiceRuntime(this, false, uses_nonsfi_mode_,
77 pp::BlockUntilComplete(), pp::BlockUntilComplete());
78 subprocess->set_service_runtime(service_runtime);
79 PLUGIN_PRINTF(("Plugin::LoadNaClModuleFromBackgroundThread "
80 "(service_runtime=%p)\n",
81 static_cast<void*>(service_runtime)));
83 // Now start the SelLdr instance. This must be created on the main thread.
84 bool service_runtime_started = false;
85 pp::CompletionCallback sel_ldr_callback =
86 callback_factory_.NewCallback(&Plugin::SignalStartSelLdrDone,
87 &service_runtime_started,
88 service_runtime);
89 pp::CompletionCallback callback =
90 callback_factory_.NewCallback(&Plugin::StartSelLdrOnMainThread,
91 service_runtime, params,
92 sel_ldr_callback);
93 pp::Module::Get()->core()->CallOnMainThread(0, callback, 0);
94 if (!service_runtime->WaitForSelLdrStart()) {
95 PLUGIN_PRINTF(("Plugin::LoadNaClModuleFromBackgroundThread "
96 "WaitForSelLdrStart timed out!\n"));
97 return false;
99 PLUGIN_PRINTF(("Plugin::LoadNaClModuleFromBackgroundThread "
100 "(service_runtime_started=%d)\n",
101 service_runtime_started));
102 if (!service_runtime_started)
103 return false;
105 PP_NaClFileInfo info;
106 info.handle = file_handle;
107 info.token_lo = 0;
108 info.token_hi = 0;
110 // Now actually load the nexe, which can happen on a background thread.
112 // We can't use pp::BlockUntilComplete() inside an in-process plugin, so we
113 // have to roll our own blocking logic, similar to WaitForSelLdrStart()
114 // above, except without timeout logic.
115 bool nexe_started = false;
116 pp::CompletionCallback started_cb = callback_factory_.NewCallback(
117 &Plugin::SignalNexeStarted, &nexe_started, service_runtime);
118 service_runtime->LoadNexeAndStart(info, started_cb);
119 service_runtime->WaitForNexeStart();
120 return nexe_started;
123 void Plugin::StartSelLdrOnMainThread(int32_t pp_error,
124 ServiceRuntime* service_runtime,
125 const SelLdrStartParams& params,
126 pp::CompletionCallback callback) {
127 if (pp_error != PP_OK) {
128 PLUGIN_PRINTF(("Plugin::StartSelLdrOnMainThread: non-PP_OK arg "
129 "-- SHOULD NOT HAPPEN\n"));
130 pp::Module::Get()->core()->CallOnMainThread(0, callback, pp_error);
131 return;
133 service_runtime->StartSelLdr(params, callback);
136 void Plugin::SignalStartSelLdrDone(int32_t pp_error,
137 bool* started,
138 ServiceRuntime* service_runtime) {
139 *started = (pp_error == PP_OK);
140 service_runtime->SignalStartSelLdrDone();
143 void Plugin::SignalNexeStarted(int32_t pp_error,
144 bool* started,
145 ServiceRuntime* service_runtime) {
146 *started = (pp_error == PP_OK);
147 service_runtime->SignalNexeStarted();
150 void Plugin::LoadNaClModule(PP_NaClFileInfo file_info,
151 bool uses_nonsfi_mode,
152 bool enable_dyncode_syscalls,
153 bool enable_exception_handling,
154 bool enable_crash_throttling,
155 const pp::CompletionCallback& init_done_cb,
156 const pp::CompletionCallback& crash_cb) {
157 CHECK(pp::Module::Get()->core()->IsMainThread());
158 // Before forking a new sel_ldr process, ensure that we do not leak
159 // the ServiceRuntime object for an existing subprocess, and that any
160 // associated listener threads do not go unjoined because if they
161 // outlive the Plugin object, they will not be memory safe.
162 ShutDownSubprocesses();
163 pp::Var manifest_base_url =
164 pp::Var(pp::PASS_REF, nacl_interface_->GetManifestBaseURL(pp_instance()));
165 std::string manifest_base_url_str = manifest_base_url.AsString();
166 SelLdrStartParams params(manifest_base_url_str,
167 true /* uses_irt */,
168 true /* uses_ppapi */,
169 uses_nonsfi_mode,
170 enable_dyncode_syscalls,
171 enable_exception_handling,
172 enable_crash_throttling);
173 ErrorInfo error_info;
174 ServiceRuntime* service_runtime =
175 new ServiceRuntime(this, true, uses_nonsfi_mode,
176 init_done_cb, crash_cb);
177 main_subprocess_.set_service_runtime(service_runtime);
178 PLUGIN_PRINTF(("Plugin::LoadNaClModule (service_runtime=%p)\n",
179 static_cast<void*>(service_runtime)));
180 if (NULL == service_runtime) {
181 error_info.SetReport(
182 PP_NACL_ERROR_SEL_LDR_INIT,
183 "sel_ldr init failure " + main_subprocess_.description());
184 ReportLoadError(error_info);
185 return;
188 pp::CompletionCallback callback = callback_factory_.NewCallback(
189 &Plugin::LoadNexeAndStart, file_info, service_runtime);
190 StartSelLdrOnMainThread(
191 static_cast<int32_t>(PP_OK), service_runtime, params, callback);
194 void Plugin::LoadNexeAndStart(int32_t pp_error,
195 PP_NaClFileInfo file_info,
196 ServiceRuntime* service_runtime) {
197 if (pp_error != PP_OK)
198 return;
200 // We don't take any action once nexe loading has completed, so pass an empty
201 // callback here for |loaded_cb|.
202 service_runtime->LoadNexeAndStart(file_info, pp::CompletionCallback());
205 bool Plugin::LoadNaClModuleContinuationIntern() {
206 ErrorInfo error_info;
207 if (!uses_nonsfi_mode_) {
208 if (!main_subprocess_.StartSrpcServices()) {
209 // The NaCl process probably crashed. On Linux, a crash causes this
210 // error, while on other platforms, the error is detected below, when we
211 // attempt to start the proxy. Report a module initialization error here,
212 // to make it less confusing for developers.
213 NaClLog(LOG_ERROR, "LoadNaClModuleContinuationIntern: "
214 "StartSrpcServices failed\n");
215 error_info.SetReport(PP_NACL_ERROR_START_PROXY_MODULE,
216 "could not initialize module.");
217 ReportLoadError(error_info);
218 return false;
222 bool result = PP_ToBool(nacl_interface_->StartPpapiProxy(pp_instance()));
223 if (result) {
224 PLUGIN_PRINTF(("Plugin::LoadNaClModule (%s)\n",
225 main_subprocess_.detailed_description().c_str()));
227 return result;
230 NaClSubprocess* Plugin::LoadHelperNaClModule(const nacl::string& helper_url,
231 PP_FileHandle file_handle,
232 ErrorInfo* error_info) {
233 nacl::scoped_ptr<NaClSubprocess> nacl_subprocess(
234 new NaClSubprocess("helper module", NULL, NULL));
235 if (NULL == nacl_subprocess.get()) {
236 error_info->SetReport(PP_NACL_ERROR_SEL_LDR_INIT,
237 "unable to allocate helper subprocess.");
238 return NULL;
241 // Do not report UMA stats for translator-related nexes.
242 // TODO(sehr): define new UMA stats for translator related nexe events.
243 // NOTE: The PNaCl translator nexes are not built to use the IRT. This is
244 // done to save on address space and swap space.
245 SelLdrStartParams params(helper_url,
246 false /* uses_irt */,
247 false /* uses_ppapi */,
248 false /* uses_nonsfi_mode */,
249 false /* enable_dyncode_syscalls */,
250 false /* enable_exception_handling */,
251 true /* enable_crash_throttling */);
253 // Helper NaCl modules always use the PNaCl manifest, as there is no
254 // corresponding NMF.
255 if (!LoadNaClModuleFromBackgroundThread(file_handle, nacl_subprocess.get(),
256 params)) {
257 return NULL;
259 // We need not wait for the init_done callback. We can block
260 // here in StartSrpcServices, since helper NaCl modules
261 // are spawned from a private thread.
263 // TODO(bsy): if helper module crashes, we should abort.
264 // crash_cb is not used here, so we are relying on crashes
265 // being detected in StartSrpcServices or later.
267 // NB: More refactoring might be needed, however, if helper
268 // NaCl modules have their own manifest. Currently the
269 // manifest is a per-plugin-instance object, not a per
270 // NaClSubprocess object.
271 if (!nacl_subprocess->StartSrpcServices()) {
272 error_info->SetReport(PP_NACL_ERROR_SRPC_CONNECTION_FAIL,
273 "SRPC connection failure for " +
274 nacl_subprocess->description());
275 return NULL;
278 PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule (%s, %s)\n",
279 helper_url.c_str(),
280 nacl_subprocess.get()->detailed_description().c_str()));
282 return nacl_subprocess.release();
285 // All failures of this function will show up as "Missing Plugin-in", so
286 // there is no need to log to JS console that there was an initialization
287 // failure. Note that module loading functions will log their own errors.
288 bool Plugin::Init(uint32_t argc, const char* argn[], const char* argv[]) {
289 PLUGIN_PRINTF(("Plugin::Init (argc=%" NACL_PRIu32 ")\n", argc));
290 nacl_interface_->InitializePlugin(pp_instance(), argc, argn, argv);
291 wrapper_factory_ = new nacl::DescWrapperFactory();
292 pp::CompletionCallback open_cb =
293 callback_factory_.NewCallback(&Plugin::NaClManifestFileDidOpen);
294 nacl_interface_->RequestNaClManifest(pp_instance(),
295 open_cb.pp_completion_callback());
296 return true;
299 Plugin::Plugin(PP_Instance pp_instance)
300 : pp::Instance(pp_instance),
301 main_subprocess_("main subprocess", NULL, NULL),
302 uses_nonsfi_mode_(false),
303 wrapper_factory_(NULL),
304 nacl_interface_(NULL),
305 uma_interface_(this) {
306 PLUGIN_PRINTF(("Plugin::Plugin (this=%p, pp_instance=%"
307 NACL_PRId32 ")\n", static_cast<void*>(this), pp_instance));
308 callback_factory_.Initialize(this);
309 nacl_interface_ = GetNaClInterface();
310 CHECK(nacl_interface_ != NULL);
312 // Notify PPB_NaCl_Private that the instance is created before altering any
313 // state that it tracks.
314 nacl_interface_->InstanceCreated(pp_instance);
315 // We call set_exit_status() here to ensure that the 'exitStatus' property is
316 // set. This can only be called when nacl_interface_ is not NULL.
317 set_exit_status(-1);
318 nexe_file_info_.handle = PP_kInvalidFileHandle;
319 nexe_file_info_.token_lo = 0;
320 nexe_file_info_.token_hi = 0;
324 Plugin::~Plugin() {
325 int64_t shutdown_start = NaClGetTimeOfDayMicroseconds();
327 PLUGIN_PRINTF(("Plugin::~Plugin (this=%p)\n",
328 static_cast<void*>(this)));
329 // Destroy the coordinator while the rest of the data is still there
330 pnacl_coordinator_.reset(NULL);
332 nacl_interface_->InstanceDestroyed(pp_instance());
334 // ShutDownSubprocesses shuts down the main subprocess, which shuts
335 // down the main ServiceRuntime object, which kills the subprocess.
336 // As a side effect of the subprocess being killed, the reverse
337 // services thread(s) will get EOF on the reverse channel(s), and
338 // the thread(s) will exit. In ServiceRuntime::Shutdown, we invoke
339 // ReverseService::WaitForServiceThreadsToExit(), so that there will
340 // not be an extent thread(s) hanging around. This means that the
341 // ~Plugin will block until this happens. This is a requirement,
342 // since the renderer should be free to unload the plugin code, and
343 // we cannot have threads running code that gets unloaded before
344 // they exit.
346 // By waiting for the threads here, we also ensure that the Plugin
347 // object and the subprocess and ServiceRuntime objects is not
348 // (fully) destroyed while the threads are running, so resources
349 // that are destroyed after ShutDownSubprocesses (below) are
350 // guaranteed to be live and valid for access from the service
351 // threads.
353 // The main_subprocess object, which wraps the main service_runtime
354 // object, is dtor'd implicitly after the explicit code below runs,
355 // so the main service runtime object will not have been dtor'd,
356 // though the Shutdown method may have been called, during the
357 // lifetime of the service threads.
358 ShutDownSubprocesses();
360 delete wrapper_factory_;
362 HistogramTimeSmall(
363 "NaCl.Perf.ShutdownTime.Total",
364 (NaClGetTimeOfDayMicroseconds() - shutdown_start)
365 / NACL_MICROS_PER_MILLI);
367 PLUGIN_PRINTF(("Plugin::~Plugin (this=%p, return)\n",
368 static_cast<void*>(this)));
371 bool Plugin::HandleDocumentLoad(const pp::URLLoader& url_loader) {
372 PLUGIN_PRINTF(("Plugin::HandleDocumentLoad (this=%p)\n",
373 static_cast<void*>(this)));
374 // We don't know if the plugin will handle the document load, but return
375 // true in order to give it a chance to respond once the proxy is started.
376 return true;
379 void Plugin::NexeFileDidOpen(int32_t pp_error) {
380 if (pp_error != PP_OK)
381 return;
383 NaClLog(4, "NexeFileDidOpen: invoking LoadNaClModule\n");
384 LoadNaClModule(
385 nexe_file_info_,
386 uses_nonsfi_mode_,
387 true, /* enable_dyncode_syscalls */
388 true, /* enable_exception_handling */
389 false, /* enable_crash_throttling */
390 callback_factory_.NewCallback(&Plugin::NexeFileDidOpenContinuation),
391 callback_factory_.NewCallback(&Plugin::NexeDidCrash));
394 void Plugin::NexeFileDidOpenContinuation(int32_t pp_error) {
395 UNREFERENCED_PARAMETER(pp_error);
396 NaClLog(4, "Entered NexeFileDidOpenContinuation\n");
397 if (LoadNaClModuleContinuationIntern()) {
398 NaClLog(4, "NexeFileDidOpenContinuation: success;"
399 " setting histograms\n");
400 int64_t nexe_size = nacl_interface_->GetNexeSize(pp_instance());
401 nacl_interface_->ReportLoadSuccess(
402 pp_instance(), program_url_.c_str(), nexe_size, nexe_size);
403 } else {
404 NaClLog(4, "NexeFileDidOpenContinuation: failed.");
406 NaClLog(4, "Leaving NexeFileDidOpenContinuation\n");
409 void Plugin::NexeDidCrash(int32_t pp_error) {
410 PLUGIN_PRINTF(("Plugin::NexeDidCrash (pp_error=%" NACL_PRId32 ")\n",
411 pp_error));
412 if (pp_error != PP_OK) {
413 PLUGIN_PRINTF(("Plugin::NexeDidCrash: CallOnMainThread callback with"
414 " non-PP_OK arg -- SHOULD NOT HAPPEN\n"));
417 std::string crash_log =
418 main_subprocess_.service_runtime()->GetCrashLogOutput();
419 nacl_interface_->NexeDidCrash(pp_instance(), crash_log.c_str());
422 void Plugin::BitcodeDidTranslate(int32_t pp_error) {
423 PLUGIN_PRINTF(("Plugin::BitcodeDidTranslate (pp_error=%" NACL_PRId32 ")\n",
424 pp_error));
425 if (pp_error != PP_OK) {
426 // Error should have been reported by pnacl. Just return.
427 PLUGIN_PRINTF(("Plugin::BitcodeDidTranslate error in Pnacl\n"));
428 return;
431 // Inform JavaScript that we successfully translated the bitcode to a nexe.
432 PP_FileHandle handle = pnacl_coordinator_->TakeTranslatedFileHandle();
434 PP_NaClFileInfo info;
435 info.handle = handle;
436 info.token_lo = 0;
437 info.token_hi = 0;
438 LoadNaClModule(
439 info,
440 false, /* uses_nonsfi_mode */
441 false, /* enable_dyncode_syscalls */
442 false, /* enable_exception_handling */
443 true, /* enable_crash_throttling */
444 callback_factory_.NewCallback(&Plugin::BitcodeDidTranslateContinuation),
445 callback_factory_.NewCallback(&Plugin::NexeDidCrash));
448 void Plugin::BitcodeDidTranslateContinuation(int32_t pp_error) {
449 NaClLog(4, "Entered BitcodeDidTranslateContinuation\n");
450 UNREFERENCED_PARAMETER(pp_error);
451 if (LoadNaClModuleContinuationIntern()) {
452 int64_t loaded;
453 int64_t total;
454 // TODO(teravest): Tighten this up so we can get rid of
455 // GetCurrentProgress(). loaded should always equal total.
456 pnacl_coordinator_->GetCurrentProgress(&loaded, &total);
457 nacl_interface_->ReportLoadSuccess(
458 pp_instance(), program_url_.c_str(), loaded, total);
462 void Plugin::NaClManifestFileDidOpen(int32_t pp_error) {
463 PLUGIN_PRINTF(("Plugin::NaClManifestFileDidOpen (pp_error=%"
464 NACL_PRId32 ")\n", pp_error));
465 if (pp_error != PP_OK)
466 return;
468 PP_Var pp_program_url;
469 PP_PNaClOptions pnacl_options = {PP_FALSE, PP_FALSE, 2};
470 PP_Bool uses_nonsfi_mode;
471 if (nacl_interface_->GetManifestProgramURL(
472 pp_instance(), &pp_program_url, &pnacl_options, &uses_nonsfi_mode)) {
473 program_url_ = pp::Var(pp::PASS_REF, pp_program_url).AsString();
474 // TODO(teravest): Make ProcessNaClManifest take responsibility for more of
475 // this function.
476 nacl_interface_->ProcessNaClManifest(pp_instance(), program_url_.c_str());
477 uses_nonsfi_mode_ = PP_ToBool(uses_nonsfi_mode);
478 if (pnacl_options.translate) {
479 pp::CompletionCallback translate_callback =
480 callback_factory_.NewCallback(&Plugin::BitcodeDidTranslate);
481 pnacl_coordinator_.reset(
482 PnaclCoordinator::BitcodeToNative(this,
483 program_url_,
484 pnacl_options,
485 translate_callback));
486 return;
487 } else {
488 pp::CompletionCallback open_callback =
489 callback_factory_.NewCallback(&Plugin::NexeFileDidOpen);
490 // Will always call the callback on success or failure.
491 nacl_interface_->DownloadNexe(pp_instance(),
492 program_url_.c_str(),
493 &nexe_file_info_,
494 open_callback.pp_completion_callback());
495 return;
500 void Plugin::ReportLoadError(const ErrorInfo& error_info) {
501 nacl_interface_->ReportLoadError(pp_instance(),
502 error_info.error_code(),
503 error_info.message().c_str());
506 bool Plugin::DocumentCanRequest(const std::string& url) {
507 CHECK(pp::Module::Get()->core()->IsMainThread());
508 CHECK(pp::URLUtil_Dev::Get() != NULL);
509 return pp::URLUtil_Dev::Get()->DocumentCanRequest(this, pp::Var(url));
512 void Plugin::set_exit_status(int exit_status) {
513 pp::Core* core = pp::Module::Get()->core();
514 if (core->IsMainThread()) {
515 SetExitStatusOnMainThread(PP_OK, exit_status);
516 } else {
517 pp::CompletionCallback callback =
518 callback_factory_.NewCallback(&Plugin::SetExitStatusOnMainThread,
519 exit_status);
520 core->CallOnMainThread(0, callback, 0);
524 void Plugin::SetExitStatusOnMainThread(int32_t pp_error,
525 int exit_status) {
526 DCHECK(pp::Module::Get()->core()->IsMainThread());
527 DCHECK(nacl_interface_);
528 nacl_interface_->SetExitStatus(pp_instance(), exit_status);
532 } // namespace plugin