/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.transport.socket;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.net.SocketException;
import java.util.LinkedList;
import java.util.Map;
import org.jboss.logging.Logger;
import org.jboss.remoting.marshal.MarshalFactory;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
/**
* This Thread object hold a single Socket connection to a client
* and is kept alive until a timeout happens, or it is aged out of the
* SocketServerInvoker's LRU cache.
* <p/>
* There is also a separate thread pool that is used if the client disconnects.
* This thread/object is re-used in that scenario and that scenario only.
* <p/>
* This is a customization of the same ServerThread class used witht the PookedInvoker.
* The custimization was made to allow for remoting marshaller/unmarshaller.
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author <a href="mailto:tom@jboss.org">Tom Elrod</a>
* @version $Revision: 1.21 $
*/
public class ServerThread extends Thread
{
final static private Logger log = Logger.getLogger(ServerThread.class);
protected SocketServerInvoker invoker;
protected LRUPool clientpool;
protected LinkedList threadpool;
protected volatile boolean running = true;
protected volatile boolean handlingResponse = true; // start off as true so that nobody can interrupt us
protected volatile boolean shutdown = false;
protected static int id = 0;
private SocketWrapper socketWrapper = null;
protected String serverSocketClass = ServerSocketWrapper.class.getName();
private Constructor serverSocketConstructor = null;
private boolean shouldCheckConnection = true;
public static synchronized int nextID()
{
int nextID = id++;
return nextID;
}
public ServerThread(Socket socket, SocketServerInvoker invoker, LRUPool clientpool,
LinkedList threadpool, int timeout, String serverSocketClass) throws Exception
{
super("SocketServerInvokerThread-" + socket.getInetAddress().getHostAddress() + "-" + nextID());
this.serverSocketClass = serverSocketClass;
this.socketWrapper = createServerSocket(socket, timeout, invoker.getLocator().getParameters());
this.invoker = invoker;
this.clientpool = clientpool;
this.threadpool = threadpool;
init();
}
private void init()
{
if(invoker != null)
{
Map configMap = invoker.getConfiguration();
String checkValue = (String)configMap.get(SocketServerInvoker.CHECK_CONNECTION_KEY);
if(checkValue != null && checkValue.length() > 0)
{
shouldCheckConnection = Boolean.valueOf(checkValue).booleanValue();
}
}
}
public void shutdown()
{
shutdown = true;
running = false;
// This is a race and there is a chance
// that a invocation is going on at the time
// of the interrupt. But I see no way right
// now to protect for this.
// NOTE ALSO!:
// Shutdown should never be synchronized.
// We don't want to hold up accept() thread! (via LRUpool)
if(!handlingResponse)
{
try
{
this.interrupt();
Thread.interrupted(); // clear
}
catch(Exception ignored)
{
}
}
}
/**
* Sets if server thread should check connection before continue to process on
* next invocation request. If is set to true, will send an ACK to client to
* verify client is still connected on same socket.
* @param checkConnection
*/
public void shouldCheckConnection(boolean checkConnection)
{
this.shouldCheckConnection = checkConnection;
}
/**
* Indicates if server will check with client (via an ACK) to
* see if is still there.
* @return
*/
public boolean getCheckingConnection()
{
return this.shouldCheckConnection;
}
private SocketWrapper createServerSocket(Socket socket, int timeout, Map metadata) throws Exception
{
if(serverSocketConstructor == null)
{
//ClassLoader classLoader = invoker.getClassLoader();
ClassLoader classLoader = null;
if(classLoader == null)
{
classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
{
classLoader = getClass().getClassLoader();
}
}
Class cl = classLoader.loadClass(serverSocketClass);
try
{
serverSocketConstructor = cl.getConstructor(new Class[]{Socket.class, Map.class, Integer.class});
}
catch(NoSuchMethodException e)
{
serverSocketConstructor = cl.getConstructor(new Class[]{Socket.class});
}
}
SocketWrapper serverSocketWrapper = null;
if(serverSocketConstructor.getParameterTypes().length == 3)
{
serverSocketWrapper = (SocketWrapper) serverSocketConstructor.newInstance(new Object[]{socket, metadata, new Integer(timeout)});
}
else
{
serverSocketWrapper = (SocketWrapper) serverSocketConstructor.newInstance(new Object[]{socket});
serverSocketWrapper.setTimeout(timeout);
}
return serverSocketWrapper;
}
public void evict()
{
running = false;
// This is a race and there is a chance
// that a invocation is going on at the time
// of the interrupt. But I see no way right
// now to protect for this.
// There may not be a problem because interrupt only effects
// threads blocking on IO.
// NOTE ALSO!:
// Shutdown should never be synchronized.
// We don't want to hold up accept() thread! (via LRUpool)
if(!handlingResponse)
{
try
{
this.interrupt();
Thread.interrupted(); // clear
}
catch(Exception ignored)
{
}
}
}
public synchronized void wakeup(Socket socket, int timeout, Map metadata) throws Exception
{
this.socketWrapper = createServerSocket(socket, timeout, metadata);
String name = "SocketServerInvokerThread-" + socket.getInetAddress().getHostAddress() + "-" + nextID();
super.setName(name);
running = true;
handlingResponse = true;
this.notify();
}
public void run()
{
try
{
while(true)
{
dorun();
/*
* The following code has been changed to eliminate a race condition with SocketServerInvoker.cleanup().
* A ServerThread can shutdown for two reasons:
*
* 1. the client shuts down, and
* 2. the server shuts down.
*
* If both occur around the same time, a problem arises. If a ServerThread starts to shut
* down because the client shut down, it will test shutdown, and if it gets to the test
* before SocketServerInvoker.cleanup() calls ServerThread.stop() to set shutdown to true, it
* will return itself to threadpool. If it moves from clientpool to threadpool at just the
* right time, SocketServerInvoker could miss it in both places and never call stop(), leaving
* it alive, resulting in a memory leak. The solution is to synchronize parts of
* ServerThread.run() and SocketServerInvoker.cleanup() so that they interact atomically.
*/
synchronized(clientpool)
{
synchronized(threadpool)
{
if(shutdown)
{
invoker = null;
return; // exit thread
}
else
{
clientpool.remove(this);
threadpool.add(this);
Thread.interrupted(); // clear any interruption so that we can be pooled.
clientpool.notify();
}
}
}
synchronized(this)
{
try
{
log.debug("begin thread wait");
this.wait();
log.debug("WAKEUP in SERVER THREAD");
}
catch (InterruptedException e)
{
if (shutdown)
{
invoker = null;
return; // exit thread
}
throw e;
}
}
}
}
catch(Exception ignored)
{
log.debug("Exiting run on exception", ignored);
}
}
protected void acknowledge() throws Exception
{
if(shouldCheckConnection)
{
// HERE IS THE RACE between ACK received and handlingResponse = true
// We can't synchronize because readByte blocks and client is expecting
// a response and we don't want to hang client.
// see shutdown and evict for more details
// There may not be a problem because interrupt only effects
// threads blocking on IO. and this thread will just continue.
handlingResponse = true;
try
{
socketWrapper.checkConnection();
}
catch(EOFException e)
{
throw new AcknowledgeFailure();
}
catch(SocketException se)
{
throw new AcknowledgeFailure();
}
catch(IOException ioe)
{
throw new AcknowledgeFailure();
}
handlingResponse = false;
}
}
protected void processInvocation() throws Exception
{
handlingResponse = true;
// Ok, now read invocation and invoke
//TODO: -TME Need better way to get the unmarshaller (via config)
UnMarshaller unmarshaller = MarshalFactory.getUnMarshaller(invoker.getLocator(), this.getClass().getClassLoader());
if(unmarshaller == null)
{
unmarshaller = MarshalFactory.getUnMarshaller(invoker.getDataType(), invoker.getSerializationType());
}
String serializationType = (String) invoker.getLocator().findSerializationType();
// Object obj = unmarshaller.read(socketWrapper.getInputStream(serializationType),null);
Object obj = unmarshaller.read(socketWrapper.getInputStream(), null);
Object resp = null;
try
{
// Make absolutely sure thread interrupted is cleared.
boolean interrupted = Thread.interrupted();
// call transport on the subclass, get the result to handback
resp = invoker.invoke(obj);
/*
if(log.isDebugEnabled())
{
System.err.println("++ returning invocation response : " + resp);
}
*/
}
catch(Exception ex)
{
resp = ex;
}
Thread.interrupted(); // clear interrupted state so we don't fail on socket writes
Marshaller marshaller = MarshalFactory.getMarshaller(invoker.getLocator(), this.getClass().getClassLoader());
if(marshaller == null)
{
marshaller = MarshalFactory.getMarshaller(invoker.getDataType(), invoker.getSerializationType());
}
// marshaller.write(resp, socketWrapper.getOutputStream(serializationType));
marshaller.write(resp, socketWrapper.getOutputStream());
handlingResponse = false;
}
/**
* This is needed because Object*Streams leak
*/
protected void dorun()
{
log.debug("beginning dorun");
running = true;
handlingResponse = true;
// Always do first one without an ACK because its not needed
try
{
processInvocation();
}
catch(Exception ex)
{
log.error("failed to process invocation.", ex);
running = false;
}
// Re-use loop
while(running)
{
try
{
acknowledge();
processInvocation();
}
catch(AcknowledgeFailure e)
{
if(!shutdown)
{
log.trace("Keep alive acknowledge failed.");
}
running = false;
}
catch(InterruptedIOException e)
{
if(!shutdown)
{
log.error("socket timed out", e);
}
running = false;
}
catch(InterruptedException e)
{
if(!shutdown)
{
log.error("interrupted", e);
}
}
catch(EOFException eof)
{
if(!shutdown)
{
log.trace("EOF received. This is likely due to client finishing comminication.");
}
running = false;
}
catch(SocketException sex)
{
if(!shutdown)
{
log.trace("SocketException received. This is likely due to client disconnecting and resetting connection.");
}
running = false;
}
catch(Exception ex)
{
if(!shutdown)
{
log.error("failed", ex);
running = false;
}
}
// clear any interruption so that thread can be pooled.
handlingResponse = false;
Thread.interrupted();
}
// Ok, we've been shutdown. Do appropriate cleanups.
try
{
InputStream in = socketWrapper.getInputStream();
if(in != null)
{
in.close();
}
OutputStream out = socketWrapper.getOutputStream();
if(out != null)
{
out.close();
}
}
catch(Exception ex)
{
}
try
{
socketWrapper.close();
}
catch(Exception ex)
{
log.error("Failed cleanup", ex);
}
socketWrapper = null;
}
public static class AcknowledgeFailure extends Exception
{
}
}