2 * Copyright 2014 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
8 // Post-message based test for simple rpc based access to name services.
17 #include <sys/fcntl.h>
22 #include "native_client/src/include/nacl_base.h"
23 #include "native_client/src/public/imc_syscalls.h"
24 #include "native_client/src/public/name_service.h"
25 #include "native_client/src/shared/platform/nacl_sync.h"
26 #include "native_client/src/shared/platform/nacl_sync_checked.h"
27 #include "native_client/src/shared/platform/nacl_sync_raii.h"
28 #include "native_client/src/shared/srpc/nacl_srpc.h"
30 // TODO(bsy): move weak_ref module to the shared directory
31 #include "native_client/src/trusted/weak_ref/weak_ref.h"
33 #include "ppapi/cpp/instance.h"
34 #include "ppapi/cpp/module.h"
35 #include "ppapi/cpp/var.h"
37 #include "ppapi/native_client/src/trusted/weak_ref/call_on_main_thread.h"
38 #include "ppapi/native_client/src/untrusted/nacl_ppapi_util/nacl_ppapi_util.h"
39 #include "ppapi/native_client/src/untrusted/nacl_ppapi_util/string_buffer.h"
41 class PostStringMessageWrapper
42 : public nacl_ppapi::EventThreadWorkStateWrapper
<nacl_ppapi::VoidResult
> {
44 PostStringMessageWrapper(nacl_ppapi::EventThreadWorkState
<
45 nacl_ppapi::VoidResult
>
47 const std::string
&msg
)
48 : nacl_ppapi::EventThreadWorkStateWrapper
<nacl_ppapi::VoidResult
>(
51 ~PostStringMessageWrapper();
52 const std::string
&msg() const { return msg_
; }
56 DISALLOW_COPY_AND_ASSIGN(PostStringMessageWrapper
);
59 // ---------------------------------------------------------------------------
64 explicit WorkRequest(const std::string
&message
)
66 next(reinterpret_cast<WorkRequest
*>(NULL
)) {}
67 std::string msg
; // copied from HandleMessage
70 DISALLOW_COPY_AND_ASSIGN(WorkRequest
);
73 // A Worker object is associated with a single worker thread and a
74 // plugin instance (which may be associated with multiple Worker
75 // objects/threads). It is created by a plugin instance (on the event
76 // handler thread) with a refcount of 2, with the expectation that one
77 // reference will be immediately handed off to its associated worker
78 // thread. When the plugin instance is about to be destroyed in the
79 // event handler thread, the event handler should invoke the
80 // ShouldExit member function, which automatically decrements the
81 // reference associated with the event handler thread (i.e., the event
82 // handler thread should no longer use the Worker*).
85 explicit Worker(MyInstance
*instance
);
87 // RunToCompletion should be invoked in the worker thread. It
88 // returns when the plugin instance went away, and will
89 // automatically unref the Worker object, so the worker thread
90 // should no longer use the Worker object pointer after invoking
92 void RunToCompletion();
94 WorkRequest
*Dequeue();
95 void Enqueue(WorkRequest
*req
);
97 void Initialize(nacl::StringBuffer
*sb
);
98 void NameServiceDump(nacl::StringBuffer
*sb
);
99 void ManifestListTest(nacl::StringBuffer
*sb
);
100 void ManifestOpenTest(nacl::StringBuffer
*sb
);
102 // Called on the event thread as part of the instance shutdown.
103 // Automatically unreferences the Worker object, so the event thread
104 // should stop using the Worker pointer after invoking ShouldExit.
107 void Unref(); // used only for error cleanup, e.g., when the worker
108 // thread did not launch.
110 bool InitializeChannel(nacl::StringBuffer
*sb
);
113 // Event thread operation(s):
115 // In order for a test method to send reply messages, it should use
116 // this PostStringMessage method, since (currently) the PostMessage
117 // interface is event thread-only and not thread-safe. Returns true
118 // if successful and the thread should continue to do work, false
119 // otherwise (anchor has been abandoned).
120 bool PostStringMessage(const std::string
&msg
);
121 // ... more Event thread operations here.
124 MyInstance
*instance_
; // cannot use directly from test worker thread!
125 nacl::WeakRefAnchor
*anchor_
;
126 // must copy out and Ref in ctor, since instance_ might go bad at any time.
129 NaClCondVar cv_
; // queue not empty or should_exit_
132 WorkRequest
*queue_head_
;
133 WorkRequest
**queue_insert_
;
139 WorkRequest
*Dequeue_mu();
140 void Enqueue_mu(WorkRequest
*req
);
142 struct DispatchTable
{
144 void (Worker::*mfunc
)(nacl::StringBuffer
*sb
);
147 bool ns_channel_initialized_
;
148 NaClSrpcChannel ns_channel_
;
150 static DispatchTable
const kDispatch
[]; // null terminated
152 DISALLOW_COPY_AND_ASSIGN(Worker
);
155 // This object represents one time the page says <embed>.
156 class MyInstance
: public nacl_ppapi::NaClPpapiPluginInstance
{
158 explicit MyInstance(PP_Instance instance
);
159 virtual ~MyInstance();
160 virtual void HandleMessage(const pp::Var
& message_data
);
162 Worker
*worker() { return worker_
; }
164 // used with plugin::WeakRefCompletionCallback
165 void PostStringMessage_EventThread(PostStringMessageWrapper
*msg_wrapper
,
170 DISALLOW_COPY_AND_ASSIGN(MyInstance
);
173 // ---------------------------------------------------------------------------
176 bool EnumerateNames(NaClSrpcChannel
*nschan
, nacl::StringBuffer
*sb
) {
179 uint32_t in_out_nbytes
;
182 buffer
= reinterpret_cast<char *>(malloc(nbytes
));
183 if (NULL
== buffer
) {
184 sb
->Printf("EnumerateNames: initial malloc failed\n");
189 in_out_nbytes
= nbytes
;
190 if (NACL_SRPC_RESULT_OK
!= NaClSrpcInvokeBySignature(nschan
,
191 NACL_NAME_SERVICE_LIST
,
194 sb
->Printf("NaClSrpcInvokeBySignature failed\n");
197 sb
->Printf("EnumerateNames: in_out_nbytes %d\n", in_out_nbytes
);
198 if (in_out_nbytes
< nbytes
) {
202 new_buffer
= reinterpret_cast<char *>(realloc(buffer
, nbytes
));
203 if (NULL
== new_buffer
) {
204 sb
->Printf("EnumerateNames: out of memory during realloc\n");
211 nbytes
= in_out_nbytes
;
212 sb
->Printf("nbytes = %u\n", (size_t) nbytes
);
213 if (nbytes
== sizeof buffer
) {
214 sb
->Printf("Insufficent space for namespace enumeration\n");
218 for (char *p
= buffer
;
219 static_cast<size_t>(p
- buffer
) < nbytes
;
221 name_len
= strlen(p
) + 1;
222 sb
->Printf("%s\n", p
);
228 // ---------------------------------------------------------------------------
230 PostStringMessageWrapper::~PostStringMessageWrapper() {}
232 // ---------------------------------------------------------------------------
234 MyInstance::MyInstance(PP_Instance instance
)
235 : nacl_ppapi::NaClPpapiPluginInstance(instance
),
236 worker_(new Worker(this)) {
239 MyInstance::~MyInstance() {
240 worker_
->ShouldExit();
243 void MyInstance::PostStringMessage_EventThread(
244 PostStringMessageWrapper
*msg_wrapper
,
246 PostMessage(msg_wrapper
->msg());
247 msg_wrapper
->SetResult(nacl_ppapi::g_void_result
);
250 // ---------------------------------------------------------------------------
252 Worker::Worker(MyInstance
*instance
)
253 : instance_(instance
),
254 anchor_(instance
->anchor()->Ref()),
255 ref_count_(2), // one for the master and one for the dame...
257 queue_insert_(&queue_head_
),
259 ns_channel_initialized_(false) {
260 NaClXMutexCtor(&mu_
);
261 NaClXCondVarCtor(&cv_
);
264 void Worker::Unref() {
267 nacl::MutexLocker
take(&mu_
);
268 do_delete
= (--ref_count_
== 0);
270 // dropped lock before invoking dtor
280 while ((req
= Dequeue_mu()) != NULL
) {
285 NaClCondVarDtor(&cv_
);
288 void Worker::ShouldExit() {
290 nacl::MutexLocker
take(&mu_
);
292 NaClXCondVarBroadcast(&cv_
);
297 WorkRequest
*Worker::Dequeue_mu() {
298 WorkRequest
*head
= queue_head_
;
301 queue_head_
= head
->next
;
302 if (queue_head_
== NULL
) {
303 queue_insert_
= &queue_head_
;
309 void Worker::Enqueue_mu(WorkRequest
*req
) {
311 *queue_insert_
= req
;
312 queue_insert_
= &req
->next
;
315 WorkRequest
*Worker::Dequeue() {
316 nacl::MutexLocker
take(&mu_
);
320 void Worker::Enqueue(WorkRequest
*req
) {
321 nacl::MutexLocker
take(&mu_
);
323 NaClXCondVarBroadcast(&cv_
);
326 Worker::DispatchTable
const Worker::kDispatch
[] = {
327 { "init", &Worker::Initialize
},
328 { "name_dump", &Worker::NameServiceDump
},
329 { "manifest_list", &Worker::ManifestListTest
},
330 { "manifest_open", &Worker::ManifestOpenTest
},
331 { reinterpret_cast<char const *>(NULL
), NULL
}
334 bool Worker::PostStringMessage(const std::string
&msg
) {
335 nacl_ppapi::EventThreadWorkState
<nacl_ppapi::VoidResult
> state
;
336 plugin::WeakRefCallOnMainThread(anchor_
,
339 &MyInstance::PostStringMessage_EventThread
,
340 new PostStringMessageWrapper(&state
, msg
));
341 if (NULL
== state
.WaitForCompletion()) {
342 // anchor_ has been abandoned, so the plugin instance went away.
343 // we should drop our ref to the anchor, then shut down the worker
345 nacl::MutexLocker
take(&mu_
);
347 // There's no need to condvar broadcast, since it is the worker
348 // thread that will look at the work queue and the should_exit_ to
349 // act on this. Unfortunately every worker thread must test the
350 // return value of PostStringMessage to determine if it should do
351 // early exit (if the worker needs to do multiple event-thread
358 void Worker::RunToCompletion() {
362 nacl::MutexLocker
take(&mu_
);
365 // drop the lock and drop the reference count to this
368 fprintf(stderr
, "RunToCompletion: Dequeuing...\n");
369 if ((req
= Dequeue_mu()) != NULL
) {
370 fprintf(stderr
, "RunToCompletion: found work %p\n",
371 reinterpret_cast<void *>(req
));
374 fprintf(stderr
, "RunToCompletion: waiting\n");
375 NaClXCondVarWait(&cv_
, &mu_
);
376 fprintf(stderr
, "RunToCompletion: woke up\n");
380 // Do the work, without holding the lock. The work function
381 // should reacquire mu_ as needed.
383 nacl::StringBuffer sb
;
385 // scan dispatch table for op_name
386 fprintf(stderr
, "RunToCompletion: scanning for %s\n", req
->msg
.c_str());
387 for (size_t ix
= 0; kDispatch
[ix
].op_name
!= NULL
; ++ix
) {
389 "RunToCompletion: comparing against %s\n", kDispatch
[ix
].op_name
);
390 if (req
->msg
== kDispatch
[ix
].op_name
) {
391 if (InitializeChannel(&sb
)) {
392 fprintf(stderr
, "RunToCompletion: invoking table entry %u\n", ix
);
393 (this->*(kDispatch
[ix
].mfunc
))(&sb
);
398 // always post a reply, even if it is the empty string
400 "RunToCompletion: posting reply %s\n", sb
.ToString().c_str());
401 if (!PostStringMessage(sb
.ToString())) {
406 fprintf(stderr
, "RunToCompletion: exiting\n");
410 bool Worker::InitializeChannel(nacl::StringBuffer
*sb
) {
411 if (ns_channel_initialized_
) {
415 nacl_nameservice(&ns
);
416 printf("ns = %d\n", ns
);
418 int connected_socket
= imc_connect(ns
);
419 assert(-1 != connected_socket
);
420 if (!NaClSrpcClientCtor(&ns_channel_
, connected_socket
)) {
421 sb
->Printf("Srpc client channel ctor failed\n");
425 sb
->Printf("NaClSrpcClientCtor succeeded\n");
427 ns_channel_initialized_
= true;
431 void Worker::Initialize(nacl::StringBuffer
*sb
) {
432 // we just want the log output from the InitializeChannel
436 // return name service output in sb
437 void Worker::NameServiceDump(nacl::StringBuffer
*sb
) {
438 (void) EnumerateNames(&ns_channel_
, sb
);
441 void Worker::ManifestListTest(nacl::StringBuffer
*sb
) {
444 // name service lookup for the manifest service descriptor
445 if (NACL_SRPC_RESULT_OK
!=
446 NaClSrpcInvokeBySignature(&ns_channel_
, NACL_NAME_SERVICE_LOOKUP
,
447 "ManifestNameService", O_RDWR
,
448 &status
, &manifest
) ||
449 NACL_NAME_SERVICE_SUCCESS
!= status
) {
450 sb
->Printf("nameservice lookup failed, status %d\n", status
);
452 sb
->Printf("Got manifest descriptor %d\n", manifest
);
453 if (-1 == manifest
) {
457 // connect to manifest name server
458 int manifest_conn
= imc_connect(manifest
);
460 sb
->Printf("got manifest connection %d\n", manifest_conn
);
461 if (-1 == manifest_conn
) {
462 sb
->Printf("could not connect\n");
466 // build the SRPC connection (do service discovery)
467 struct NaClSrpcChannel manifest_channel
;
468 if (!NaClSrpcClientCtor(&manifest_channel
, manifest_conn
)) {
469 sb
->Printf("could not build srpc client\n");
472 sb
->Printf("ManifestListTest: basic connectivity ok\n");
474 // list manifest service contents
476 uint32_t nbytes
= sizeof buffer
;
478 if (NACL_SRPC_RESULT_OK
!=
479 NaClSrpcInvokeBySignature(&manifest_channel
, NACL_NAME_SERVICE_LIST
,
481 sb
->Printf("manifest list RPC failed\n");
482 NaClSrpcDtor(&manifest_channel
);
487 sb
->Printf("Manifest Contents:\n");
489 // Should we explicitly sort the names? This would ensure that the
490 // test output is easy to compare with expected results. Currently,
491 // the manifest uses a set to hold the names, so the results will be
492 // sorted anyway, but this is not a guarantee of the API.
493 for (char *p
= buffer
;
494 static_cast<size_t>(p
- buffer
) < nbytes
;
496 name_len
= strlen(p
);
497 sb
->Printf("%.*s\n", (int) name_len
, p
);
499 NaClSrpcDtor(&manifest_channel
);
503 void Worker::ManifestOpenTest(nacl::StringBuffer
*sb
) {
506 struct NaClSrpcChannel manifest_channel
;
508 // name service lookup for the manifest service descriptor
509 if (NACL_SRPC_RESULT_OK
!=
510 NaClSrpcInvokeBySignature(&ns_channel_
, NACL_NAME_SERVICE_LOOKUP
,
511 "ManifestNameService", O_RDWR
,
512 &status
, &manifest
) ||
513 NACL_NAME_SERVICE_SUCCESS
!= status
) {
514 sb
->Printf("nameservice lookup failed, status %d\n", status
);
517 sb
->Printf("Got manifest descriptor %d\n", manifest
);
518 if (-1 == manifest
) {
522 // connect to manifest name server
523 int manifest_conn
= imc_connect(manifest
);
525 sb
->Printf("got manifest connection %d\n", manifest_conn
);
526 if (-1 == manifest_conn
) {
527 sb
->Printf("could not connect\n");
531 // build the SRPC connection (do service discovery)
532 if (!NaClSrpcClientCtor(&manifest_channel
, manifest_conn
)) {
533 sb
->Printf("could not build srpc client\n");
539 sb
->Printf("Invoking name service lookup\n");
540 if (NACL_SRPC_RESULT_OK
!=
541 NaClSrpcInvokeBySignature(&manifest_channel
,
542 NACL_NAME_SERVICE_LOOKUP
,
543 "files/test_file", O_RDONLY
,
545 sb
->Printf("manifest lookup RPC failed\n");
546 NaClSrpcDtor(&manifest_channel
);
551 sb
->Printf("File Contents:\n");
555 while ((len
= read(desc
, buffer
, sizeof buffer
- 1)) > 0) {
556 // NB: fgets does not discard the newline nor any carriage return
557 // character before that.
559 // Note that CR LF is the default end-of-line style for Windows.
560 // Furthermore, when the test_file (input data, which happens to
561 // be the nmf file) is initially created in a change list, the
562 // patch is sent to our try bots as text. This means that when
563 // the file arrives, it has CR LF endings instead of the original
564 // LF line endings. Since the expected or golden data is
565 // (manually) encoded in the HTML file's JavaScript, there will be
566 // a mismatch. After submission, the svn property svn:eol-style
567 // will be set to LF, so a clean check out should have LF and not
568 // CR LF endings, and the tests will pass without CR removal.
569 // However -- and there's always a however in long discourses --
570 // if the nmf file is edited, say, because the test is being
571 // modified, and the modification is being done on a Windows
572 // machine, then it is likely that the editor used by the
573 // programmer will convert the file to CR LF endings. Which,
574 // unfortunatly, implies that the test will mysteriously fail
577 // To defend against such nonsense, we weaken the test slighty,
578 // and just strip the CR if it is present.
579 if (len
>= 2 && buffer
[len
-1] == '\n' && buffer
[len
-2] == '\r') {
580 buffer
[len
-2] = '\n';
581 buffer
[len
-1] = '\0';
585 sb
->Printf("%s", buffer
);
587 NaClSrpcDtor(&manifest_channel
);
591 // HandleMessage gets invoked when postMessage is called on the DOM
592 // element associated with this plugin instance. In this case, if we
593 // are given a string, we'll post a message back to JavaScript with a
594 // reply -- essentially treating this as a string-based RPC.
595 void MyInstance::HandleMessage(const pp::Var
& message
) {
596 if (message
.is_string()) {
598 "HandleMessage: enqueuing %s\n", message
.AsString().c_str());
600 worker_
->Enqueue(new WorkRequest(message
.AsString()));
602 fprintf(stderr
, "HandleMessage: message is not a string\n");
607 void *worker_thread_start(void *arg
) {
608 Worker
*worker
= reinterpret_cast<Worker
*>(arg
);
610 fprintf(stderr
, "Sleeping...\n"); fflush(stderr
);
612 fprintf(stderr
, "worker_thread_start: worker %p\n",
613 reinterpret_cast<void *>(worker
));
615 worker
->RunToCompletion();
616 worker
= NULL
; // RunToCompletion automatically Unrefs
617 return reinterpret_cast<void *>(NULL
);
620 // This object is the global object representing this plugin library as long
622 class MyModule
: public pp::Module
{
624 MyModule() : pp::Module() {}
625 virtual ~MyModule() {}
627 // Override CreateInstance to create your customized Instance object.
628 virtual pp::Instance
*CreateInstance(PP_Instance instance
);
630 DISALLOW_COPY_AND_ASSIGN(MyModule
);
633 pp::Instance
*MyModule::CreateInstance(PP_Instance pp_instance
) {
634 MyInstance
*instance
= new MyInstance(pp_instance
);
635 // spawn worker thread associated with this instance
638 fprintf(stderr
, "CreateInstance invoked\n"); fflush(NULL
);
639 if (0 != pthread_create(&thread
,
640 reinterpret_cast<pthread_attr_t
*>(NULL
),
642 reinterpret_cast<void *>(instance
->worker()))) {
643 // Remove the reference the ownership of which should have been
644 // passed to the worker thread.
645 instance
->worker()->Unref();
648 fprintf(stderr
, "pthread_create failed\n"); fflush(NULL
);
650 fprintf(stderr
, "CreateInstance: Worker thread started\n");
651 fprintf(stderr
, "CreateInstance: worker thread object %p\n",
652 reinterpret_cast<void *>(instance
->worker()));
653 (void) pthread_detach(thread
);
655 fprintf(stderr
, "CreateInstance: returning instance %p\n",
656 reinterpret_cast<void *>(instance
));
663 // Factory function for your specialization of the Module object.
664 Module
* CreateModule() {
665 fprintf(stderr
, "CreateModule invoked\n"); fflush(NULL
);
666 return new MyModule();