diff options
Diffstat (limited to '')
-rwxr-xr-x | linden/indra/llplugin/llpluginprocesschild.cpp | 569 |
1 files changed, 569 insertions, 0 deletions
diff --git a/linden/indra/llplugin/llpluginprocesschild.cpp b/linden/indra/llplugin/llpluginprocesschild.cpp new file mode 100755 index 0000000..8dbf2b3 --- /dev/null +++ b/linden/indra/llplugin/llpluginprocesschild.cpp | |||
@@ -0,0 +1,569 @@ | |||
1 | /** | ||
2 | * @file llpluginprocesschild.cpp | ||
3 | * @brief LLPluginProcessChild handles the child side of the external-process plugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-2010, Linden Research, Inc. | ||
9 | * | ||
10 | * Second Life Viewer Source Code | ||
11 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
12 | * to you under the terms of the GNU General Public License, version 2.0 | ||
13 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
14 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
15 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
16 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
17 | * | ||
18 | * There are special exceptions to the terms and conditions of the GPL as | ||
19 | * it is applied to this Source Code. View the full text of the exception | ||
20 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
21 | * online at | ||
22 | * http://secondlife.com/developers/opensource/flossexception | ||
23 | * | ||
24 | * By copying, modifying or distributing this software, you acknowledge | ||
25 | * that you have read and understood your obligations described above, | ||
26 | * and agree to abide by those obligations. | ||
27 | * | ||
28 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
29 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
30 | * COMPLETENESS OR PERFORMANCE. | ||
31 | * $/LicenseInfo$ | ||
32 | * | ||
33 | * @endcond | ||
34 | */ | ||
35 | |||
36 | #include "linden_common.h" | ||
37 | |||
38 | #include "llpluginprocesschild.h" | ||
39 | #include "llplugininstance.h" | ||
40 | #include "llpluginmessagepipe.h" | ||
41 | #include "llpluginmessageclasses.h" | ||
42 | |||
43 | static const F32 HEARTBEAT_SECONDS = 1.0f; | ||
44 | static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. | ||
45 | |||
46 | LLPluginProcessChild::LLPluginProcessChild() | ||
47 | { | ||
48 | mState = STATE_UNINITIALIZED; | ||
49 | mInstance = NULL; | ||
50 | mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); | ||
51 | mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz | ||
52 | mCPUElapsed = 0.0f; | ||
53 | mBlockingRequest = false; | ||
54 | mBlockingResponseReceived = false; | ||
55 | } | ||
56 | |||
57 | LLPluginProcessChild::~LLPluginProcessChild() | ||
58 | { | ||
59 | if(mInstance != NULL) | ||
60 | { | ||
61 | sendMessageToPlugin(LLPluginMessage("base", "cleanup")); | ||
62 | |||
63 | // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted | ||
64 | // appears to fail and lock up which means that a given instance of the slplugin process never exits. | ||
65 | // This is bad, especially when users try to update their version of SL - it fails because the slplugin | ||
66 | // process as well as a bunch of plugin specific files are locked and cannot be overwritten. | ||
67 | exit( 0 ); | ||
68 | //delete mInstance; | ||
69 | //mInstance = NULL; | ||
70 | } | ||
71 | } | ||
72 | |||
73 | void LLPluginProcessChild::killSockets(void) | ||
74 | { | ||
75 | killMessagePipe(); | ||
76 | mSocket.reset(); | ||
77 | } | ||
78 | |||
79 | void LLPluginProcessChild::init(U32 launcher_port) | ||
80 | { | ||
81 | mLauncherHost = LLHost("127.0.0.1", launcher_port); | ||
82 | setState(STATE_INITIALIZED); | ||
83 | } | ||
84 | |||
85 | void LLPluginProcessChild::idle(void) | ||
86 | { | ||
87 | bool idle_again; | ||
88 | do | ||
89 | { | ||
90 | if(APR_STATUS_IS_EOF(mSocketError)) | ||
91 | { | ||
92 | // Plugin socket was closed. This covers both normal plugin termination and host crashes. | ||
93 | setState(STATE_ERROR); | ||
94 | } | ||
95 | else if(mSocketError != APR_SUCCESS) | ||
96 | { | ||
97 | LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL; | ||
98 | setState(STATE_ERROR); | ||
99 | } | ||
100 | |||
101 | if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) | ||
102 | { | ||
103 | // The pipe has been closed -- we're done. | ||
104 | // TODO: This could be slightly more subtle, but I'm not sure it needs to be. | ||
105 | LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL; | ||
106 | setState(STATE_ERROR); | ||
107 | } | ||
108 | |||
109 | // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). | ||
110 | // 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. | ||
111 | // When in doubt, don't do it. | ||
112 | idle_again = false; | ||
113 | |||
114 | if(mInstance != NULL) | ||
115 | { | ||
116 | // Provide some time to the plugin | ||
117 | mInstance->idle(); | ||
118 | } | ||
119 | |||
120 | switch(mState) | ||
121 | { | ||
122 | case STATE_UNINITIALIZED: | ||
123 | break; | ||
124 | |||
125 | case STATE_INITIALIZED: | ||
126 | if(mSocket->blockingConnect(mLauncherHost)) | ||
127 | { | ||
128 | // This automatically sets mMessagePipe | ||
129 | new LLPluginMessagePipe(this, mSocket); | ||
130 | |||
131 | setState(STATE_CONNECTED); | ||
132 | } | ||
133 | else | ||
134 | { | ||
135 | // connect failed | ||
136 | setState(STATE_ERROR); | ||
137 | } | ||
138 | break; | ||
139 | |||
140 | case STATE_CONNECTED: | ||
141 | sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); | ||
142 | setState(STATE_PLUGIN_LOADING); | ||
143 | break; | ||
144 | |||
145 | case STATE_PLUGIN_LOADING: | ||
146 | if(!mPluginFile.empty()) | ||
147 | { | ||
148 | mInstance = new LLPluginInstance(this); | ||
149 | if(mInstance->load(mPluginFile) == 0) | ||
150 | { | ||
151 | mHeartbeat.start(); | ||
152 | mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); | ||
153 | mCPUElapsed = 0.0f; | ||
154 | setState(STATE_PLUGIN_LOADED); | ||
155 | } | ||
156 | else | ||
157 | { | ||
158 | setState(STATE_ERROR); | ||
159 | } | ||
160 | } | ||
161 | break; | ||
162 | |||
163 | case STATE_PLUGIN_LOADED: | ||
164 | { | ||
165 | setState(STATE_PLUGIN_INITIALIZING); | ||
166 | LLPluginMessage message("base", "init"); | ||
167 | sendMessageToPlugin(message); | ||
168 | } | ||
169 | break; | ||
170 | |||
171 | case STATE_PLUGIN_INITIALIZING: | ||
172 | // waiting for init_response... | ||
173 | break; | ||
174 | |||
175 | case STATE_RUNNING: | ||
176 | if(mInstance != NULL) | ||
177 | { | ||
178 | // Provide some time to the plugin | ||
179 | LLPluginMessage message("base", "idle"); | ||
180 | message.setValueReal("time", PLUGIN_IDLE_SECONDS); | ||
181 | sendMessageToPlugin(message); | ||
182 | |||
183 | mInstance->idle(); | ||
184 | |||
185 | if(mHeartbeat.hasExpired()) | ||
186 | { | ||
187 | |||
188 | // This just proves that we're not stuck down inside the plugin code. | ||
189 | LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); | ||
190 | |||
191 | // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. | ||
192 | // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. | ||
193 | // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. | ||
194 | heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); | ||
195 | |||
196 | sendMessageToParent(heartbeat); | ||
197 | |||
198 | mHeartbeat.reset(); | ||
199 | mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); | ||
200 | mCPUElapsed = 0.0f; | ||
201 | } | ||
202 | } | ||
203 | // receivePluginMessage will transition to STATE_UNLOADING | ||
204 | break; | ||
205 | |||
206 | case STATE_UNLOADING: | ||
207 | if(mInstance != NULL) | ||
208 | { | ||
209 | sendMessageToPlugin(LLPluginMessage("base", "cleanup")); | ||
210 | delete mInstance; | ||
211 | mInstance = NULL; | ||
212 | } | ||
213 | setState(STATE_UNLOADED); | ||
214 | break; | ||
215 | |||
216 | case STATE_UNLOADED: | ||
217 | killSockets(); | ||
218 | setState(STATE_DONE); | ||
219 | break; | ||
220 | |||
221 | case STATE_ERROR: | ||
222 | // Close the socket to the launcher | ||
223 | killSockets(); | ||
224 | // TODO: Where do we go from here? Just exit()? | ||
225 | setState(STATE_DONE); | ||
226 | break; | ||
227 | |||
228 | case STATE_DONE: | ||
229 | // just sit here. | ||
230 | break; | ||
231 | } | ||
232 | |||
233 | } while (idle_again); | ||
234 | } | ||
235 | |||
236 | void LLPluginProcessChild::sleep(F64 seconds) | ||
237 | { | ||
238 | deliverQueuedMessages(); | ||
239 | if(mMessagePipe) | ||
240 | { | ||
241 | mMessagePipe->pump(seconds); | ||
242 | } | ||
243 | else | ||
244 | { | ||
245 | ms_sleep((int)(seconds * 1000.0f)); | ||
246 | } | ||
247 | } | ||
248 | |||
249 | void LLPluginProcessChild::pump(void) | ||
250 | { | ||
251 | deliverQueuedMessages(); | ||
252 | if(mMessagePipe) | ||
253 | { | ||
254 | mMessagePipe->pump(0.0f); | ||
255 | } | ||
256 | else | ||
257 | { | ||
258 | // Should we warn here? | ||
259 | } | ||
260 | } | ||
261 | |||
262 | |||
263 | bool LLPluginProcessChild::isRunning(void) | ||
264 | { | ||
265 | bool result = false; | ||
266 | |||
267 | if(mState == STATE_RUNNING) | ||
268 | result = true; | ||
269 | |||
270 | return result; | ||
271 | } | ||
272 | |||
273 | bool LLPluginProcessChild::isDone(void) | ||
274 | { | ||
275 | bool result = false; | ||
276 | |||
277 | switch(mState) | ||
278 | { | ||
279 | case STATE_DONE: | ||
280 | result = true; | ||
281 | break; | ||
282 | default: | ||
283 | break; | ||
284 | } | ||
285 | |||
286 | return result; | ||
287 | } | ||
288 | |||
289 | void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) | ||
290 | { | ||
291 | if (mInstance) | ||
292 | { | ||
293 | std::string buffer = message.generate(); | ||
294 | |||
295 | LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; | ||
296 | LLTimer elapsed; | ||
297 | |||
298 | mInstance->sendMessage(buffer); | ||
299 | |||
300 | mCPUElapsed += elapsed.getElapsedTimeF64(); | ||
301 | } | ||
302 | else | ||
303 | { | ||
304 | LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL; | ||
305 | } | ||
306 | } | ||
307 | |||
308 | void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) | ||
309 | { | ||
310 | std::string buffer = message.generate(); | ||
311 | |||
312 | LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; | ||
313 | |||
314 | writeMessageRaw(buffer); | ||
315 | } | ||
316 | |||
317 | void LLPluginProcessChild::receiveMessageRaw(const std::string &message) | ||
318 | { | ||
319 | // Incoming message from the TCP Socket | ||
320 | |||
321 | LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; | ||
322 | |||
323 | // Decode this message | ||
324 | LLPluginMessage parsed; | ||
325 | parsed.parse(message); | ||
326 | |||
327 | if(mBlockingRequest) | ||
328 | { | ||
329 | // We're blocking the plugin waiting for a response. | ||
330 | |||
331 | if(parsed.hasValue("blocking_response")) | ||
332 | { | ||
333 | // This is the message we've been waiting for -- fall through and send it immediately. | ||
334 | mBlockingResponseReceived = true; | ||
335 | } | ||
336 | else | ||
337 | { | ||
338 | // Still waiting. Queue this message and don't process it yet. | ||
339 | mMessageQueue.push(message); | ||
340 | return; | ||
341 | } | ||
342 | } | ||
343 | |||
344 | bool passMessage = true; | ||
345 | |||
346 | // FIXME: how should we handle queueing here? | ||
347 | |||
348 | { | ||
349 | std::string message_class = parsed.getClass(); | ||
350 | if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) | ||
351 | { | ||
352 | passMessage = false; | ||
353 | |||
354 | std::string message_name = parsed.getName(); | ||
355 | if(message_name == "load_plugin") | ||
356 | { | ||
357 | mPluginFile = parsed.getValue("file"); | ||
358 | } | ||
359 | else if(message_name == "shm_add") | ||
360 | { | ||
361 | std::string name = parsed.getValue("name"); | ||
362 | size_t size = (size_t)parsed.getValueS32("size"); | ||
363 | |||
364 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
365 | if(iter != mSharedMemoryRegions.end()) | ||
366 | { | ||
367 | // Need to remove the old region first | ||
368 | LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; | ||
369 | } | ||
370 | else | ||
371 | { | ||
372 | // This is a new region | ||
373 | LLPluginSharedMemory *region = new LLPluginSharedMemory; | ||
374 | if(region->attach(name, size)) | ||
375 | { | ||
376 | mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); | ||
377 | |||
378 | std::stringstream addr; | ||
379 | addr << region->getMappedAddress(); | ||
380 | |||
381 | // Send the add notification to the plugin | ||
382 | LLPluginMessage message("base", "shm_added"); | ||
383 | message.setValue("name", name); | ||
384 | message.setValueS32("size", (S32)size); | ||
385 | message.setValuePointer("address", region->getMappedAddress()); | ||
386 | sendMessageToPlugin(message); | ||
387 | |||
388 | // and send the response to the parent | ||
389 | message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); | ||
390 | message.setValue("name", name); | ||
391 | sendMessageToParent(message); | ||
392 | } | ||
393 | else | ||
394 | { | ||
395 | LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; | ||
396 | delete region; | ||
397 | } | ||
398 | } | ||
399 | |||
400 | } | ||
401 | else if(message_name == "shm_remove") | ||
402 | { | ||
403 | std::string name = parsed.getValue("name"); | ||
404 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
405 | if(iter != mSharedMemoryRegions.end()) | ||
406 | { | ||
407 | // forward the remove request to the plugin -- its response will trigger us to detach the segment. | ||
408 | LLPluginMessage message("base", "shm_remove"); | ||
409 | message.setValue("name", name); | ||
410 | sendMessageToPlugin(message); | ||
411 | } | ||
412 | else | ||
413 | { | ||
414 | LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; | ||
415 | } | ||
416 | } | ||
417 | else if(message_name == "sleep_time") | ||
418 | { | ||
419 | mSleepTime = parsed.getValueReal("time"); | ||
420 | } | ||
421 | else if(message_name == "crash") | ||
422 | { | ||
423 | // Crash the plugin | ||
424 | LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; | ||
425 | } | ||
426 | else if(message_name == "hang") | ||
427 | { | ||
428 | // Hang the plugin | ||
429 | LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; | ||
430 | while(1) | ||
431 | { | ||
432 | // wheeeeeeeee...... | ||
433 | } | ||
434 | } | ||
435 | else | ||
436 | { | ||
437 | LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; | ||
438 | } | ||
439 | } | ||
440 | } | ||
441 | |||
442 | if(passMessage && mInstance != NULL) | ||
443 | { | ||
444 | LLTimer elapsed; | ||
445 | |||
446 | mInstance->sendMessage(message); | ||
447 | |||
448 | mCPUElapsed += elapsed.getElapsedTimeF64(); | ||
449 | } | ||
450 | } | ||
451 | |||
452 | /* virtual */ | ||
453 | void LLPluginProcessChild::receivePluginMessage(const std::string &message) | ||
454 | { | ||
455 | LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; | ||
456 | |||
457 | if(mBlockingRequest) | ||
458 | { | ||
459 | // | ||
460 | LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; | ||
461 | } | ||
462 | |||
463 | // Incoming message from the plugin instance | ||
464 | bool passMessage = true; | ||
465 | |||
466 | // FIXME: how should we handle queueing here? | ||
467 | |||
468 | // Intercept certain base messages (responses to ones sent by this class) | ||
469 | { | ||
470 | // Decode this message | ||
471 | LLPluginMessage parsed; | ||
472 | parsed.parse(message); | ||
473 | |||
474 | if(parsed.hasValue("blocking_request")) | ||
475 | { | ||
476 | mBlockingRequest = true; | ||
477 | } | ||
478 | |||
479 | std::string message_class = parsed.getClass(); | ||
480 | if(message_class == "base") | ||
481 | { | ||
482 | std::string message_name = parsed.getName(); | ||
483 | if(message_name == "init_response") | ||
484 | { | ||
485 | // The plugin has finished initializing. | ||
486 | setState(STATE_RUNNING); | ||
487 | |||
488 | // Don't pass this message up to the parent | ||
489 | passMessage = false; | ||
490 | |||
491 | LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); | ||
492 | LLSD versions = parsed.getValueLLSD("versions"); | ||
493 | new_message.setValueLLSD("versions", versions); | ||
494 | |||
495 | if(parsed.hasValue("plugin_version")) | ||
496 | { | ||
497 | std::string plugin_version = parsed.getValue("plugin_version"); | ||
498 | new_message.setValueLLSD("plugin_version", plugin_version); | ||
499 | } | ||
500 | |||
501 | // Let the parent know it's loaded and initialized. | ||
502 | sendMessageToParent(new_message); | ||
503 | } | ||
504 | else if(message_name == "shm_remove_response") | ||
505 | { | ||
506 | // Don't pass this message up to the parent | ||
507 | passMessage = false; | ||
508 | |||
509 | std::string name = parsed.getValue("name"); | ||
510 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
511 | if(iter != mSharedMemoryRegions.end()) | ||
512 | { | ||
513 | // detach the shared memory region | ||
514 | iter->second->detach(); | ||
515 | |||
516 | // and remove it from our map | ||
517 | mSharedMemoryRegions.erase(iter); | ||
518 | |||
519 | // Finally, send the response to the parent. | ||
520 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); | ||
521 | message.setValue("name", name); | ||
522 | sendMessageToParent(message); | ||
523 | } | ||
524 | else | ||
525 | { | ||
526 | LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; | ||
527 | } | ||
528 | } | ||
529 | } | ||
530 | } | ||
531 | |||
532 | if(passMessage) | ||
533 | { | ||
534 | LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; | ||
535 | writeMessageRaw(message); | ||
536 | } | ||
537 | |||
538 | while(mBlockingRequest) | ||
539 | { | ||
540 | // The plugin wants to block and wait for a response to this message. | ||
541 | sleep(mSleepTime); // this will pump the message pipe and process messages | ||
542 | |||
543 | if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) | ||
544 | { | ||
545 | // Response has been received, or we've hit an error state. Stop waiting. | ||
546 | mBlockingRequest = false; | ||
547 | mBlockingResponseReceived = false; | ||
548 | } | ||
549 | } | ||
550 | } | ||
551 | |||
552 | |||
553 | void LLPluginProcessChild::setState(EState state) | ||
554 | { | ||
555 | LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; | ||
556 | mState = state; | ||
557 | }; | ||
558 | |||
559 | void LLPluginProcessChild::deliverQueuedMessages() | ||
560 | { | ||
561 | if(!mBlockingRequest) | ||
562 | { | ||
563 | while(!mMessageQueue.empty()) | ||
564 | { | ||
565 | receiveMessageRaw(mMessageQueue.front()); | ||
566 | mMessageQueue.pop(); | ||
567 | } | ||
568 | } | ||
569 | } | ||