FELIX-914 Ensure remote shell connections are terminated
when the remote shell bundle is stopped.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@739941 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java b/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
index 689cdda..396699c 100644
--- a/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
+++ b/shell.remote/src/main/java/org/apache/felix/shell/remote/Listener.java
@@ -24,6 +24,8 @@
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
+import java.util.HashSet;
+import java.util.Set;
import org.osgi.framework.BundleContext;
@@ -42,6 +44,7 @@
private AtomicInteger m_UseCounter;
private int m_MaxConnections;
private int m_SoTimeout;
+ private Set m_connections;
/**
@@ -55,6 +58,7 @@
m_SoTimeout = getProperty( bundleContext, "osgi.shell.telnet.socketTimeout", 0 );
m_MaxConnections = getProperty( bundleContext, "osgi.shell.telnet.maxconn", 2 );
m_UseCounter = new AtomicInteger( 0 );
+ m_connections = new HashSet();
m_ListenerThread = new Thread( new Acceptor(), "telnetconsole.Listener" );
m_ListenerThread.start();
}//activate
@@ -80,6 +84,23 @@
{
Activator.getServices().error( "Listener::deactivate()", ex );
}
+
+ // get the active connections (and clear the list)
+ // we have to work on a copy, since stopping any active connection
+ // will try to remove itself from the set, which might cause a
+ // ConcurrentModificationException if we would iterate over the list
+ Shell[] connections;
+ synchronized ( m_connections )
+ {
+ connections = ( Shell[] ) m_connections.toArray( new Shell[m_connections.size()] );
+ m_connections.clear();
+ }
+
+ // now terminate all active connections
+ for ( int i = 0; i < connections.length; i++ )
+ {
+ connections[i].terminate();
+ }
}//deactivate
/**
@@ -128,7 +149,8 @@
{
m_UseCounter.increment();
//run on the connection thread
- Thread connectionThread = new Thread( new Shell( s, m_UseCounter ) );
+ Thread connectionThread = new Thread( new Shell( Listener.this, s, m_UseCounter ) );
+ connectionThread.setName( "telnetconsole.shell remote=" + s.getRemoteSocketAddress() );
connectionThread.start();
}
}
@@ -177,10 +199,46 @@
String propValue = bundleContext.getProperty( propName );
if ( propValue != null )
{
- return propValue;
+ return propValue;
}
return defaultValue;
}
+ /**
+ * Registers the given {@link Shell} instance handling a remote connection
+ * to this listener.
+ * <p>
+ * This method is called by the {@link Shell#run()} method to register the
+ * remote connection for it to be terminated in case this listener is
+ * {@link #deactivate() deactivated} before the remote connection is
+ * terminated.
+ *
+ * @param connection The {@link Shell} connection to register
+ */
+ void registerConnection( Shell connection )
+ {
+ synchronized ( m_connections )
+ {
+ m_connections.add( connection );
+ }
+ }
+
+ /**
+ * Unregisters the given {@link Shell} instance handling a remote connection
+ * from this listener.
+ * <p>
+ * This method is called when the {@link Shell#run()} method terminates to
+ * inform this listener instance that the remote connection has ended and
+ * thus does not need to be cleaned up when this listener terminates.
+ *
+ * @param connection The {@link Shell} connection to unregister
+ */
+ void unregisterConnection( Shell connection )
+ {
+ synchronized ( m_connections )
+ {
+ m_connections.remove( connection );
+ }
+ }
}//class Listener
diff --git a/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java b/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
index 636a4e9..f14c026 100644
--- a/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
+++ b/shell.remote/src/main/java/org/apache/felix/shell/remote/Shell.java
@@ -27,26 +27,44 @@
/**
* Implements the shell.
+ * <p>
+ * This class is instantiated by the {@link Listener} thread to handle a single
+ * remote connection in its own thread. The connection handler thread either
+ * terminates on request by the remote end or by the Remote Shell bundle being
+ * stopped. In the latter case, the {@link #terminate()} method is called, which
+ * closes the Socket used to handle the remote console. This causes a
+ * <code>SocketException</code> in the handler thread reading from the socket
+ * which in turn causes the {@link #run()} method to terminate and thus to
+ * end the handler thread.
*/
class Shell implements Runnable
{
+ private Listener m_owner;
private Socket m_Socket;
private AtomicInteger m_UseCounter;
- public Shell( Socket s, AtomicInteger counter )
+ public Shell( Listener owner, Socket s, AtomicInteger counter )
{
+ m_owner = owner;
m_Socket = s;
m_UseCounter = counter;
}//constructor
-
+ void terminate()
+ {
+ // called by Listener.deactivate() to terminate this session
+ exit( "\r\nFelix Remote Shell Console Terminating" );
+ }//terminate
+
/**
* Runs the shell.
*/
public void run()
{
+ m_owner.registerConnection( this );
+
try
{
PrintStream out = new TerminalPrintStream( m_Socket.getOutputStream() );
@@ -55,8 +73,8 @@
// Print welcome banner.
out.println();
- out.println( "Felix Shell Console:" );
- out.println( "=====================" );
+ out.println( "Felix Remote Shell Console:" );
+ out.println( "============================" );
out.println( "" );
do
@@ -69,22 +87,18 @@
//make sure to capture end of stream
if ( line == null )
{
- exit();
+ out.println( "exit" );
return;
}
}
catch ( Exception ex )
{
- exit();
return;
}
line = line.trim();
if ( line.equalsIgnoreCase( "exit" ) || line.equalsIgnoreCase( "disconnect" ) )
{
- in.close();
- out.close();
- exit();
return;
}
@@ -109,11 +123,32 @@
{
Activator.getServices().error( "Shell::run()", ex );
}
+ finally
+ {
+ // no need to clean up in/out, since exit does it all
+ exit( null );
+ }
}//run
- private void exit()
+ private void exit(String message)
{
+ // farewell message
+ try
+ {
+ PrintStream out = new TerminalPrintStream( m_Socket.getOutputStream() );
+ if ( message != null )
+ {
+ out.println( message );
+ }
+ out.println( "Good Bye!" );
+ out.close();
+ }
+ catch ( IOException ioe )
+ {
+ // ignore
+ }
+
try
{
m_Socket.close();
@@ -122,6 +157,7 @@
{
Activator.getServices().error( "Shell::exit()", ex );
}
+ m_owner.unregisterConnection( this );
m_UseCounter.decrement();
}//exit