aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llplugin/llpluginprocessparent.cpp
diff options
context:
space:
mode:
authorArmin Weatherwax2010-06-14 12:04:49 +0200
committerArmin Weatherwax2010-09-23 15:38:25 +0200
commit35df5441d3e2789663532c948731aff3a1e04728 (patch)
treeac7674289784a5f96106ea507637055a8dada78a /linden/indra/llplugin/llpluginprocessparent.cpp
parentChanged version to Experimental 2010.09.18 (diff)
downloadmeta-impy-35df5441d3e2789663532c948731aff3a1e04728.zip
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.gz
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.bz2
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.xz
llmediaplugins first step
Diffstat (limited to '')
-rw-r--r--linden/indra/llplugin/llpluginprocessparent.cpp714
1 files changed, 714 insertions, 0 deletions
diff --git a/linden/indra/llplugin/llpluginprocessparent.cpp b/linden/indra/llplugin/llpluginprocessparent.cpp
new file mode 100644
index 0000000..bd36d11
--- /dev/null
+++ b/linden/indra/llplugin/llpluginprocessparent.cpp
@@ -0,0 +1,714 @@
1/**
2 * @file llpluginprocessparent.cpp
3 * @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, Linden Research, Inc.
8 *
9 * Second Life Viewer Source Code
10 * The source code in this file ("Source Code") is provided by Linden Lab
11 * to you under the terms of the GNU General Public License, version 2.0
12 * ("GPL"), unless you have obtained a separate licensing agreement
13 * ("Other License"), formally executed by you and Linden Lab. Terms of
14 * the GPL can be found in doc/GPL-license.txt in this distribution, or
15 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
16 *
17 * There are special exceptions to the terms and conditions of the GPL as
18 * it is applied to this Source Code. View the full text of the exception
19 * in the file doc/FLOSS-exception.txt in this software distribution, or
20 * online at
21 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
22 *
23 * By copying, modifying or distributing this software, you acknowledge
24 * that you have read and understood your obligations described above,
25 * and agree to abide by those obligations.
26 *
27 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
28 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
29 * COMPLETENESS OR PERFORMANCE.
30 * $/LicenseInfo$
31 */
32
33#include "linden_common.h"
34
35#include "llpluginprocessparent.h"
36#include "llpluginmessagepipe.h"
37#include "llpluginmessageclasses.h"
38
39#include "llapr.h"
40
41//virtual
42LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
43{
44
45}
46
47LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
48{
49 mOwner = owner;
50 mBoundPort = 0;
51 mState = STATE_UNINITIALIZED;
52 mDisableTimeout = false;
53 mDebug = false;
54
55 mPluginLaunchTimeout = 60.0f;
56 mPluginLockupTimeout = 30.0f;
57
58 // Don't start the timer here -- start it when we actually launch the plugin process.
59 mHeartbeat.stop();
60}
61
62LLPluginProcessParent::~LLPluginProcessParent()
63{
64 LL_DEBUGS("Plugin") << "destructor" << LL_ENDL;
65
66 // Destroy any remaining shared memory regions
67 sharedMemoryRegionsType::iterator iter;
68 while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end())
69 {
70 // destroy the shared memory region
71 iter->second->destroy();
72
73 // and remove it from our map
74 mSharedMemoryRegions.erase(iter);
75 }
76
77 // orphaning the process means it won't be killed when the LLProcessLauncher is destructed.
78 // This is what we want -- it should exit cleanly once it notices the sockets have been closed.
79 mProcess.orphan();
80 killSockets();
81}
82
83void LLPluginProcessParent::killSockets(void)
84{
85 killMessagePipe();
86 mListenSocket.reset();
87 mSocket.reset();
88}
89
90void LLPluginProcessParent::errorState(void)
91{
92 if(mState < STATE_RUNNING)
93 setState(STATE_LAUNCH_FAILURE);
94 else
95 setState(STATE_ERROR);
96}
97
98void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
99{
100 mProcess.setExecutable(launcher_filename);
101 mPluginFile = plugin_filename;
102 mCPUUsage = 0.0f;
103 mDebug = debug;
104 mUserDataPath = user_data_path;
105
106 setState(STATE_INITIALIZED);
107}
108
109bool LLPluginProcessParent::accept()
110{
111 bool result = false;
112
113 apr_status_t status = APR_EGENERAL;
114 apr_socket_t *new_socket = NULL;
115
116 status = apr_socket_accept(
117 &new_socket,
118 mListenSocket->getSocket(),
119 gAPRPoolp);
120
121
122 if(status == APR_SUCCESS)
123 {
124// llinfos << "SUCCESS" << llendl;
125 // Success. Create a message pipe on the new socket
126
127 // we MUST create a new pool for the LLSocket, since it will take ownership of it and delete it in its destructor!
128 apr_pool_t* new_pool = NULL;
129 status = apr_pool_create(&new_pool, gAPRPoolp);
130
131 mSocket = LLSocket::create(new_socket, new_pool);
132 new LLPluginMessagePipe(this, mSocket);
133
134 result = true;
135 }
136 else if(APR_STATUS_IS_EAGAIN(status))
137 {
138// llinfos << "EAGAIN" << llendl;
139
140 // No incoming connections. This is not an error.
141 status = APR_SUCCESS;
142 }
143 else
144 {
145// llinfos << "Error:" << llendl;
146 ll_apr_warn_status(status);
147
148 // Some other error.
149 errorState();
150 }
151
152 return result;
153}
154
155void LLPluginProcessParent::idle(void)
156{
157 bool idle_again;
158
159 do
160 {
161 // Give time to network processing
162 if(mMessagePipe)
163 {
164 if(!mMessagePipe->pump())
165 {
166// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL;
167 errorState();
168 }
169 }
170
171 if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING))
172 {
173 // The socket is in an error state -- the plugin is gone.
174 LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
175 errorState();
176 }
177
178 // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
179 // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return.
180 // When in doubt, don't do it.
181 idle_again = false;
182 switch(mState)
183 {
184 case STATE_UNINITIALIZED:
185 break;
186
187 case STATE_INITIALIZED:
188 {
189
190 apr_status_t status = APR_SUCCESS;
191 apr_sockaddr_t* addr = NULL;
192 mListenSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
193 mBoundPort = 0;
194
195 // This code is based on parts of LLSocket::create() in lliosocket.cpp.
196
197 status = apr_sockaddr_info_get(
198 &addr,
199 "127.0.0.1",
200 APR_INET,
201 0, // port 0 = ephemeral ("find me a port")
202 0,
203 gAPRPoolp);
204
205 if(ll_apr_warn_status(status))
206 {
207 killSockets();
208 errorState();
209 break;
210 }
211
212 // This allows us to reuse the address on quick down/up. This is unlikely to create problems.
213 ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1));
214
215 status = apr_socket_bind(mListenSocket->getSocket(), addr);
216 if(ll_apr_warn_status(status))
217 {
218 killSockets();
219 errorState();
220 break;
221 }
222
223 // Get the actual port the socket was bound to
224 {
225 apr_sockaddr_t* bound_addr = NULL;
226 if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket())))
227 {
228 killSockets();
229 errorState();
230 break;
231 }
232 mBoundPort = bound_addr->port;
233
234 if(mBoundPort == 0)
235 {
236 LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL;
237
238 killSockets();
239 errorState();
240 break;
241 }
242 }
243
244 LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL;
245
246 // Make the listen socket non-blocking
247 status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1);
248 if(ll_apr_warn_status(status))
249 {
250 killSockets();
251 errorState();
252 break;
253 }
254
255 apr_socket_timeout_set(mListenSocket->getSocket(), 0);
256 if(ll_apr_warn_status(status))
257 {
258 killSockets();
259 errorState();
260 break;
261 }
262
263 // If it's a stream based socket, we need to tell the OS
264 // to keep a queue of incoming connections for ACCEPT.
265 status = apr_socket_listen(
266 mListenSocket->getSocket(),
267 10); // FIXME: Magic number for queue size
268
269 if(ll_apr_warn_status(status))
270 {
271 killSockets();
272 errorState();
273 break;
274 }
275
276 // If we got here, we're listening.
277 setState(STATE_LISTENING);
278 }
279 break;
280
281 case STATE_LISTENING:
282 {
283 // Launch the plugin process.
284
285 // Only argument to the launcher is the port number we're listening on
286 std::stringstream stream;
287 stream << mBoundPort;
288 mProcess.addArgument(stream.str());
289 if(mProcess.launch() != 0)
290 {
291 errorState();
292 }
293 else
294 {
295 if(mDebug)
296 {
297 #if LL_DARWIN
298 // If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue.
299
300 // The command we're constructing would look like this on the command line:
301 // osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
302
303 std::stringstream cmd;
304
305 mDebugger.setExecutable("/usr/bin/osascript");
306 mDebugger.addArgument("-e");
307 mDebugger.addArgument("tell application \"Terminal\"");
308 mDebugger.addArgument("-e");
309 cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
310 mDebugger.addArgument(cmd.str());
311 mDebugger.addArgument("-e");
312 mDebugger.addArgument("do script \"continue\" in win");
313 mDebugger.addArgument("-e");
314 mDebugger.addArgument("end tell");
315 mDebugger.launch();
316
317 #endif
318 }
319
320 // This will allow us to time out if the process never starts.
321 mHeartbeat.start();
322 mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout);
323 setState(STATE_LAUNCHED);
324 }
325 }
326 break;
327
328 case STATE_LAUNCHED:
329 // waiting for the plugin to connect
330 if(pluginLockedUpOrQuit())
331 {
332 errorState();
333 }
334 else
335 {
336 // Check for the incoming connection.
337 if(accept())
338 {
339 // Stop listening on the server port
340 mListenSocket.reset();
341 setState(STATE_CONNECTED);
342 }
343 }
344 break;
345
346 case STATE_CONNECTED:
347 // waiting for hello message from the plugin
348
349 if(pluginLockedUpOrQuit())
350 {
351 errorState();
352 }
353 break;
354
355 case STATE_HELLO:
356 LL_DEBUGS("Plugin") << "received hello message" << llendl;
357
358 // Send the message to load the plugin
359 {
360 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin");
361 message.setValue("file", mPluginFile);
362 message.setValue("user_data_path", mUserDataPath);
363 sendMessage(message);
364 }
365
366 setState(STATE_LOADING);
367 break;
368
369 case STATE_LOADING:
370 // The load_plugin_response message will kick us from here into STATE_RUNNING
371 if(pluginLockedUpOrQuit())
372 {
373 errorState();
374 }
375 break;
376
377 case STATE_RUNNING:
378 if(pluginLockedUpOrQuit())
379 {
380 errorState();
381 }
382 break;
383
384 case STATE_EXITING:
385 if(!mProcess.isRunning())
386 {
387 setState(STATE_CLEANUP);
388 }
389 else if(pluginLockedUp())
390 {
391 LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl;
392 errorState();
393 }
394 break;
395
396 case STATE_LAUNCH_FAILURE:
397 if(mOwner != NULL)
398 {
399 mOwner->pluginLaunchFailed();
400 }
401 setState(STATE_CLEANUP);
402 break;
403
404 case STATE_ERROR:
405 if(mOwner != NULL)
406 {
407 mOwner->pluginDied();
408 }
409 setState(STATE_CLEANUP);
410 break;
411
412 case STATE_CLEANUP:
413 // Don't do a kill here anymore -- closing the sockets is the new 'kill'.
414 mProcess.orphan();
415 killSockets();
416 setState(STATE_DONE);
417 break;
418
419
420 case STATE_DONE:
421 // just sit here.
422 break;
423
424 }
425
426 } while (idle_again);
427}
428
429bool LLPluginProcessParent::isLoading(void)
430{
431 bool result = false;
432
433 if(mState <= STATE_LOADING)
434 result = true;
435
436 return result;
437}
438
439bool LLPluginProcessParent::isRunning(void)
440{
441 bool result = false;
442
443 if(mState == STATE_RUNNING)
444 result = true;
445
446 return result;
447}
448
449bool LLPluginProcessParent::isDone(void)
450{
451 bool result = false;
452
453 if(mState == STATE_DONE)
454 result = true;
455
456 return result;
457}
458
459void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send)
460{
461 if(force_send || (sleep_time != mSleepTime))
462 {
463 // Cache the time locally
464 mSleepTime = sleep_time;
465
466 if(canSendMessage())
467 {
468 // and send to the plugin.
469 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time");
470 message.setValueReal("time", mSleepTime);
471 sendMessage(message);
472 }
473 else
474 {
475 // Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later.
476 }
477 }
478}
479
480void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
481{
482
483 std::string buffer = message.generate();
484 LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;
485 writeMessageRaw(buffer);
486}
487
488
489void LLPluginProcessParent::receiveMessageRaw(const std::string &message)
490{
491 LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL;
492
493 // FIXME: should this go into a queue instead?
494
495 LLPluginMessage parsed;
496 if(parsed.parse(message) != -1)
497 {
498 receiveMessage(parsed);
499 }
500}
501
502void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message)
503{
504 std::string message_class = message.getClass();
505 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
506 {
507 // internal messages should be handled here
508 std::string message_name = message.getName();
509 if(message_name == "hello")
510 {
511 if(mState == STATE_CONNECTED)
512 {
513 // Plugin host has launched. Tell it which plugin to load.
514 setState(STATE_HELLO);
515 }
516 else
517 {
518 LL_WARNS("Plugin") << "received hello message in wrong state -- bailing out" << LL_ENDL;
519 errorState();
520 }
521
522 }
523 else if(message_name == "load_plugin_response")
524 {
525 if(mState == STATE_LOADING)
526 {
527 // Plugin has been loaded.
528
529 mPluginVersionString = message.getValue("plugin_version");
530 LL_INFOS("Plugin") << "plugin version string: " << mPluginVersionString << LL_ENDL;
531
532 // Check which message classes/versions the plugin supports.
533 // TODO: check against current versions
534 // TODO: kill plugin on major mismatches?
535 mMessageClassVersions = message.getValueLLSD("versions");
536 LLSD::map_iterator iter;
537 for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++)
538 {
539 LL_INFOS("Plugin") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL;
540 }
541
542 // Send initial sleep time
543 setSleepTime(mSleepTime, true);
544
545 setState(STATE_RUNNING);
546 }
547 else
548 {
549 LL_WARNS("Plugin") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL;
550 errorState();
551 }
552 }
553 else if(message_name == "heartbeat")
554 {
555 // this resets our timer.
556 mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
557
558 mCPUUsage = message.getValueReal("cpu_usage");
559
560 LL_DEBUGS("Plugin") << "cpu usage reported as " << mCPUUsage << LL_ENDL;
561
562 }
563 else if(message_name == "shm_add_response")
564 {
565 // Nothing to do here.
566 }
567 else if(message_name == "shm_remove_response")
568 {
569 std::string name = message.getValue("name");
570 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
571
572 if(iter != mSharedMemoryRegions.end())
573 {
574 // destroy the shared memory region
575 iter->second->destroy();
576
577 // and remove it from our map
578 mSharedMemoryRegions.erase(iter);
579 }
580 }
581 else
582 {
583 LL_WARNS("Plugin") << "Unknown internal message from child: " << message_name << LL_ENDL;
584 }
585 }
586 else
587 {
588 if(mOwner != NULL)
589 {
590 mOwner->receivePluginMessage(message);
591 }
592 }
593}
594
595std::string LLPluginProcessParent::addSharedMemory(size_t size)
596{
597 std::string name;
598
599 LLPluginSharedMemory *region = new LLPluginSharedMemory;
600
601 // This is a new region
602 if(region->create(size))
603 {
604 name = region->getName();
605
606 mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region));
607
608 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add");
609 message.setValue("name", name);
610 message.setValueS32("size", (S32)size);
611 sendMessage(message);
612 }
613 else
614 {
615 LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL;
616
617 // Don't leak
618 delete region;
619 }
620
621 return name;
622}
623
624void LLPluginProcessParent::removeSharedMemory(const std::string &name)
625{
626 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
627
628 if(iter != mSharedMemoryRegions.end())
629 {
630 // This segment exists. Send the message to the child to unmap it. The response will cause the parent to unmap our end.
631 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove");
632 message.setValue("name", name);
633 sendMessage(message);
634 }
635 else
636 {
637 LL_WARNS("Plugin") << "Request to remove an unknown shared memory segment." << LL_ENDL;
638 }
639}
640size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name)
641{
642 size_t result = 0;
643
644 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
645 if(iter != mSharedMemoryRegions.end())
646 {
647 result = iter->second->getSize();
648 }
649
650 return result;
651}
652void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name)
653{
654 void *result = NULL;
655
656 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
657 if(iter != mSharedMemoryRegions.end())
658 {
659 result = iter->second->getMappedAddress();
660 }
661
662 return result;
663}
664
665std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class)
666{
667 std::string result;
668
669 if(mMessageClassVersions.has(message_class))
670 {
671 result = mMessageClassVersions[message_class].asString();
672 }
673
674 return result;
675}
676
677std::string LLPluginProcessParent::getPluginVersion(void)
678{
679 return mPluginVersionString;
680}
681
682void LLPluginProcessParent::setState(EState state)
683{
684 LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
685 mState = state;
686};
687
688bool LLPluginProcessParent::pluginLockedUpOrQuit()
689{
690 bool result = false;
691
692 if(!mDisableTimeout && !mDebug)
693 {
694 if(!mProcess.isRunning())
695 {
696 LL_WARNS("Plugin") << "child exited" << llendl;
697 result = true;
698 }
699 else if(pluginLockedUp())
700 {
701 LL_WARNS("Plugin") << "timeout" << llendl;
702 result = true;
703 }
704 }
705
706 return result;
707}
708
709bool LLPluginProcessParent::pluginLockedUp()
710{
711 // If the timer is running and has expired, the plugin has locked up.
712 return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
713}
714