Package org.jboss.remoting.transport.socket

Source Code of org.jboss.remoting.transport.socket.ServerThread

/*
* 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
   {

   }
}
TOP

Related Classes of org.jboss.remoting.transport.socket.ServerThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.