You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@mina.apache.org by Brian Parkinson <pa...@ecobee.com> on 2009/06/23 20:52:01 UTC

Question about mina - deadlock?

Hello:

I have a question about mina - wondering if someone out there has a couple minutes to help out:

I have a server running mina-2.0.0M1 (which is pretty old) which opens both a non-SSL and SSL ports. There are two issues I'm seeing in production - I'm not sure that this is mina related - still investigating, but right now, it looks to be comms related.

1. Every 4 days or so (so far, can't correlate to anything) the server "locks up" and refuses all subsequent requests (?). Trying to narrow down, I have been informed that there may be an issue with mina-core-2.0.0M1, and I should upgrade to mina-core-2.0.0M6. I've done this - the compile was pretty straightforward. The init code is attached below - does this look correct - just want to make sure that I'm not doing something wrong which might cause resources to leak over time.

2. I've noticed one recurring error in my log files - in my HttpRequestDecoder (extends MessageDecoderAdapter), I am throwing an exception trying to parse up the headers - the code ends up getting "partial line" headers (for example, the decoder attempts to parse a line "Content-Ty" with nothing else on the line) - it seems like the HTTP packet is not complete - should I be concerned about this? An exception is thrown, and the caller will re-send anyways, but the error is seen a fair bit throughout the logs, and I'm wondering if this might be contributing to the lock up issue (?).

Any help is appreciated - I'm flying a bit blind trying to track this down.

Regards,

Brian Parkinson
ecobee inc.
parki@ecobee.com

---

Here's how the mina server is set up:

private void start()
{
	makeConnection(8382, false);
	makeConnection(8383, true);
}

private void makeConnection(int port, boolean sslEnabled) throws Exception
{
	// Create an acceptor
	NioSocketAcceptor acceptor = new NioSocketAcceptor();
	
    DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();

	// Create a service configuration
    chain.addLast("protocolFilter", new ProtocolCodecFilter(new HttpServerProtocolCodecFactory()));
		
	if(sslEnabled)
	{
		Log.info(Server.class, "start", "Adding SSL filter."); 
		
		SslFilter sslFilter = new SslFilter(SslContextFactory.getServerContext());
		
		chain.addFirst("sslFilter", sslFilter);
	}
	
	if(logEnabled_)
	{
		Log.info(Server.class, "start", "Adding log filter."); 
		
		chain.addLast("logger", new LoggingFilter());
	}
	        
    // Add in the custom filter which manages session information within the server.
	// Add to the front of the chain, as we trap everything we need to here.
    chain.addFirst("myFilter", new ServerFilter(sslEnabled));

    // Set up the local address.
	acceptor.setDefaultLocalAddress(new InetSocketAddress(port));
	
	// Create the server handler and bind id to the acceptor.
	RequestHandler handler = new RequestHandler(sslEnabled);
	acceptor.setHandler(handler);
	acceptor.bind();
}

RE: Question about mina - deadlock?

Posted by Brian Parkinson <pa...@ecobee.com>.
Emmanuel writes:

"Can you post your decodable() and doDecode() methods ?"

Below is the code for the request decoder.

Any help is super-appreciated - 

Regards,

Brian...

--- x8 snip


package com.ecobee.communicator.server.mina;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.*;

import com.ecobee.communicator.server.IServer;

import com.ecobee.foundation.Container;
import com.ecobee.foundation.net.*;
import com.whatevernot.util.Log;
import org.apache.mina.common.IoBuffer;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;

/**
 * A request decoder, which handles the parsing of the incoming requests. This
 * is heavily adapted from the mina example code, but contains a number of changes
 * since we don't restrict communications to request and response.
 * @author The Apache MINA Project (dev@mina.apache.org)
 * @version $Rev: 593479 $, $Date: 2007-11-09 05:21:35 -0500 (Fri, 09 Nov 2007) $
 */
public class HttpRequestDecoder extends MessageDecoderAdapter
{
	final private static String DEFAULT_CONTENT_TYPE =
		((IServer) Container.getContext().getBean("server")).getDefaultContentType();
	
	final private static byte[] CONTENT_LENGTH = new String("Content-Length:").getBytes();
	final private static int CONTENT_LENGTH_LENGTH = CONTENT_LENGTH.length;
	
	final private CharsetDecoder DECODER = Charset.defaultCharset().newDecoder();

	private class ParseContext
	{
		private int offset;
		private IoBuffer in;
		public ParseContext(int offset, IoBuffer in)
		{
			this.offset = offset;
			this.in = in;
		}
	}
	
	/**
	 * Default constructor, as this object is created dynamically.
	 */
	public HttpRequestDecoder()
	{
	}
	
	/**
	 * @see MessageDecoderAdapter#decodable
	 */
	public MessageDecoderResult decodable(IoSession session, IoBuffer in)
	{	
		try
		{	
			boolean value = messageComplete(in);
			
			if(value)
			{				
				return MessageDecoderResult.OK;
			}
			else
			{
				// Return NEED_DATA if the whole header is not read yet.
				return MessageDecoderResult.NEED_DATA;
			}
	    }
		catch (Exception ex)
		{
			Log.error(this, "decodable", "Exception decoding HTTP request.", ex);
			
			return MessageDecoderResult.NOT_OK;
	    }    
	}
	
	/**
	 * @see MessageDecoderAdapter#decode
	 */
	public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
		throws Exception
	{
		ParseContext parseContext = new ParseContext(0, in);
		
		// Loop, as it's possible that there is more than one message in the buffer.
		while(true)
		{
			IHttpRequest request = parseRequest(parseContext);
			
			if(request == null) break;

			out.write(request);
		}
			
		return MessageDecoderResult.OK;
	}
	
	private boolean messageComplete(IoBuffer in) throws Exception
	{
		// We need at least 4 bytes to make any sense of the request.
		if(in.remaining() < 4)
		{
			return false;
		}
		
		// Loop forwards looking for the line separator 0x0D 0x0A 0x0D 0x0A.
		int lineSeparatorIndex = findSeparator(in);
		
		if(isGet(in))
		{
			// If there is a line separator, we have a valid GET request.
			return (lineSeparatorIndex != -1);
		}
		
		if(isPost(in))
		{
			// If there is no valid separator, then the POST request is invalid.
			if(lineSeparatorIndex == -1)
			{
				return false;
			}

			int last = in.remaining() - 1;
			for (int i = 0; i <= (last - CONTENT_LENGTH_LENGTH); i++)
            {
				boolean found = false;
				
				for(int j = 0; j < CONTENT_LENGTH_LENGTH; j++)
				{					
				    if(in.get(i + j) != CONTENT_LENGTH[j])
				    {
				    	found = false;
				    	break;
				    }
				    found = true;
				}

				if(found)
				{
					// Retrieve value from this position till next 0x0D 0x0A.
					StringBuilder contentLength = new StringBuilder();
					for(int j = i + CONTENT_LENGTH.length; j < last; j++)
					{
						if (in.get(j) == 0x0D) break;
						
						contentLength.append(new String(new byte[] { in.get(j) }));
					}
					
					int intContentLength = Integer.parseInt(contentLength.toString().trim());
		
					// If content-length worth of data has been received then the message is complete.
					return ((lineSeparatorIndex + 4 + intContentLength) <= in.remaining());
				}
			}
		}

		// The message is not complete and we need more data.
	    return false;
	}
	
	private IHttpRequest parseRequest(ParseContext parseContext) throws IOException
	{
		int offset = parseContext.offset;
		IoBuffer in = parseContext.in;
		
		// Find the line separator.
		int separatorIndex = findSeparator(in, offset);
		
		// If there is no separator, we need more data.
		if(separatorIndex == -1) return null;
		
		// The end of the message is the separator index + size of the separator.
		int endOfMessage = separatorIndex + 4;

		// Update the parse context offset to the beginning of the next message, if any.
		parseContext.offset = endOfMessage;
		
		String contents = in.getString(endOfMessage - offset, DECODER);

		BufferedReader reader = new BufferedReader(new StringReader(contents));

		String firstLine = reader.readLine();
		if(firstLine == null) return null;
		
		String[] url = firstLine.split(" ");

		if (url.length < 3) return null;
	
		String method = url[0].toUpperCase();
		String context = url[1];
		String protocol = url[2];
		
		Map<String, String> headers = new HashMap<String, String>();
		
		// Parse up the headers.
		while(true)
		{
			String line = reader.readLine();
			
			if((line == null) || (line.length() == 0)) break;
			
			String[] tokens = line.split(": ");
			if(tokens.length == 2)
			{
				headers.put(tokens[0], tokens[1]);
			}
			else if(tokens.length == 1)
			{
				headers.put(tokens[0], "");
			}
			else
			{
				Log.error(this, "parseRequest", "Cannot parse partial header: " + line + " firstLine: " + firstLine);
				return null;
			}
		}
		
		// Determine if the request is compressed and/or encrypted or not.
		String contentType = headers.get(IHttpRequest.CONTENT_TYPE_HEADER);
		if(contentType == null) contentType = DEFAULT_CONTENT_TYPE;

		IHttpRequest request = makeRequest(contentType);
		
		request.setMethod(method);
		request.setContext(context);
		request.setProtocol(protocol);
		
		Iterator<String> iterator = headers.keySet().iterator();
		while(iterator.hasNext())
		{
			String key = iterator.next();
			String value = headers.get(key);
			request.setHeader(key, value);
		}
		
		// Parse up any parameters.
		int idx = context.indexOf('?');
		if(idx != -1)
		{
			// The context becomes everything up to the '?' character.
			request.setContext(context.substring(0, idx));
			
			// The parameter list is everything else.
	        String params = url[1].substring(idx + 1);
	        
	        // Split up the parameter list.
			String[] match = params.split("\\&");
			for(String element : match)
			{
				String[] tokens = element.split("=");

				switch(tokens.length)
				{
				case 0:
					request.setParameter(element, "");
				    break;
				case 1:
					request.setParameter(tokens[0], "");
					break;
				default:
					request.setParameter(tokens[0], tokens[1]);
					break;
				}
			}
		}

		// If method 'POST' then read Content-Length worth of data
		if(request.getMethod() == IHttpRequest.HttpMethod.POST)
		{
			int contentLength = request.getContentLength();

			byte[] bodyBytes = new byte[contentLength];
			
			in.get(bodyBytes, 0, contentLength);

			request.setBodyBytes(bodyBytes);
		}

		return request;
	}
	
	private int findSeparator(IoBuffer in)
	{
		return findSeparator(in, 0);
	}
	
	private int findSeparator(IoBuffer in, int offset)
	{
		int last = in.remaining() - 1;
		for(int i = offset; i <= last - 3; ++i)
		{
			if(isSeparator(in, i)) return i;
		}	
		return -1;
	}
	
	private boolean isSeparator(IoBuffer in, int i)
	{
		return ((in.get(i) == (byte) 0x0D &&
			in.get(i + 1) == (byte) 0x0A && 
			in.get(i + 2) == (byte) 0x0D &&
			in.get(i + 3) == (byte) 0x0A));
	}
	
	private boolean isGet(IoBuffer in)
	{
		return (in.get(0) == (byte) 'G') &&
		(in.get(1) == (byte) 'E') &&
		(in.get(2) == (byte) 'T');
	}
	
	private boolean isPost(IoBuffer in)
	{
		return ((in.get(0) == (byte) 'P') &&
			(in.get(1) == (byte) 'O') &&
			(in.get(2) == (byte) 'S') &&
			(in.get(3) == (byte) 'T'));
	}

	private IHttpRequest makeRequest(String contentType)
	{
		if(IHttpRequest.AES_CONTENT_TYPE.equals(contentType))
		{
			return new AesInboundHttpRequest();
		}
		else if(IHttpRequest.ZLIB_CONTENT_TYPE.equals(contentType))
		{
			return new ZLibInboundHttpRequest();
		}
		else
		{
			return new HttpRequest();
		}	
	}
}

Re: Question about mina - deadlock?

Posted by Emmanuel Lecharny <el...@apache.org>.
Brian Parkinson wrote:
> Emmanuel writes: 
>
> ---x8 snip
>
>   
>> 2. I've noticed one recurring error in my log files - in my HttpRequestDecoder (extends MessageDecoderAdapter), I am throwing an exception trying to parse up the headers - the code ends up getting "partial line" headers (for example, the decoder attempts to parse a line "Content-Ty" with nothing else on the line) - it seems like the HTTP packet is not complete - should I be concerned about this? An exception is thrown, and the caller will re-send anyways, but the error is seen a fair bit throughout the logs, and I'm wondering if this might be contributing to the lock up issue (?).
>>     
>    
> You have no guarantee that the messages sent by a client will be received fully. They can even been sent one byte at a time. It's up to you to gather all the incoming bytes until you have enough bytes to decode your protocol. In your case, however, you can use the TextLineDecoder which waits for a line feed to forward the decoded message to your handler.
>
> ---x8 snip
>
> I am using a custom HttpRequestDecoder (which subclasses MessageDecoderAdaptor). The 'decodable' method looks correct, but then when 'decode' gets called, the buffer is incomplete - I can't figure that out. Almost invariably, the 'decode' method fails on an incomplete header line - I see a truncated header 'Content-Ty' repeatedly in the logs. 
>   
Can you post your decodable() and doDecode() methods ?

-- 
--
cordialement, regards,
Emmanuel Lécharny
www.iktek.com
directory.apache.org



RE: Question about mina - deadlock?

Posted by Brian Parkinson <pa...@ecobee.com>.
Emmanuel writes: 

---x8 snip

> 2. I've noticed one recurring error in my log files - in my HttpRequestDecoder (extends MessageDecoderAdapter), I am throwing an exception trying to parse up the headers - the code ends up getting "partial line" headers (for example, the decoder attempts to parse a line "Content-Ty" with nothing else on the line) - it seems like the HTTP packet is not complete - should I be concerned about this? An exception is thrown, and the caller will re-send anyways, but the error is seen a fair bit throughout the logs, and I'm wondering if this might be contributing to the lock up issue (?).
   
You have no guarantee that the messages sent by a client will be received fully. They can even been sent one byte at a time. It's up to you to gather all the incoming bytes until you have enough bytes to decode your protocol. In your case, however, you can use the TextLineDecoder which waits for a line feed to forward the decoded message to your handler.

---x8 snip

I am using a custom HttpRequestDecoder (which subclasses MessageDecoderAdaptor). The 'decodable' method looks correct, but then when 'decode' gets called, the buffer is incomplete - I can't figure that out. Almost invariably, the 'decode' method fails on an incomplete header line - I see a truncated header 'Content-Ty' repeatedly in the logs. 

A lot of this code was written > 1 year ago and based on M1. Should I be decoding differently?

Thanks.

Brian Parkinson
ecobee.com




Re: Question about mina - deadlock?

Posted by Emmanuel Lecharny <el...@apache.org>.
Brian Parkinson wrote:
> Hello:
>
> I have a question about mina - wondering if someone out there has a couple minutes to help out:
>
> I have a server running mina-2.0.0M1 (which is pretty old) which opens both a non-SSL and SSL ports. There are two issues I'm seeing in production - I'm not sure that this is mina related - still investigating, but right now, it looks to be comms related.
>
> 1. Every 4 days or so (so far, can't correlate to anything) the server "locks up" and refuses all subsequent requests (?). Trying to narrow down, I have been informed that there may be an issue with mina-core-2.0.0M1, and I should upgrade to mina-core-2.0.0M6. I've done this - the compile was pretty straightforward. The init code is attached below - does this look correct - just want to make sure that I'm not doing something wrong which might cause resources to leak over time.
>   
Seems ok to me.
> 2. I've noticed one recurring error in my log files - in my HttpRequestDecoder (extends MessageDecoderAdapter), I am throwing an exception trying to parse up the headers - the code ends up getting "partial line" headers (for example, the decoder attempts to parse a line "Content-Ty" with nothing else on the line) - it seems like the HTTP packet is not complete - should I be concerned about this? An exception is thrown, and the caller will re-send anyways, but the error is seen a fair bit throughout the logs, and I'm wondering if this might be contributing to the lock up issue (?).
>   
You have no guarantee that the messages sent by a client will be 
received fully. They can even been sent one byte at a time. It's up to 
you to gather all the incoming bytes until you have enough bytes to 
decode your protocol. In your case, however, you can use the 
TextLineDecoder which waits for a line feed to forward the decoded 
message to your handler.

I think it's described on the sparse doco...

-- 
--
cordialement, regards,
Emmanuel Lécharny
www.iktek.com
directory.apache.org