OLD | NEW |
| (Empty) |
1 /* | |
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. | |
5 */ | |
6 | |
7 // | |
8 // Post-message based test for simple rpc based access to name services. | |
9 // | |
10 | |
11 #include <string> | |
12 | |
13 #include <assert.h> | |
14 #include <stdio.h> | |
15 #include <stdlib.h> | |
16 #include <inttypes.h> | |
17 #include <sys/fcntl.h> | |
18 #include <string.h> | |
19 #include <unistd.h> | |
20 #include <pthread.h> | |
21 | |
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" | |
29 | |
30 // TODO(bsy): move weak_ref module to the shared directory | |
31 #include "native_client/src/trusted/weak_ref/weak_ref.h" | |
32 | |
33 #include "ppapi/cpp/instance.h" | |
34 #include "ppapi/cpp/module.h" | |
35 #include "ppapi/cpp/var.h" | |
36 | |
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" | |
40 | |
41 class PostStringMessageWrapper | |
42 : public nacl_ppapi::EventThreadWorkStateWrapper<nacl_ppapi::VoidResult> { | |
43 public: | |
44 PostStringMessageWrapper(nacl_ppapi::EventThreadWorkState< | |
45 nacl_ppapi::VoidResult> | |
46 *state, | |
47 const std::string &msg) | |
48 : nacl_ppapi::EventThreadWorkStateWrapper<nacl_ppapi::VoidResult>( | |
49 state), | |
50 msg_(msg) {} | |
51 ~PostStringMessageWrapper(); | |
52 const std::string &msg() const { return msg_; } | |
53 private: | |
54 std::string msg_; | |
55 | |
56 DISALLOW_COPY_AND_ASSIGN(PostStringMessageWrapper); | |
57 }; | |
58 | |
59 // --------------------------------------------------------------------------- | |
60 | |
61 class MyInstance; | |
62 | |
63 struct WorkRequest { | |
64 explicit WorkRequest(const std::string &message) | |
65 : msg(message), | |
66 next(reinterpret_cast<WorkRequest *>(NULL)) {} | |
67 std::string msg; // copied from HandleMessage | |
68 WorkRequest *next; | |
69 private: | |
70 DISALLOW_COPY_AND_ASSIGN(WorkRequest); | |
71 }; | |
72 | |
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*). | |
83 class Worker { | |
84 public: | |
85 explicit Worker(MyInstance *instance); | |
86 | |
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 | |
91 // RunToCompletion(). | |
92 void RunToCompletion(); | |
93 | |
94 WorkRequest *Dequeue(); | |
95 void Enqueue(WorkRequest *req); | |
96 | |
97 void Initialize(nacl::StringBuffer *sb); | |
98 void ManifestListTest(nacl::StringBuffer *sb); | |
99 void ManifestOpenTest(nacl::StringBuffer *sb); | |
100 | |
101 // Called on the event thread as part of the instance shutdown. | |
102 // Automatically unreferences the Worker object, so the event thread | |
103 // should stop using the Worker pointer after invoking ShouldExit. | |
104 void ShouldExit(); | |
105 | |
106 void Unref(); // used only for error cleanup, e.g., when the worker | |
107 // thread did not launch. | |
108 | |
109 bool InitializeChannel(nacl::StringBuffer *sb); | |
110 | |
111 protected: | |
112 // Event thread operation(s): | |
113 // | |
114 // In order for a test method to send reply messages, it should use | |
115 // this PostStringMessage method, since (currently) the PostMessage | |
116 // interface is event thread-only and not thread-safe. Returns true | |
117 // if successful and the thread should continue to do work, false | |
118 // otherwise (anchor has been abandoned). | |
119 bool PostStringMessage(const std::string &msg); | |
120 // ... more Event thread operations here. | |
121 | |
122 private: | |
123 MyInstance *instance_; // cannot use directly from test worker thread! | |
124 nacl::WeakRefAnchor *anchor_; | |
125 // must copy out and Ref in ctor, since instance_ might go bad at any time. | |
126 | |
127 NaClMutex mu_; | |
128 NaClCondVar cv_; // queue not empty or should_exit_ | |
129 | |
130 int ref_count_; | |
131 WorkRequest *queue_head_; | |
132 WorkRequest **queue_insert_; | |
133 | |
134 bool should_exit_; | |
135 | |
136 ~Worker(); | |
137 | |
138 WorkRequest *Dequeue_mu(); | |
139 void Enqueue_mu(WorkRequest *req); | |
140 | |
141 struct DispatchTable { | |
142 char const *op_name; | |
143 void (Worker::*mfunc)(nacl::StringBuffer *sb); | |
144 }; | |
145 | |
146 bool ns_channel_initialized_; | |
147 NaClSrpcChannel ns_channel_; | |
148 | |
149 static DispatchTable const kDispatch[]; // null terminated | |
150 | |
151 DISALLOW_COPY_AND_ASSIGN(Worker); | |
152 }; | |
153 | |
154 // This object represents one time the page says <embed>. | |
155 class MyInstance : public nacl_ppapi::NaClPpapiPluginInstance { | |
156 public: | |
157 explicit MyInstance(PP_Instance instance); | |
158 virtual ~MyInstance(); | |
159 virtual void HandleMessage(const pp::Var& message_data); | |
160 | |
161 Worker *worker() { return worker_; } | |
162 | |
163 // used with plugin::WeakRefCompletionCallback | |
164 void PostStringMessage_EventThread(PostStringMessageWrapper *msg_wrapper, | |
165 int32_t err); | |
166 private: | |
167 Worker *worker_; | |
168 | |
169 DISALLOW_COPY_AND_ASSIGN(MyInstance); | |
170 }; | |
171 | |
172 // --------------------------------------------------------------------------- | |
173 | |
174 PostStringMessageWrapper::~PostStringMessageWrapper() {} | |
175 | |
176 // --------------------------------------------------------------------------- | |
177 | |
178 MyInstance::MyInstance(PP_Instance instance) | |
179 : nacl_ppapi::NaClPpapiPluginInstance(instance), | |
180 worker_(new Worker(this)) { | |
181 } | |
182 | |
183 MyInstance::~MyInstance() { | |
184 worker_->ShouldExit(); | |
185 } | |
186 | |
187 void MyInstance::PostStringMessage_EventThread( | |
188 PostStringMessageWrapper *msg_wrapper, | |
189 int32_t err) { | |
190 PostMessage(msg_wrapper->msg()); | |
191 msg_wrapper->SetResult(nacl_ppapi::g_void_result); | |
192 } | |
193 | |
194 // --------------------------------------------------------------------------- | |
195 | |
196 Worker::Worker(MyInstance *instance) | |
197 : instance_(instance), | |
198 anchor_(instance->anchor()->Ref()), | |
199 ref_count_(2), // one for the master and one for the dame... | |
200 queue_head_(NULL), | |
201 queue_insert_(&queue_head_), | |
202 should_exit_(false), | |
203 ns_channel_initialized_(false) { | |
204 NaClXMutexCtor(&mu_); | |
205 NaClXCondVarCtor(&cv_); | |
206 } | |
207 | |
208 void Worker::Unref() { | |
209 bool do_delete; | |
210 do { | |
211 nacl::MutexLocker take(&mu_); | |
212 do_delete = (--ref_count_ == 0); | |
213 } while (0); | |
214 // dropped lock before invoking dtor | |
215 if (do_delete) { | |
216 delete this; | |
217 } | |
218 } | |
219 | |
220 Worker::~Worker() { | |
221 anchor_->Unref(); | |
222 | |
223 WorkRequest *req; | |
224 while ((req = Dequeue_mu()) != NULL) { | |
225 delete req; | |
226 } | |
227 | |
228 NaClMutexDtor(&mu_); | |
229 NaClCondVarDtor(&cv_); | |
230 } | |
231 | |
232 void Worker::ShouldExit() { | |
233 do { | |
234 nacl::MutexLocker take(&mu_); | |
235 should_exit_ = true; | |
236 NaClXCondVarBroadcast(&cv_); | |
237 } while (0); | |
238 Unref(); | |
239 } | |
240 | |
241 WorkRequest *Worker::Dequeue_mu() { | |
242 WorkRequest *head = queue_head_; | |
243 | |
244 if (head != NULL) { | |
245 queue_head_ = head->next; | |
246 if (queue_head_ == NULL) { | |
247 queue_insert_ = &queue_head_; | |
248 } | |
249 } | |
250 return head; | |
251 } | |
252 | |
253 void Worker::Enqueue_mu(WorkRequest *req) { | |
254 req->next = NULL; | |
255 *queue_insert_ = req; | |
256 queue_insert_ = &req->next; | |
257 } | |
258 | |
259 WorkRequest *Worker::Dequeue() { | |
260 nacl::MutexLocker take(&mu_); | |
261 return Dequeue_mu(); | |
262 } | |
263 | |
264 void Worker::Enqueue(WorkRequest *req) { | |
265 nacl::MutexLocker take(&mu_); | |
266 Enqueue_mu(req); | |
267 NaClXCondVarBroadcast(&cv_); | |
268 } | |
269 | |
270 Worker::DispatchTable const Worker::kDispatch[] = { | |
271 { "init", &Worker::Initialize }, | |
272 { "manifest_open", &Worker::ManifestOpenTest }, | |
273 { reinterpret_cast<char const *>(NULL), NULL } | |
274 }; | |
275 | |
276 bool Worker::PostStringMessage(const std::string &msg) { | |
277 nacl_ppapi::EventThreadWorkState<nacl_ppapi::VoidResult> state; | |
278 plugin::WeakRefCallOnMainThread(anchor_, | |
279 0 /* mS */, | |
280 instance_, | |
281 &MyInstance::PostStringMessage_EventThread, | |
282 new PostStringMessageWrapper(&state, msg)); | |
283 if (NULL == state.WaitForCompletion()) { | |
284 // anchor_ has been abandoned, so the plugin instance went away. | |
285 // we should drop our ref to the anchor, then shut down the worker | |
286 // thread. | |
287 nacl::MutexLocker take(&mu_); | |
288 should_exit_ = true; | |
289 // There's no need to condvar broadcast, since it is the worker | |
290 // thread that will look at the work queue and the should_exit_ to | |
291 // act on this. Unfortunately every worker thread must test the | |
292 // return value of PostStringMessage to determine if it should do | |
293 // early exit (if the worker needs to do multiple event-thread | |
294 // operations). | |
295 return false; | |
296 } | |
297 return true; | |
298 } | |
299 | |
300 void Worker::RunToCompletion() { | |
301 for (;;) { | |
302 WorkRequest *req; | |
303 do { | |
304 nacl::MutexLocker take(&mu_); | |
305 for (;;) { | |
306 if (should_exit_) { | |
307 // drop the lock and drop the reference count to this | |
308 goto break_x3; | |
309 } | |
310 fprintf(stderr, "RunToCompletion: Dequeuing...\n"); | |
311 if ((req = Dequeue_mu()) != NULL) { | |
312 fprintf(stderr, "RunToCompletion: found work %p\n", | |
313 reinterpret_cast<void *>(req)); | |
314 break; | |
315 } | |
316 fprintf(stderr, "RunToCompletion: waiting\n"); | |
317 NaClXCondVarWait(&cv_, &mu_); | |
318 fprintf(stderr, "RunToCompletion: woke up\n"); | |
319 } | |
320 } while (0); | |
321 | |
322 // Do the work, without holding the lock. The work function | |
323 // should reacquire mu_ as needed. | |
324 | |
325 nacl::StringBuffer sb; | |
326 | |
327 // scan dispatch table for op_name | |
328 fprintf(stderr, "RunToCompletion: scanning for %s\n", req->msg.c_str()); | |
329 for (size_t ix = 0; kDispatch[ix].op_name != NULL; ++ix) { | |
330 fprintf(stderr, | |
331 "RunToCompletion: comparing against %s\n", kDispatch[ix].op_name); | |
332 if (req->msg == kDispatch[ix].op_name) { | |
333 if (InitializeChannel(&sb)) { | |
334 fprintf(stderr, "RunToCompletion: invoking table entry %u\n", ix); | |
335 (this->*(kDispatch[ix].mfunc))(&sb); | |
336 } | |
337 break; | |
338 } | |
339 } | |
340 // always post a reply, even if it is the empty string | |
341 fprintf(stderr, | |
342 "RunToCompletion: posting reply %s\n", sb.ToString().c_str()); | |
343 if (!PostStringMessage(sb.ToString())) { | |
344 break; | |
345 } | |
346 } | |
347 break_x3: | |
348 fprintf(stderr, "RunToCompletion: exiting\n"); | |
349 Unref(); | |
350 } | |
351 | |
352 bool Worker::InitializeChannel(nacl::StringBuffer *sb) { | |
353 if (ns_channel_initialized_) { | |
354 return true; | |
355 } | |
356 int ns = -1; | |
357 nacl_nameservice(&ns); | |
358 printf("ns = %d\n", ns); | |
359 assert(-1 != ns); | |
360 int connected_socket = imc_connect(ns); | |
361 assert(-1 != connected_socket); | |
362 if (!NaClSrpcClientCtor(&ns_channel_, connected_socket)) { | |
363 sb->Printf("Srpc client channel ctor failed\n"); | |
364 close(ns); | |
365 return false; | |
366 } | |
367 sb->Printf("NaClSrpcClientCtor succeeded\n"); | |
368 close(ns); | |
369 ns_channel_initialized_ = true; | |
370 return true; | |
371 } | |
372 | |
373 void Worker::Initialize(nacl::StringBuffer *sb) { | |
374 // we just want the log output from the InitializeChannel | |
375 return; | |
376 } | |
377 | |
378 void Worker::ManifestOpenTest(nacl::StringBuffer *sb) { | |
379 int status = -1; | |
380 int manifest; | |
381 struct NaClSrpcChannel manifest_channel; | |
382 | |
383 // name service lookup for the manifest service descriptor | |
384 if (NACL_SRPC_RESULT_OK != | |
385 NaClSrpcInvokeBySignature(&ns_channel_, NACL_NAME_SERVICE_LOOKUP, | |
386 "ManifestNameService", O_RDWR, | |
387 &status, &manifest) || | |
388 NACL_NAME_SERVICE_SUCCESS != status) { | |
389 sb->Printf("nameservice lookup failed, status %d\n", status); | |
390 return; | |
391 } | |
392 sb->Printf("Got manifest descriptor %d\n", manifest); | |
393 if (-1 == manifest) { | |
394 return; | |
395 } | |
396 | |
397 // connect to manifest name server | |
398 int manifest_conn = imc_connect(manifest); | |
399 close(manifest); | |
400 sb->Printf("got manifest connection %d\n", manifest_conn); | |
401 if (-1 == manifest_conn) { | |
402 sb->Printf("could not connect\n"); | |
403 return; | |
404 } | |
405 | |
406 // build the SRPC connection (do service discovery) | |
407 if (!NaClSrpcClientCtor(&manifest_channel, manifest_conn)) { | |
408 sb->Printf("could not build srpc client\n"); | |
409 return; | |
410 } | |
411 | |
412 int desc; | |
413 | |
414 sb->Printf("Invoking name service lookup\n"); | |
415 if (NACL_SRPC_RESULT_OK != | |
416 NaClSrpcInvokeBySignature(&manifest_channel, | |
417 NACL_NAME_SERVICE_LOOKUP, | |
418 "files/test_file", O_RDONLY, | |
419 &status, &desc)) { | |
420 sb->Printf("manifest lookup RPC failed\n"); | |
421 NaClSrpcDtor(&manifest_channel); | |
422 return; | |
423 } | |
424 | |
425 sb->DiscardOutput(); | |
426 sb->Printf("File Contents:\n"); | |
427 | |
428 char buffer[4096]; | |
429 int len; | |
430 while ((len = read(desc, buffer, sizeof buffer - 1)) > 0) { | |
431 // Null terminate. | |
432 buffer[len] = '\0'; | |
433 sb->Printf("%s", buffer); | |
434 } | |
435 NaClSrpcDtor(&manifest_channel); | |
436 return; | |
437 } | |
438 | |
439 // HandleMessage gets invoked when postMessage is called on the DOM | |
440 // element associated with this plugin instance. In this case, if we | |
441 // are given a string, we'll post a message back to JavaScript with a | |
442 // reply -- essentially treating this as a string-based RPC. | |
443 void MyInstance::HandleMessage(const pp::Var& message) { | |
444 if (message.is_string()) { | |
445 fprintf(stderr, | |
446 "HandleMessage: enqueuing %s\n", message.AsString().c_str()); | |
447 fflush(NULL); | |
448 worker_->Enqueue(new WorkRequest(message.AsString())); | |
449 } else { | |
450 fprintf(stderr, "HandleMessage: message is not a string\n"); | |
451 fflush(NULL); | |
452 } | |
453 } | |
454 | |
455 void *worker_thread_start(void *arg) { | |
456 Worker *worker = reinterpret_cast<Worker *>(arg); | |
457 | |
458 fprintf(stderr, "Sleeping...\n"); fflush(stderr); | |
459 sleep(1); | |
460 fprintf(stderr, "worker_thread_start: worker %p\n", | |
461 reinterpret_cast<void *>(worker)); | |
462 fflush(NULL); | |
463 worker->RunToCompletion(); | |
464 worker = NULL; // RunToCompletion automatically Unrefs | |
465 return reinterpret_cast<void *>(NULL); | |
466 } | |
467 | |
468 // This object is the global object representing this plugin library as long | |
469 // as it is loaded. | |
470 class MyModule : public pp::Module { | |
471 public: | |
472 MyModule() : pp::Module() {} | |
473 virtual ~MyModule() {} | |
474 | |
475 // Override CreateInstance to create your customized Instance object. | |
476 virtual pp::Instance *CreateInstance(PP_Instance instance); | |
477 | |
478 DISALLOW_COPY_AND_ASSIGN(MyModule); | |
479 }; | |
480 | |
481 pp::Instance *MyModule::CreateInstance(PP_Instance pp_instance) { | |
482 MyInstance *instance = new MyInstance(pp_instance); | |
483 // spawn worker thread associated with this instance | |
484 pthread_t thread; | |
485 | |
486 fprintf(stderr, "CreateInstance invoked\n"); fflush(NULL); | |
487 if (0 != pthread_create(&thread, | |
488 reinterpret_cast<pthread_attr_t *>(NULL), | |
489 worker_thread_start, | |
490 reinterpret_cast<void *>(instance->worker()))) { | |
491 // Remove the reference the ownership of which should have been | |
492 // passed to the worker thread. | |
493 instance->worker()->Unref(); | |
494 delete instance; | |
495 instance = NULL; | |
496 fprintf(stderr, "pthread_create failed\n"); fflush(NULL); | |
497 } else { | |
498 fprintf(stderr, "CreateInstance: Worker thread started\n"); | |
499 fprintf(stderr, "CreateInstance: worker thread object %p\n", | |
500 reinterpret_cast<void *>(instance->worker())); | |
501 (void) pthread_detach(thread); | |
502 } | |
503 fprintf(stderr, "CreateInstance: returning instance %p\n", | |
504 reinterpret_cast<void *>(instance)); | |
505 | |
506 return instance; | |
507 } | |
508 | |
509 namespace pp { | |
510 | |
511 // Factory function for your specialization of the Module object. | |
512 Module* CreateModule() { | |
513 fprintf(stderr, "CreateModule invoked\n"); fflush(NULL); | |
514 return new MyModule(); | |
515 } | |
516 | |
517 } // namespace pp | |
OLD | NEW |