Commits

Monroe Linden committed a5f067e

Add a way for plugins to send a message and block waiting for the response
This requires some cooperation between the plugin and the host, and will only work for specific messages.

The way it works is as follows:
* the plugin sends a message containing the key "blocking_request" (with any value)
* this will cause the "send message" function to block (continuing to pull incoming messages off the network socket and queue them) until it receives a message from the host containing the key "blocking_response"
** NOTE: if the plugin sends a blocking_request that isn't set up to cause the host to send back a blocking_response, it will block forever
* the blocking_response message will be handed to the plugin's "receive message" function _immediately_ (before the "send message" function returns)
** this means that the plugin can extract response data for use by the the code that sent the message (but is still blocked inside the "send message" call)
** NOTE: this BREAKS the invariant stating that the plugin's "receive message" function will never be called recursively, and the plugin MUST be prepared to deal with this
* after the plugin finishes processing the blocking_response message, the "send message" function that was called with the blocking_request message will return to the plugin
* any queued messages will be delivered after the outer invocation of the plugin's "receive message" function returns (as normal)

Inside the viewer, the code can tell when a plugin is in this blocked state by calling LLPluginProcessParent::isBlocked(). LLPluginClassMedia uses this to avoid sending mouse-move and size-change messages to blocked plugins.

  • Participants
  • Parent commits 63b8074

Comments (0)

Files changed (6)

indra/llplugin/llpluginclassmedia.cpp

 		mPlugin->idle();
 	}
 	
-	if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL))
+	if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked()))
 	{
 		// Can't process a size change at this time
 	}
 {
 	if(type == MOUSE_EVENT_MOVE)
 	{
+		if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked())
+		{
+			// Don't queue up mouse move events that can't be delivered.
+		}
+
 		if((x == mLastMouseX) && (y == mLastMouseY))
 		{
 			// Don't spam unnecessary mouse move events.

indra/llplugin/llpluginmessagepipe.cpp

 void LLPluginMessagePipe::processInput(void)
 {
 	// Look for input delimiter(s) in the input buffer.
-	int start = 0;
 	int delim;
-	while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos)
+	while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos)
 	{	
 		// Let the owner process this message
 		if (mOwner)
 		{
-			mOwner->receiveMessageRaw(mInput.substr(start, delim - start));
+			// Pull the message out of the input buffer before calling receiveMessageRaw.
+			// It's now possible for this function to get called recursively (in the case where the plugin makes a blocking request)
+			// and this guarantees that the messages will get dequeued correctly.
+			std::string message(mInput, 0, delim);
+			mInput.erase(0, delim + 1);
+			mOwner->receiveMessageRaw(message);
 		}
 		else
 		{
 			LL_WARNS("Plugin") << "!mOwner" << LL_ENDL;
 		}
-		
-		start = delim + 1;
 	}
-	
-	// Remove delivered messages from the input buffer.
-	if(start != 0)
-		mInput = mInput.substr(start);
-	
 }
 

indra/llplugin/llpluginprocesschild.cpp

 	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()
 
 void LLPluginProcessChild::sleep(F64 seconds)
 {
+	deliverQueuedMessages();
 	if(mMessagePipe)
 	{
 		mMessagePipe->pump(seconds);
 
 void LLPluginProcessChild::pump(void)
 {
+	deliverQueuedMessages();
 	if(mMessagePipe)
 	{
 		mMessagePipe->pump(0.0f);
 
 	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?
 	
 	{
-		// Decode this message
-		LLPluginMessage parsed;
-		parsed.parse(message);
-		
 		std::string message_class = parsed.getClass();
 		if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
 		{
 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;
 
 		// 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")
 		{
 		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;
+		}
+	}
 }
 
 
 	LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
 	mState = state; 
 };
+
+void LLPluginProcessChild::deliverQueuedMessages()
+{
+	if(!mBlockingRequest)
+	{
+		while(!mMessageQueue.empty())
+		{
+			receiveMessageRaw(mMessageQueue.front());
+			mMessageQueue.pop();
+		}
+	}
+}

indra/llplugin/llpluginprocesschild.h

 	LLTimer mHeartbeat;
 	F64		mSleepTime;
 	F64		mCPUElapsed;
+	bool	mBlockingRequest;
+	bool	mBlockingResponseReceived;
+	std::queue<std::string> mMessageQueue;
+	
+	void deliverQueuedMessages();
 	
 };
 

indra/llplugin/llpluginprocessparent.cpp

 	mCPUUsage = 0.0;
 	mDisableTimeout = false;
 	mDebug = false;
+	mBlocked = false;
 
 	mPluginLaunchTimeout = 60.0f;
 	mPluginLockupTimeout = 15.0f;
 
 void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
 {
+	if(message.hasValue("blocking_response"))
+	{
+		mBlocked = false;
+
+		// reset the heartbeat timer, since there will have been no heartbeats while the plugin was blocked.
+		mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
+	}
 	
 	std::string buffer = message.generate();
 	LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;	
 
 void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message)
 {
+	if(message.hasValue("blocking_request"))
+	{
+		mBlocked = true;
+	}
+
 	std::string message_class = message.getClass();
 	if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
 	{
 {
 	bool result = false;
 	
-	if(!mDisableTimeout && !mDebug)
+	if(!mProcess.isRunning())
 	{
-		if(!mProcess.isRunning())
-		{
-			LL_WARNS("Plugin") << "child exited" << llendl;
-			result = true;
-		}
-		else if(pluginLockedUp())
-		{
-			LL_WARNS("Plugin") << "timeout" << llendl;
-			result = true;
-		}
+		LL_WARNS("Plugin") << "child exited" << llendl;
+		result = true;
+	}
+	else if(pluginLockedUp())
+	{
+		LL_WARNS("Plugin") << "timeout" << llendl;
+		result = true;
 	}
 	
 	return result;
 
 bool LLPluginProcessParent::pluginLockedUp()
 {
+	if(mDisableTimeout || mDebug || mBlocked)
+	{
+		// Never time out a plugin process in these cases.
+		return false;
+	}
+	
 	// If the timer is running and has expired, the plugin has locked up.
 	return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
 }

indra/llplugin/llpluginprocessparent.h

 	// returns true if the process has exited or we've had a fatal error
 	bool isDone(void);	
 	
+	// returns true if the process is currently waiting on a blocking request
+	bool isBlocked(void) { return mBlocked; };
+	
 	void killSockets(void);
 	
 	// Go to the proper error state
 	
 	bool mDisableTimeout;
 	bool mDebug;
+	bool mBlocked;
 
 	LLProcessLauncher mDebugger;