From 6c7a97286113b1d3335d585d0d7cbc047baae021 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 15 Nov 2016 15:53:24 -0500 Subject: DRTVWR-418: Fold windows64 into windows platform with new autobuild. autobuild 1.1 now supports expanding $variables within a config file -- support that was explicitly added to address this very problem. So now the windows platform in autobuild.xml uses $AUTOBUILD_ADDRSIZE, $AUTOBUILD_WIN_VSPLATFORM and $AUTOBUILD_WIN_CMAKE_GEN, which should handle most of the deltas between the windows platform and windows64. This permits removing the windows64 platform definition from autobuild.xml. The one remaining delta between the windows64 and windows platform definitions was -DLL_64BIT_BUILD=TRUE. But we can handle that instead by checking ADDRESS_SIZE. Change all existing references to WORD_SIZE to ADDRESS_SIZE instead, and set ADDRESS_SIZE to $AUTOBUILD_ADDRSIZE. Change the one existing LL_64BIT_BUILD reference to test (ADDRESS_SIZE EQUAL 64) instead. --- indra/llplugin/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 84667f1b82..22f3b24dc5 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -56,13 +56,13 @@ set(llplugin_HEADER_FILES set_source_files_properties(${llplugin_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) -if(NOT WORD_SIZE EQUAL 32) +if(NOT ADDRESS_SIZE EQUAL 32) if(WINDOWS) add_definitions(/FIXED:NO) else(WINDOWS) # not windows therefore gcc LINUX and DARWIN add_definitions(-fPIC) endif(WINDOWS) -endif(NOT WORD_SIZE EQUAL 32) +endif(NOT ADDRESS_SIZE EQUAL 32) list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) -- cgit v1.2.3 From 8a461c00f4eb64027e4d81c081d6b7aa6d680d6e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Dec 2016 08:50:10 -0500 Subject: DRTVWR-418: Until we figure out how to say FIXED:NO to linker, don't. The present CMake logic wants to pass FIXED:NO to the linker for 64-bit builds, which on the face of it seems like a Good Thing: it permits code to be relocated in memory, preventing collisions if two libraries happen to want to load into overlapping address ranges. However the way it's being specified is wrong and harmful. Passing /FIXED:NO to the compiler command line engages /FI (Forced Include!) of a nonexistent file XED:NO -- producing lots of baffling fatal compile errors. Thanks Callum for diagnosing this! --- indra/llplugin/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 22f3b24dc5..2cdea67723 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -58,7 +58,7 @@ set_source_files_properties(${llplugin_HEADER_FILES} if(NOT ADDRESS_SIZE EQUAL 32) if(WINDOWS) - add_definitions(/FIXED:NO) + ##add_definitions(/FIXED:NO) else(WINDOWS) # not windows therefore gcc LINUX and DARWIN add_definitions(-fPIC) endif(WINDOWS) -- cgit v1.2.3 From 45f67f4ed7922e87ae5dd471bd82506f1e2300da Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 19 Apr 2017 13:22:32 -0700 Subject: Hopeful fix for MAINT-7220 Windows Error Message 'SLPlugin.exe has stopped working ' appears. --- indra/llplugin/llpluginmessagepipe.cpp | 2 +- indra/llplugin/llpluginprocesschild.cpp | 1171 ++++++++++++++++--------------- 2 files changed, 590 insertions(+), 583 deletions(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp index 7e2bf90ad1..b7497ad781 100644 --- a/indra/llplugin/llpluginmessagepipe.cpp +++ b/indra/llplugin/llpluginmessagepipe.cpp @@ -215,7 +215,7 @@ bool LLPluginMessagePipe::pumpOutput() else if(APR_STATUS_IS_EOF(status)) { // This is what we normally expect when a plugin exits. - LL_INFOS() << "Got EOF from plugin socket. " << LL_ENDL; + //LL_INFOS() << "Got EOF from plugin socket. " << LL_ENDL; if(mOwner) { diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp index be80d38305..3804ffadbb 100644 --- a/indra/llplugin/llpluginprocesschild.cpp +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -1,582 +1,589 @@ -/** - * @file llpluginprocesschild.cpp - * @brief LLPluginProcessChild handles the child side of the external-process plugin API. - * - * @cond - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * @endcond - */ - -#include "linden_common.h" - -#include "llpluginprocesschild.h" -#include "llplugininstance.h" -#include "llpluginmessagepipe.h" -#include "llpluginmessageclasses.h" - -static const F32 GOODBYE_SECONDS = 20.0f; -static const F32 HEARTBEAT_SECONDS = 1.0f; -static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. - -LLPluginProcessChild::LLPluginProcessChild() -{ - mState = STATE_UNINITIALIZED; - mInstance = NULL; - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz - mCPUElapsed = 0.0f; - mBlockingRequest = false; - mBlockingResponseReceived = false; -} - -LLPluginProcessChild::~LLPluginProcessChild() -{ - if(mInstance != NULL) - { - sendMessageToPlugin(LLPluginMessage("base", "cleanup")); - - // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted - // appears to fail and lock up which means that a given instance of the slplugin process never exits. - // This is bad, especially when users try to update their version of SL - it fails because the slplugin - // process as well as a bunch of plugin specific files are locked and cannot be overwritten. - exit( 0 ); - //delete mInstance; - //mInstance = NULL; - } -} - -void LLPluginProcessChild::killSockets(void) -{ - killMessagePipe(); - mSocket.reset(); -} - -void LLPluginProcessChild::init(U32 launcher_port) -{ - mLauncherHost = LLHost("127.0.0.1", launcher_port); - setState(STATE_INITIALIZED); -} - -void LLPluginProcessChild::idle(void) -{ - bool idle_again; - do - { - if(APR_STATUS_IS_EOF(mSocketError)) - { - // Plugin socket was closed. This covers both normal plugin termination and host crashes. - setState(STATE_ERROR); - } - else if(mSocketError != APR_SUCCESS) - { - LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL; - setState(STATE_ERROR); - } - - if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) - { - // The pipe has been closed -- we're done. - // TODO: This could be slightly more subtle, but I'm not sure it needs to be. - LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL; - setState(STATE_ERROR); - } - - // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). - // 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. - // When in doubt, don't do it. - idle_again = false; - - if(mInstance != NULL) - { - // Provide some time to the plugin - mInstance->idle(); - } - - switch(mState) - { - case STATE_UNINITIALIZED: - break; - - case STATE_INITIALIZED: - if(mSocket->blockingConnect(mLauncherHost)) - { - // This automatically sets mMessagePipe - new LLPluginMessagePipe(this, mSocket); - - setState(STATE_CONNECTED); - } - else - { - // connect failed - setState(STATE_ERROR); - } - break; - - case STATE_CONNECTED: - sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); - setState(STATE_PLUGIN_LOADING); - break; - - case STATE_PLUGIN_LOADING: - if(!mPluginFile.empty()) - { - mInstance = new LLPluginInstance(this); - if(mInstance->load(mPluginDir, mPluginFile) == 0) - { - mHeartbeat.start(); - mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); - mCPUElapsed = 0.0f; - setState(STATE_PLUGIN_LOADED); - } - else - { - setState(STATE_ERROR); - } - } - break; - - case STATE_PLUGIN_LOADED: - { - setState(STATE_PLUGIN_INITIALIZING); - LLPluginMessage message("base", "init"); - sendMessageToPlugin(message); - } - break; - - case STATE_PLUGIN_INITIALIZING: - // waiting for init_response... - break; - - case STATE_RUNNING: - if(mInstance != NULL) - { - // Provide some time to the plugin - LLPluginMessage message("base", "idle"); - message.setValueReal("time", PLUGIN_IDLE_SECONDS); - sendMessageToPlugin(message); - - mInstance->idle(); - - if(mHeartbeat.hasExpired()) - { - - // This just proves that we're not stuck down inside the plugin code. - LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); - - // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. - // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. - // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. - heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); - - sendMessageToParent(heartbeat); - - mHeartbeat.reset(); - mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); - mCPUElapsed = 0.0f; - } - } - // receivePluginMessage will transition to STATE_UNLOADING - break; - - case STATE_SHUTDOWNREQ: - if (mInstance != NULL) - { - sendMessageToPlugin(LLPluginMessage("base", "cleanup")); - delete mInstance; - mInstance = NULL; - } - setState(STATE_UNLOADING); - mWaitGoodbye.setTimerExpirySec(GOODBYE_SECONDS); - break; - - case STATE_UNLOADING: - // waiting for goodbye from plugin. - if (mWaitGoodbye.hasExpired()) - { - LL_WARNS() << "Wait for goodbye expired. Advancing to UNLOADED" << LL_ENDL; - setState(STATE_UNLOADED); - } - break; - - case STATE_UNLOADED: - killSockets(); - setState(STATE_DONE); - break; - - case STATE_ERROR: - // Close the socket to the launcher - killSockets(); - // TODO: Where do we go from here? Just exit()? - setState(STATE_DONE); - break; - - case STATE_DONE: - // just sit here. - break; - } - - } while (idle_again); -} - -void LLPluginProcessChild::sleep(F64 seconds) -{ - deliverQueuedMessages(); - if(mMessagePipe) - { - mMessagePipe->pump(seconds); - } - else - { - ms_sleep((int)(seconds * 1000.0f)); - } -} - -void LLPluginProcessChild::pump(void) -{ - deliverQueuedMessages(); - if(mMessagePipe) - { - mMessagePipe->pump(0.0f); - } - else - { - // Should we warn here? - } -} - - -bool LLPluginProcessChild::isRunning(void) -{ - bool result = false; - - if(mState == STATE_RUNNING) - result = true; - - return result; -} - -bool LLPluginProcessChild::isDone(void) -{ - bool result = false; - - switch(mState) - { - case STATE_DONE: - result = true; - break; - default: - break; - } - - return result; -} - -void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) -{ - if (mInstance) - { - std::string buffer = message.generate(); - - LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; - LLTimer elapsed; - - mInstance->sendMessage(buffer); - - mCPUElapsed += elapsed.getElapsedTimeF64(); - } - else - { - LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL; - } -} - -void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) -{ - std::string buffer = message.generate(); - - LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; - - writeMessageRaw(buffer); -} - -void LLPluginProcessChild::receiveMessageRaw(const std::string &message) -{ - // Incoming message from the TCP Socket - - LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; - - // Decode this message - LLPluginMessage parsed; - parsed.parse(message); - - if(mBlockingRequest) - { - // We're blocking the plugin waiting for a response. - - if(parsed.hasValue("blocking_response")) - { - // This is the message we've been waiting for -- fall through and send it immediately. - mBlockingResponseReceived = true; - } - else - { - // Still waiting. Queue this message and don't process it yet. - mMessageQueue.push(message); - return; - } - } - - bool passMessage = true; - - // FIXME: how should we handle queueing here? - - { - std::string message_class = parsed.getClass(); - if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) - { - passMessage = false; - - std::string message_name = parsed.getName(); - if(message_name == "load_plugin") - { - mPluginFile = parsed.getValue("file"); - mPluginDir = parsed.getValue("dir"); - } - else if (message_name == "shutdown_plugin") - { - setState(STATE_SHUTDOWNREQ); - } - else if(message_name == "shm_add") - { - std::string name = parsed.getValue("name"); - size_t size = (size_t)parsed.getValueS32("size"); - - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if(iter != mSharedMemoryRegions.end()) - { - // Need to remove the old region first - LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; - } - else - { - // This is a new region - LLPluginSharedMemory *region = new LLPluginSharedMemory; - if(region->attach(name, size)) - { - mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); - - std::stringstream addr; - addr << region->getMappedAddress(); - - // Send the add notification to the plugin - LLPluginMessage message("base", "shm_added"); - message.setValue("name", name); - message.setValueS32("size", (S32)size); - message.setValuePointer("address", region->getMappedAddress()); - sendMessageToPlugin(message); - - // and send the response to the parent - message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); - message.setValue("name", name); - sendMessageToParent(message); - } - else - { - LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; - delete region; - } - } - - } - else if(message_name == "shm_remove") - { - std::string name = parsed.getValue("name"); - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if(iter != mSharedMemoryRegions.end()) - { - // forward the remove request to the plugin -- its response will trigger us to detach the segment. - LLPluginMessage message("base", "shm_remove"); - message.setValue("name", name); - sendMessageToPlugin(message); - } - else - { - LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; - } - } - else if(message_name == "sleep_time") - { - mSleepTime = llmax(parsed.getValueReal("time"), 1.0 / 100.0); // clamp to maximum of 100Hz - } - else if(message_name == "crash") - { - // Crash the plugin - LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; - } - else if(message_name == "hang") - { - // Hang the plugin - LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; - while(1) - { - // wheeeeeeeee...... - } - } - else - { - LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; - } - } - } - - if(passMessage && mInstance != NULL) - { - LLTimer elapsed; - - mInstance->sendMessage(message); - - mCPUElapsed += elapsed.getElapsedTimeF64(); - } -} - -/* virtual */ -void LLPluginProcessChild::receivePluginMessage(const std::string &message) -{ - LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; - - if(mBlockingRequest) - { - // - LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; - } - - // Incoming message from the plugin instance - bool passMessage = true; - - // FIXME: how should we handle queueing here? - - // Intercept certain base messages (responses to ones sent by this class) - { - // Decode this message - LLPluginMessage parsed; - parsed.parse(message); - - if(parsed.hasValue("blocking_request")) - { - mBlockingRequest = true; - } - - std::string message_class = parsed.getClass(); - if(message_class == "base") - { - std::string message_name = parsed.getName(); - if(message_name == "init_response") - { - // The plugin has finished initializing. - setState(STATE_RUNNING); - - // Don't pass this message up to the parent - passMessage = false; - - LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); - LLSD versions = parsed.getValueLLSD("versions"); - new_message.setValueLLSD("versions", versions); - - if(parsed.hasValue("plugin_version")) - { - std::string plugin_version = parsed.getValue("plugin_version"); - new_message.setValueLLSD("plugin_version", plugin_version); - } - - // Let the parent know it's loaded and initialized. - sendMessageToParent(new_message); - } - else if (message_name == "goodbye") - { - setState(STATE_UNLOADED); - } - else if(message_name == "shm_remove_response") - { - // Don't pass this message up to the parent - passMessage = false; - - std::string name = parsed.getValue("name"); - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if(iter != mSharedMemoryRegions.end()) - { - // detach the shared memory region - iter->second->detach(); - - // and remove it from our map - mSharedMemoryRegions.erase(iter); - - // Finally, send the response to the parent. - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); - message.setValue("name", name); - sendMessageToParent(message); - } - else - { - LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; - } - } - } - } - - if(passMessage) - { - LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; - writeMessageRaw(message); - } - - while(mBlockingRequest) - { - // The plugin wants to block and wait for a response to this message. - sleep(mSleepTime); // this will pump the message pipe and process messages - - if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) - { - // Response has been received, or we've hit an error state. Stop waiting. - mBlockingRequest = false; - mBlockingResponseReceived = false; - } - } -} - - -void LLPluginProcessChild::setState(EState state) -{ - LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; - mState = state; -}; - -void LLPluginProcessChild::deliverQueuedMessages() -{ - if(!mBlockingRequest) - { - while(!mMessageQueue.empty()) - { - receiveMessageRaw(mMessageQueue.front()); - mMessageQueue.pop(); - } - } -} +/** +* @file llpluginprocesschild.cpp +* @brief LLPluginProcessChild handles the child side of the external-process plugin API. +* +* @cond +* $LicenseInfo:firstyear=2008&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +* @endcond +*/ + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llplugininstance.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +static const F32 GOODBYE_SECONDS = 20.0f; +static const F32 HEARTBEAT_SECONDS = 1.0f; +static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. + +LLPluginProcessChild::LLPluginProcessChild() +{ + mState = STATE_UNINITIALIZED; + mInstance = NULL; + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz + mCPUElapsed = 0.0f; + mBlockingRequest = false; + mBlockingResponseReceived = false; +} + +LLPluginProcessChild::~LLPluginProcessChild() +{ + if (mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + + // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted + // appears to fail and lock up which means that a given instance of the slplugin process never exits. + // This is bad, especially when users try to update their version of SL - it fails because the slplugin + // process as well as a bunch of plugin specific files are locked and cannot be overwritten. + exit(0); + //delete mInstance; + //mInstance = NULL; + } +} + +void LLPluginProcessChild::killSockets(void) +{ + killMessagePipe(); + mSocket.reset(); +} + +void LLPluginProcessChild::init(U32 launcher_port) +{ + mLauncherHost = LLHost("127.0.0.1", launcher_port); + setState(STATE_INITIALIZED); +} + +void LLPluginProcessChild::idle(void) +{ + bool idle_again; + do + { + if (mState < STATE_SHUTDOWNREQ) + { // Once we have hit the shutdown request state checking for errors might put us in a spurious + // error state... don't do that. + + if (APR_STATUS_IS_EOF(mSocketError)) + { + // Plugin socket was closed. This covers both normal plugin termination and host crashes. + setState(STATE_ERROR); + } + else if (mSocketError != APR_SUCCESS) + { + LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR" << LL_ENDL; + setState(STATE_ERROR); + } + + if ((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) + { + // The pipe has been closed -- we're done. + // TODO: This could be slightly more subtle, but I'm not sure it needs to be. + LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR" << LL_ENDL; + setState(STATE_ERROR); + } + } + + // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). + // 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. + // When in doubt, don't do it. + idle_again = false; + + if (mInstance != NULL) + { + // Provide some time to the plugin + mInstance->idle(); + } + + switch (mState) + { + case STATE_UNINITIALIZED: + break; + + case STATE_INITIALIZED: + if (mSocket->blockingConnect(mLauncherHost)) + { + // This automatically sets mMessagePipe + new LLPluginMessagePipe(this, mSocket); + + setState(STATE_CONNECTED); + } + else + { + // connect failed + setState(STATE_ERROR); + } + break; + + case STATE_CONNECTED: + sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); + setState(STATE_PLUGIN_LOADING); + break; + + case STATE_PLUGIN_LOADING: + if (!mPluginFile.empty()) + { + mInstance = new LLPluginInstance(this); + if (mInstance->load(mPluginDir, mPluginFile) == 0) + { + mHeartbeat.start(); + mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); + mCPUElapsed = 0.0f; + setState(STATE_PLUGIN_LOADED); + } + else + { + setState(STATE_ERROR); + } + } + break; + + case STATE_PLUGIN_LOADED: + { + setState(STATE_PLUGIN_INITIALIZING); + LLPluginMessage message("base", "init"); + sendMessageToPlugin(message); + } + break; + + case STATE_PLUGIN_INITIALIZING: + // waiting for init_response... + break; + + case STATE_RUNNING: + if (mInstance != NULL) + { + // Provide some time to the plugin + LLPluginMessage message("base", "idle"); + message.setValueReal("time", PLUGIN_IDLE_SECONDS); + sendMessageToPlugin(message); + + mInstance->idle(); + + if (mHeartbeat.hasExpired()) + { + + // This just proves that we're not stuck down inside the plugin code. + LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); + + // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. + // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. + // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. + heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); + + sendMessageToParent(heartbeat); + + mHeartbeat.reset(); + mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); + mCPUElapsed = 0.0f; + } + } + // receivePluginMessage will transition to STATE_UNLOADING + break; + + case STATE_SHUTDOWNREQ: + // set next state first thing in case "cleanup" message advances state. + setState(STATE_UNLOADING); + mWaitGoodbye.setTimerExpirySec(GOODBYE_SECONDS); + + if (mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + } + break; + + case STATE_UNLOADING: + // waiting for goodbye from plugin. + if (mWaitGoodbye.hasExpired()) + { + LL_WARNS() << "Wait for goodbye expired. Advancing to UNLOADED" << LL_ENDL; + setState(STATE_UNLOADED); + } + break; + + case STATE_UNLOADED: + killSockets(); + delete mInstance; + mInstance = NULL; + setState(STATE_DONE); + break; + + case STATE_ERROR: + // Close the socket to the launcher + killSockets(); + // TODO: Where do we go from here? Just exit()? + setState(STATE_DONE); + break; + + case STATE_DONE: + // just sit here. + break; + } + + } while (idle_again); +} + +void LLPluginProcessChild::sleep(F64 seconds) +{ + deliverQueuedMessages(); + if (mMessagePipe) + { + mMessagePipe->pump(seconds); + } + else + { + ms_sleep((int)(seconds * 1000.0f)); + } +} + +void LLPluginProcessChild::pump(void) +{ + deliverQueuedMessages(); + if (mMessagePipe) + { + mMessagePipe->pump(0.0f); + } + else + { + // Should we warn here? + } +} + + +bool LLPluginProcessChild::isRunning(void) +{ + bool result = false; + + if (mState == STATE_RUNNING) + result = true; + + return result; +} + +bool LLPluginProcessChild::isDone(void) +{ + bool result = false; + + switch (mState) + { + case STATE_DONE: + result = true; + break; + default: + break; + } + + return result; +} + +void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) +{ + if (mInstance) + { + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; + LLTimer elapsed; + + mInstance->sendMessage(buffer); + + mCPUElapsed += elapsed.getElapsedTimeF64(); + } + else + { + LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL; + } +} + +void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) +{ + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; + + writeMessageRaw(buffer); +} + +void LLPluginProcessChild::receiveMessageRaw(const std::string &message) +{ + // Incoming message from the TCP Socket + + LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; + + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + if (mBlockingRequest) + { + // We're blocking the plugin waiting for a response. + + if (parsed.hasValue("blocking_response")) + { + // This is the message we've been waiting for -- fall through and send it immediately. + mBlockingResponseReceived = true; + } + else + { + // Still waiting. Queue this message and don't process it yet. + mMessageQueue.push(message); + return; + } + } + + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + { + std::string message_class = parsed.getClass(); + if (message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) + { + passMessage = false; + + std::string message_name = parsed.getName(); + if (message_name == "load_plugin") + { + mPluginFile = parsed.getValue("file"); + mPluginDir = parsed.getValue("dir"); + } + else if (message_name == "shutdown_plugin") + { + setState(STATE_SHUTDOWNREQ); + } + else if (message_name == "shm_add") + { + std::string name = parsed.getValue("name"); + size_t size = (size_t)parsed.getValueS32("size"); + + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // Need to remove the old region first + LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; + } + else + { + // This is a new region + LLPluginSharedMemory *region = new LLPluginSharedMemory; + if (region->attach(name, size)) + { + mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); + + std::stringstream addr; + addr << region->getMappedAddress(); + + // Send the add notification to the plugin + LLPluginMessage message("base", "shm_added"); + message.setValue("name", name); + message.setValueS32("size", (S32)size); + message.setValuePointer("address", region->getMappedAddress()); + sendMessageToPlugin(message); + + // and send the response to the parent + message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; + delete region; + } + } + + } + else if (message_name == "shm_remove") + { + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // forward the remove request to the plugin -- its response will trigger us to detach the segment. + LLPluginMessage message("base", "shm_remove"); + message.setValue("name", name); + sendMessageToPlugin(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; + } + } + else if (message_name == "sleep_time") + { + mSleepTime = llmax(parsed.getValueReal("time"), 1.0 / 100.0); // clamp to maximum of 100Hz + } + else if (message_name == "crash") + { + // Crash the plugin + LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; + } + else if (message_name == "hang") + { + // Hang the plugin + LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; + while (1) + { + // wheeeeeeeee...... + } + } + else + { + LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; + } + } + } + + if (passMessage && mInstance != NULL) + { + LLTimer elapsed; + + mInstance->sendMessage(message); + + mCPUElapsed += elapsed.getElapsedTimeF64(); + } +} + +/* virtual */ +void LLPluginProcessChild::receivePluginMessage(const std::string &message) +{ + LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; + + if (mBlockingRequest) + { + // + LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; + } + + // Incoming message from the plugin instance + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + // Intercept certain base messages (responses to ones sent by this class) + { + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + if (parsed.hasValue("blocking_request")) + { + mBlockingRequest = true; + } + + std::string message_class = parsed.getClass(); + if (message_class == "base") + { + std::string message_name = parsed.getName(); + if (message_name == "init_response") + { + // The plugin has finished initializing. + setState(STATE_RUNNING); + + // Don't pass this message up to the parent + passMessage = false; + + LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); + LLSD versions = parsed.getValueLLSD("versions"); + new_message.setValueLLSD("versions", versions); + + if (parsed.hasValue("plugin_version")) + { + std::string plugin_version = parsed.getValue("plugin_version"); + new_message.setValueLLSD("plugin_version", plugin_version); + } + + // Let the parent know it's loaded and initialized. + sendMessageToParent(new_message); + } + else if (message_name == "goodbye") + { + setState(STATE_UNLOADED); + } + else if (message_name == "shm_remove_response") + { + // Don't pass this message up to the parent + passMessage = false; + + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // detach the shared memory region + iter->second->detach(); + + // and remove it from our map + mSharedMemoryRegions.erase(iter); + + // Finally, send the response to the parent. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; + } + } + } + } + + if (passMessage) + { + LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; + writeMessageRaw(message); + } + + while (mBlockingRequest) + { + // The plugin wants to block and wait for a response to this message. + sleep(mSleepTime); // this will pump the message pipe and process messages + + if (mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) + { + // Response has been received, or we've hit an error state. Stop waiting. + mBlockingRequest = false; + mBlockingResponseReceived = false; + } + } +} + + +void LLPluginProcessChild::setState(EState state) +{ + LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; + mState = state; +}; + +void LLPluginProcessChild::deliverQueuedMessages() +{ + if (!mBlockingRequest) + { + while (!mMessageQueue.empty()) + { + receiveMessageRaw(mMessageQueue.front()); + mMessageQueue.pop(); + } + } +} -- cgit v1.2.3 From 95c7feba072128233884d6453f1e46b475df7f02 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 19 Apr 2017 15:54:26 -0700 Subject: Remove the scary 32bit exception handler that patches kernel32.dll since it was (a) scary, (b) didn't work on 64 bit and (c) likely the cause of a lot of anti-virus false positives --- indra/llplugin/slplugin/slplugin.cpp | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/slplugin/slplugin.cpp b/indra/llplugin/slplugin/slplugin.cpp index 684bcf1207..b960565416 100644 --- a/indra/llplugin/slplugin/slplugin.cpp +++ b/indra/llplugin/slplugin/slplugin.cpp @@ -100,33 +100,8 @@ LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter( BOOL PreventSetUnhandledExceptionFilter() { -// WARNING: This won't work on 64-bit Windows systems so we turn it off it. -// It should work for any flavor of 32-bit Windows we care about. -// If it's off, sometimes you will see an OS message when a plugin crashes -#ifndef _WIN64 - HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" ); - if ( NULL == hKernel32 ) - return FALSE; - - void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" ); - if( NULL == pOrgEntry ) - return FALSE; - - unsigned char newJump[ 100 ]; - DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; - dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far - void *pNewFunc = &MyDummySetUnhandledExceptionFilter; - DWORD dwNewEntryAddr = (DWORD) pNewFunc; - DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; - - newJump[ 0 ] = 0xE9; // JMP absolute - memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) ); - SIZE_T bytesWritten; - BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten ); - return bRet; -#else - return FALSE; -#endif + // remove the scary stuff that also isn't supported on 64 bit Windows + return TRUE; } //////////////////////////////////////////////////////////////////////////////// -- cgit v1.2.3 From 28824e68fffb26da21aa68bf711ee57de686d88b Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Wed, 19 Apr 2017 15:55:26 -0700 Subject: Turn off message that is expected behavior and will fill up the logs/console --- indra/llplugin/llpluginmessagepipe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp index b7497ad781..9468696507 100644 --- a/indra/llplugin/llpluginmessagepipe.cpp +++ b/indra/llplugin/llpluginmessagepipe.cpp @@ -329,7 +329,7 @@ bool LLPluginMessagePipe::pumpInput(F64 timeout) else if(APR_STATUS_IS_EOF(status)) { // This is what we normally expect when a plugin exits. - LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL; + //LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL; if(mOwner) { -- cgit v1.2.3 From 362ffb213108ba7c5e582aa9d72a44a82f5455a3 Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 21 Apr 2017 13:37:24 -0700 Subject: Fix windows line endings because it's 2017 and our tools can't deal with it --- indra/llplugin/llpluginprocesschild.cpp | 1178 +++++++++++++++---------------- 1 file changed, 589 insertions(+), 589 deletions(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp index 3804ffadbb..e24d222cb6 100644 --- a/indra/llplugin/llpluginprocesschild.cpp +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -1,589 +1,589 @@ -/** -* @file llpluginprocesschild.cpp -* @brief LLPluginProcessChild handles the child side of the external-process plugin API. -* -* @cond -* $LicenseInfo:firstyear=2008&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -* @endcond -*/ - -#include "linden_common.h" - -#include "llpluginprocesschild.h" -#include "llplugininstance.h" -#include "llpluginmessagepipe.h" -#include "llpluginmessageclasses.h" - -static const F32 GOODBYE_SECONDS = 20.0f; -static const F32 HEARTBEAT_SECONDS = 1.0f; -static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. - -LLPluginProcessChild::LLPluginProcessChild() -{ - mState = STATE_UNINITIALIZED; - mInstance = NULL; - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz - mCPUElapsed = 0.0f; - mBlockingRequest = false; - mBlockingResponseReceived = false; -} - -LLPluginProcessChild::~LLPluginProcessChild() -{ - if (mInstance != NULL) - { - sendMessageToPlugin(LLPluginMessage("base", "cleanup")); - - // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted - // appears to fail and lock up which means that a given instance of the slplugin process never exits. - // This is bad, especially when users try to update their version of SL - it fails because the slplugin - // process as well as a bunch of plugin specific files are locked and cannot be overwritten. - exit(0); - //delete mInstance; - //mInstance = NULL; - } -} - -void LLPluginProcessChild::killSockets(void) -{ - killMessagePipe(); - mSocket.reset(); -} - -void LLPluginProcessChild::init(U32 launcher_port) -{ - mLauncherHost = LLHost("127.0.0.1", launcher_port); - setState(STATE_INITIALIZED); -} - -void LLPluginProcessChild::idle(void) -{ - bool idle_again; - do - { - if (mState < STATE_SHUTDOWNREQ) - { // Once we have hit the shutdown request state checking for errors might put us in a spurious - // error state... don't do that. - - if (APR_STATUS_IS_EOF(mSocketError)) - { - // Plugin socket was closed. This covers both normal plugin termination and host crashes. - setState(STATE_ERROR); - } - else if (mSocketError != APR_SUCCESS) - { - LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR" << LL_ENDL; - setState(STATE_ERROR); - } - - if ((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) - { - // The pipe has been closed -- we're done. - // TODO: This could be slightly more subtle, but I'm not sure it needs to be. - LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR" << LL_ENDL; - setState(STATE_ERROR); - } - } - - // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). - // 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. - // When in doubt, don't do it. - idle_again = false; - - if (mInstance != NULL) - { - // Provide some time to the plugin - mInstance->idle(); - } - - switch (mState) - { - case STATE_UNINITIALIZED: - break; - - case STATE_INITIALIZED: - if (mSocket->blockingConnect(mLauncherHost)) - { - // This automatically sets mMessagePipe - new LLPluginMessagePipe(this, mSocket); - - setState(STATE_CONNECTED); - } - else - { - // connect failed - setState(STATE_ERROR); - } - break; - - case STATE_CONNECTED: - sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); - setState(STATE_PLUGIN_LOADING); - break; - - case STATE_PLUGIN_LOADING: - if (!mPluginFile.empty()) - { - mInstance = new LLPluginInstance(this); - if (mInstance->load(mPluginDir, mPluginFile) == 0) - { - mHeartbeat.start(); - mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); - mCPUElapsed = 0.0f; - setState(STATE_PLUGIN_LOADED); - } - else - { - setState(STATE_ERROR); - } - } - break; - - case STATE_PLUGIN_LOADED: - { - setState(STATE_PLUGIN_INITIALIZING); - LLPluginMessage message("base", "init"); - sendMessageToPlugin(message); - } - break; - - case STATE_PLUGIN_INITIALIZING: - // waiting for init_response... - break; - - case STATE_RUNNING: - if (mInstance != NULL) - { - // Provide some time to the plugin - LLPluginMessage message("base", "idle"); - message.setValueReal("time", PLUGIN_IDLE_SECONDS); - sendMessageToPlugin(message); - - mInstance->idle(); - - if (mHeartbeat.hasExpired()) - { - - // This just proves that we're not stuck down inside the plugin code. - LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); - - // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. - // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. - // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. - heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); - - sendMessageToParent(heartbeat); - - mHeartbeat.reset(); - mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); - mCPUElapsed = 0.0f; - } - } - // receivePluginMessage will transition to STATE_UNLOADING - break; - - case STATE_SHUTDOWNREQ: - // set next state first thing in case "cleanup" message advances state. - setState(STATE_UNLOADING); - mWaitGoodbye.setTimerExpirySec(GOODBYE_SECONDS); - - if (mInstance != NULL) - { - sendMessageToPlugin(LLPluginMessage("base", "cleanup")); - } - break; - - case STATE_UNLOADING: - // waiting for goodbye from plugin. - if (mWaitGoodbye.hasExpired()) - { - LL_WARNS() << "Wait for goodbye expired. Advancing to UNLOADED" << LL_ENDL; - setState(STATE_UNLOADED); - } - break; - - case STATE_UNLOADED: - killSockets(); - delete mInstance; - mInstance = NULL; - setState(STATE_DONE); - break; - - case STATE_ERROR: - // Close the socket to the launcher - killSockets(); - // TODO: Where do we go from here? Just exit()? - setState(STATE_DONE); - break; - - case STATE_DONE: - // just sit here. - break; - } - - } while (idle_again); -} - -void LLPluginProcessChild::sleep(F64 seconds) -{ - deliverQueuedMessages(); - if (mMessagePipe) - { - mMessagePipe->pump(seconds); - } - else - { - ms_sleep((int)(seconds * 1000.0f)); - } -} - -void LLPluginProcessChild::pump(void) -{ - deliverQueuedMessages(); - if (mMessagePipe) - { - mMessagePipe->pump(0.0f); - } - else - { - // Should we warn here? - } -} - - -bool LLPluginProcessChild::isRunning(void) -{ - bool result = false; - - if (mState == STATE_RUNNING) - result = true; - - return result; -} - -bool LLPluginProcessChild::isDone(void) -{ - bool result = false; - - switch (mState) - { - case STATE_DONE: - result = true; - break; - default: - break; - } - - return result; -} - -void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) -{ - if (mInstance) - { - std::string buffer = message.generate(); - - LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; - LLTimer elapsed; - - mInstance->sendMessage(buffer); - - mCPUElapsed += elapsed.getElapsedTimeF64(); - } - else - { - LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL; - } -} - -void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) -{ - std::string buffer = message.generate(); - - LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; - - writeMessageRaw(buffer); -} - -void LLPluginProcessChild::receiveMessageRaw(const std::string &message) -{ - // Incoming message from the TCP Socket - - LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; - - // Decode this message - LLPluginMessage parsed; - parsed.parse(message); - - if (mBlockingRequest) - { - // We're blocking the plugin waiting for a response. - - if (parsed.hasValue("blocking_response")) - { - // This is the message we've been waiting for -- fall through and send it immediately. - mBlockingResponseReceived = true; - } - else - { - // Still waiting. Queue this message and don't process it yet. - mMessageQueue.push(message); - return; - } - } - - bool passMessage = true; - - // FIXME: how should we handle queueing here? - - { - std::string message_class = parsed.getClass(); - if (message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) - { - passMessage = false; - - std::string message_name = parsed.getName(); - if (message_name == "load_plugin") - { - mPluginFile = parsed.getValue("file"); - mPluginDir = parsed.getValue("dir"); - } - else if (message_name == "shutdown_plugin") - { - setState(STATE_SHUTDOWNREQ); - } - else if (message_name == "shm_add") - { - std::string name = parsed.getValue("name"); - size_t size = (size_t)parsed.getValueS32("size"); - - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if (iter != mSharedMemoryRegions.end()) - { - // Need to remove the old region first - LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; - } - else - { - // This is a new region - LLPluginSharedMemory *region = new LLPluginSharedMemory; - if (region->attach(name, size)) - { - mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); - - std::stringstream addr; - addr << region->getMappedAddress(); - - // Send the add notification to the plugin - LLPluginMessage message("base", "shm_added"); - message.setValue("name", name); - message.setValueS32("size", (S32)size); - message.setValuePointer("address", region->getMappedAddress()); - sendMessageToPlugin(message); - - // and send the response to the parent - message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); - message.setValue("name", name); - sendMessageToParent(message); - } - else - { - LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; - delete region; - } - } - - } - else if (message_name == "shm_remove") - { - std::string name = parsed.getValue("name"); - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if (iter != mSharedMemoryRegions.end()) - { - // forward the remove request to the plugin -- its response will trigger us to detach the segment. - LLPluginMessage message("base", "shm_remove"); - message.setValue("name", name); - sendMessageToPlugin(message); - } - else - { - LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; - } - } - else if (message_name == "sleep_time") - { - mSleepTime = llmax(parsed.getValueReal("time"), 1.0 / 100.0); // clamp to maximum of 100Hz - } - else if (message_name == "crash") - { - // Crash the plugin - LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; - } - else if (message_name == "hang") - { - // Hang the plugin - LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; - while (1) - { - // wheeeeeeeee...... - } - } - else - { - LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; - } - } - } - - if (passMessage && mInstance != NULL) - { - LLTimer elapsed; - - mInstance->sendMessage(message); - - mCPUElapsed += elapsed.getElapsedTimeF64(); - } -} - -/* virtual */ -void LLPluginProcessChild::receivePluginMessage(const std::string &message) -{ - LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; - - if (mBlockingRequest) - { - // - LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; - } - - // Incoming message from the plugin instance - bool passMessage = true; - - // FIXME: how should we handle queueing here? - - // Intercept certain base messages (responses to ones sent by this class) - { - // Decode this message - LLPluginMessage parsed; - parsed.parse(message); - - if (parsed.hasValue("blocking_request")) - { - mBlockingRequest = true; - } - - std::string message_class = parsed.getClass(); - if (message_class == "base") - { - std::string message_name = parsed.getName(); - if (message_name == "init_response") - { - // The plugin has finished initializing. - setState(STATE_RUNNING); - - // Don't pass this message up to the parent - passMessage = false; - - LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); - LLSD versions = parsed.getValueLLSD("versions"); - new_message.setValueLLSD("versions", versions); - - if (parsed.hasValue("plugin_version")) - { - std::string plugin_version = parsed.getValue("plugin_version"); - new_message.setValueLLSD("plugin_version", plugin_version); - } - - // Let the parent know it's loaded and initialized. - sendMessageToParent(new_message); - } - else if (message_name == "goodbye") - { - setState(STATE_UNLOADED); - } - else if (message_name == "shm_remove_response") - { - // Don't pass this message up to the parent - passMessage = false; - - std::string name = parsed.getValue("name"); - sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); - if (iter != mSharedMemoryRegions.end()) - { - // detach the shared memory region - iter->second->detach(); - - // and remove it from our map - mSharedMemoryRegions.erase(iter); - - // Finally, send the response to the parent. - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); - message.setValue("name", name); - sendMessageToParent(message); - } - else - { - LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; - } - } - } - } - - if (passMessage) - { - LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; - writeMessageRaw(message); - } - - while (mBlockingRequest) - { - // The plugin wants to block and wait for a response to this message. - sleep(mSleepTime); // this will pump the message pipe and process messages - - if (mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) - { - // Response has been received, or we've hit an error state. Stop waiting. - mBlockingRequest = false; - mBlockingResponseReceived = false; - } - } -} - - -void LLPluginProcessChild::setState(EState state) -{ - LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; - mState = state; -}; - -void LLPluginProcessChild::deliverQueuedMessages() -{ - if (!mBlockingRequest) - { - while (!mMessageQueue.empty()) - { - receiveMessageRaw(mMessageQueue.front()); - mMessageQueue.pop(); - } - } -} +/** +* @file llpluginprocesschild.cpp +* @brief LLPluginProcessChild handles the child side of the external-process plugin API. +* +* @cond +* $LicenseInfo:firstyear=2008&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +* @endcond +*/ + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llplugininstance.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +static const F32 GOODBYE_SECONDS = 20.0f; +static const F32 HEARTBEAT_SECONDS = 1.0f; +static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. + +LLPluginProcessChild::LLPluginProcessChild() +{ + mState = STATE_UNINITIALIZED; + mInstance = NULL; + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz + mCPUElapsed = 0.0f; + mBlockingRequest = false; + mBlockingResponseReceived = false; +} + +LLPluginProcessChild::~LLPluginProcessChild() +{ + if (mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + + // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted + // appears to fail and lock up which means that a given instance of the slplugin process never exits. + // This is bad, especially when users try to update their version of SL - it fails because the slplugin + // process as well as a bunch of plugin specific files are locked and cannot be overwritten. + exit(0); + //delete mInstance; + //mInstance = NULL; + } +} + +void LLPluginProcessChild::killSockets(void) +{ + killMessagePipe(); + mSocket.reset(); +} + +void LLPluginProcessChild::init(U32 launcher_port) +{ + mLauncherHost = LLHost("127.0.0.1", launcher_port); + setState(STATE_INITIALIZED); +} + +void LLPluginProcessChild::idle(void) +{ + bool idle_again; + do + { + if (mState < STATE_SHUTDOWNREQ) + { // Once we have hit the shutdown request state checking for errors might put us in a spurious + // error state... don't do that. + + if (APR_STATUS_IS_EOF(mSocketError)) + { + // Plugin socket was closed. This covers both normal plugin termination and host crashes. + setState(STATE_ERROR); + } + else if (mSocketError != APR_SUCCESS) + { + LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR" << LL_ENDL; + setState(STATE_ERROR); + } + + if ((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) + { + // The pipe has been closed -- we're done. + // TODO: This could be slightly more subtle, but I'm not sure it needs to be. + LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR" << LL_ENDL; + setState(STATE_ERROR); + } + } + + // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). + // 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. + // When in doubt, don't do it. + idle_again = false; + + if (mInstance != NULL) + { + // Provide some time to the plugin + mInstance->idle(); + } + + switch (mState) + { + case STATE_UNINITIALIZED: + break; + + case STATE_INITIALIZED: + if (mSocket->blockingConnect(mLauncherHost)) + { + // This automatically sets mMessagePipe + new LLPluginMessagePipe(this, mSocket); + + setState(STATE_CONNECTED); + } + else + { + // connect failed + setState(STATE_ERROR); + } + break; + + case STATE_CONNECTED: + sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); + setState(STATE_PLUGIN_LOADING); + break; + + case STATE_PLUGIN_LOADING: + if (!mPluginFile.empty()) + { + mInstance = new LLPluginInstance(this); + if (mInstance->load(mPluginDir, mPluginFile) == 0) + { + mHeartbeat.start(); + mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); + mCPUElapsed = 0.0f; + setState(STATE_PLUGIN_LOADED); + } + else + { + setState(STATE_ERROR); + } + } + break; + + case STATE_PLUGIN_LOADED: + { + setState(STATE_PLUGIN_INITIALIZING); + LLPluginMessage message("base", "init"); + sendMessageToPlugin(message); + } + break; + + case STATE_PLUGIN_INITIALIZING: + // waiting for init_response... + break; + + case STATE_RUNNING: + if (mInstance != NULL) + { + // Provide some time to the plugin + LLPluginMessage message("base", "idle"); + message.setValueReal("time", PLUGIN_IDLE_SECONDS); + sendMessageToPlugin(message); + + mInstance->idle(); + + if (mHeartbeat.hasExpired()) + { + + // This just proves that we're not stuck down inside the plugin code. + LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); + + // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. + // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. + // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. + heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); + + sendMessageToParent(heartbeat); + + mHeartbeat.reset(); + mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); + mCPUElapsed = 0.0f; + } + } + // receivePluginMessage will transition to STATE_UNLOADING + break; + + case STATE_SHUTDOWNREQ: + // set next state first thing in case "cleanup" message advances state. + setState(STATE_UNLOADING); + mWaitGoodbye.setTimerExpirySec(GOODBYE_SECONDS); + + if (mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + } + break; + + case STATE_UNLOADING: + // waiting for goodbye from plugin. + if (mWaitGoodbye.hasExpired()) + { + LL_WARNS() << "Wait for goodbye expired. Advancing to UNLOADED" << LL_ENDL; + setState(STATE_UNLOADED); + } + break; + + case STATE_UNLOADED: + killSockets(); + delete mInstance; + mInstance = NULL; + setState(STATE_DONE); + break; + + case STATE_ERROR: + // Close the socket to the launcher + killSockets(); + // TODO: Where do we go from here? Just exit()? + setState(STATE_DONE); + break; + + case STATE_DONE: + // just sit here. + break; + } + + } while (idle_again); +} + +void LLPluginProcessChild::sleep(F64 seconds) +{ + deliverQueuedMessages(); + if (mMessagePipe) + { + mMessagePipe->pump(seconds); + } + else + { + ms_sleep((int)(seconds * 1000.0f)); + } +} + +void LLPluginProcessChild::pump(void) +{ + deliverQueuedMessages(); + if (mMessagePipe) + { + mMessagePipe->pump(0.0f); + } + else + { + // Should we warn here? + } +} + + +bool LLPluginProcessChild::isRunning(void) +{ + bool result = false; + + if (mState == STATE_RUNNING) + result = true; + + return result; +} + +bool LLPluginProcessChild::isDone(void) +{ + bool result = false; + + switch (mState) + { + case STATE_DONE: + result = true; + break; + default: + break; + } + + return result; +} + +void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) +{ + if (mInstance) + { + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; + LLTimer elapsed; + + mInstance->sendMessage(buffer); + + mCPUElapsed += elapsed.getElapsedTimeF64(); + } + else + { + LL_WARNS("Plugin") << "mInstance == NULL" << LL_ENDL; + } +} + +void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) +{ + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; + + writeMessageRaw(buffer); +} + +void LLPluginProcessChild::receiveMessageRaw(const std::string &message) +{ + // Incoming message from the TCP Socket + + LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; + + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + if (mBlockingRequest) + { + // We're blocking the plugin waiting for a response. + + if (parsed.hasValue("blocking_response")) + { + // This is the message we've been waiting for -- fall through and send it immediately. + mBlockingResponseReceived = true; + } + else + { + // Still waiting. Queue this message and don't process it yet. + mMessageQueue.push(message); + return; + } + } + + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + { + std::string message_class = parsed.getClass(); + if (message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) + { + passMessage = false; + + std::string message_name = parsed.getName(); + if (message_name == "load_plugin") + { + mPluginFile = parsed.getValue("file"); + mPluginDir = parsed.getValue("dir"); + } + else if (message_name == "shutdown_plugin") + { + setState(STATE_SHUTDOWNREQ); + } + else if (message_name == "shm_add") + { + std::string name = parsed.getValue("name"); + size_t size = (size_t)parsed.getValueS32("size"); + + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // Need to remove the old region first + LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; + } + else + { + // This is a new region + LLPluginSharedMemory *region = new LLPluginSharedMemory; + if (region->attach(name, size)) + { + mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); + + std::stringstream addr; + addr << region->getMappedAddress(); + + // Send the add notification to the plugin + LLPluginMessage message("base", "shm_added"); + message.setValue("name", name); + message.setValueS32("size", (S32)size); + message.setValuePointer("address", region->getMappedAddress()); + sendMessageToPlugin(message); + + // and send the response to the parent + message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; + delete region; + } + } + + } + else if (message_name == "shm_remove") + { + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // forward the remove request to the plugin -- its response will trigger us to detach the segment. + LLPluginMessage message("base", "shm_remove"); + message.setValue("name", name); + sendMessageToPlugin(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; + } + } + else if (message_name == "sleep_time") + { + mSleepTime = llmax(parsed.getValueReal("time"), 1.0 / 100.0); // clamp to maximum of 100Hz + } + else if (message_name == "crash") + { + // Crash the plugin + LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; + } + else if (message_name == "hang") + { + // Hang the plugin + LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; + while (1) + { + // wheeeeeeeee...... + } + } + else + { + LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; + } + } + } + + if (passMessage && mInstance != NULL) + { + LLTimer elapsed; + + mInstance->sendMessage(message); + + mCPUElapsed += elapsed.getElapsedTimeF64(); + } +} + +/* virtual */ +void LLPluginProcessChild::receivePluginMessage(const std::string &message) +{ + LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; + + if (mBlockingRequest) + { + // + LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; + } + + // Incoming message from the plugin instance + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + // Intercept certain base messages (responses to ones sent by this class) + { + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + if (parsed.hasValue("blocking_request")) + { + mBlockingRequest = true; + } + + std::string message_class = parsed.getClass(); + if (message_class == "base") + { + std::string message_name = parsed.getName(); + if (message_name == "init_response") + { + // The plugin has finished initializing. + setState(STATE_RUNNING); + + // Don't pass this message up to the parent + passMessage = false; + + LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); + LLSD versions = parsed.getValueLLSD("versions"); + new_message.setValueLLSD("versions", versions); + + if (parsed.hasValue("plugin_version")) + { + std::string plugin_version = parsed.getValue("plugin_version"); + new_message.setValueLLSD("plugin_version", plugin_version); + } + + // Let the parent know it's loaded and initialized. + sendMessageToParent(new_message); + } + else if (message_name == "goodbye") + { + setState(STATE_UNLOADED); + } + else if (message_name == "shm_remove_response") + { + // Don't pass this message up to the parent + passMessage = false; + + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if (iter != mSharedMemoryRegions.end()) + { + // detach the shared memory region + iter->second->detach(); + + // and remove it from our map + mSharedMemoryRegions.erase(iter); + + // Finally, send the response to the parent. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; + } + } + } + } + + if (passMessage) + { + LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; + writeMessageRaw(message); + } + + while (mBlockingRequest) + { + // The plugin wants to block and wait for a response to this message. + sleep(mSleepTime); // this will pump the message pipe and process messages + + if (mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) + { + // Response has been received, or we've hit an error state. Stop waiting. + mBlockingRequest = false; + mBlockingResponseReceived = false; + } + } +} + + +void LLPluginProcessChild::setState(EState state) +{ + LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; + mState = state; +}; + +void LLPluginProcessChild::deliverQueuedMessages() +{ + if (!mBlockingRequest) + { + while (!mMessageQueue.empty()) + { + receiveMessageRaw(mMessageQueue.front()); + mMessageQueue.pop(); + } + } +} -- cgit v1.2.3 From 19bb6fd33e315e1370583d7a90ec3da10f184a54 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 27 Sep 2017 15:27:30 -0400 Subject: MAINT-7081: Mention nghttp2 library wherever it must be mentioned. --- indra/llplugin/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llplugin') diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 2cdea67723..129efeb529 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -82,7 +82,7 @@ if (LL_TESTS) set_source_files_properties( llplugincookiestore.cpp PROPERTIES - LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES}" + LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES};${NGHTTP2_LIBRARIES}" ) LL_ADD_PROJECT_UNIT_TESTS(llplugin "${llplugin_TEST_SOURCE_FILES}") -- cgit v1.2.3