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