/* * Created on Nov 3, 2005 * Created by Alon Rohter * Copyright (C) 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.clientmessageservice.impl; import java.nio.channels.SocketChannel; import java.util.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.networkmanager.VirtualChannelSelector; import com.aelitis.azureus.core.peermanager.messaging.Message; import com.aelitis.azureus.core.peermanager.messaging.azureus.AZGenericMapPayload; /** * */ public class NonBlockingReadWriteService { private final VirtualChannelSelector read_selector; private final VirtualChannelSelector write_selector; private final ArrayList connections = new ArrayList(); private final AEMonitor connections_mon = new AEMonitor( "connections" ); private final ServiceListener listener; private final String service_name; private volatile boolean destroyed; private long last_timeout_check_time = 0; private static final int TIMEOUT_CHECK_INTERVAL_MS = 10*1000; //check for timeouts every 10sec private final int activity_timeout_period_ms; private final int close_delay_period_ms; public NonBlockingReadWriteService( String _service_name, int timeout, ServiceListener _listener ) { this( _service_name, timeout, 0, _listener ); } public NonBlockingReadWriteService( String _service_name, int timeout, int close_delay, ServiceListener _listener ) { this.service_name = _service_name; this.listener = _listener; read_selector = new VirtualChannelSelector( service_name, VirtualChannelSelector.OP_READ, false ); write_selector = new VirtualChannelSelector( service_name, VirtualChannelSelector.OP_WRITE, true ); if( timeout < TIMEOUT_CHECK_INTERVAL_MS /1000 ) timeout = TIMEOUT_CHECK_INTERVAL_MS /1000; this.activity_timeout_period_ms = timeout *1000; close_delay_period_ms = close_delay * 1000; new AEThread2( "[" +service_name+ "] Service Select", true ) { public void run() { while( true ) { boolean stop_after_select = destroyed; if ( stop_after_select ){ read_selector.destroy(); write_selector.destroy(); } try{ read_selector.select( 50 ); write_selector.select( 50 ); } catch( Throwable t ) { Debug.out( "[" +service_name+ "] SelectorLoop() EXCEPTION: ", t ); } if (stop_after_select){ break; } doConnectionTimeoutChecks(); // check this at the end so we have one last run through the selectors to do cancels // before exiting } } }.start(); } public void destroy() { try { connections_mon.enter(); connections.clear(); destroyed = true; }finally{ connections_mon.exit(); } } public void addClientConnection( ClientConnection connection ) { //add to active list try { connections_mon.enter(); if ( destroyed ){ Debug.out( "connection added after destroy" ); } connections.add( connection ); }finally { connections_mon.exit(); } registerForSelection( connection ); } public void removeClientConnection( ClientConnection connection ) { read_selector.cancel( connection.getSocketChannel() ); write_selector.cancel( connection.getSocketChannel() ); //remove from active list try { connections_mon.enter(); connections.remove( connection ); } finally { connections_mon.exit(); } } private void registerForSelection( final ClientConnection client ) { //READS VirtualChannelSelector.VirtualSelectorListener read_listener = new VirtualChannelSelector.VirtualSelectorListener() { //SUCCESS public boolean selectSuccess( VirtualChannelSelector selector, SocketChannel sc, Object attachment ) { try{ Message[] messages = client.readMessages(); if( messages != null ) { for( int i=0; i < messages.length; i++ ) { AZGenericMapPayload msg = (AZGenericMapPayload)messages[i]; ClientMessage client_msg = new ClientMessage( msg.getID(), client, msg.getMapPayload(), null ); //note no handler. we let the listener attach it listener.messageReceived( client_msg ); } } return( client.getLastReadMadeProgress()); } catch( Throwable t ) { if ( !client.isClosePending()){ //System.out.println( "[" +new Date()+ "] Connection read error [" +sc.socket().getInetAddress()+ "] [" +client.getDebugString()+ "]: " +t.getMessage() ); } listener.connectionError( client, t ); return( false ); } } //FAILURE public void selectFailure( VirtualChannelSelector selector, SocketChannel sc, Object attachment, Throwable msg ) { if ( !destroyed ){ msg.printStackTrace(); } listener.connectionError( client, msg ); } }; //WRITES final VirtualChannelSelector.VirtualSelectorListener write_listener = new VirtualChannelSelector.VirtualSelectorListener() { public boolean selectSuccess( VirtualChannelSelector selector, SocketChannel sc, Object attachment ) { try{ boolean more_writes_needed = client.writeMessages(); if( more_writes_needed ) { write_selector.resumeSelects( client.getSocketChannel() ); //we need to resume since write selects are auto-paused after select op } return( client.getLastWriteMadeProgress()); } catch( Throwable t ) { //System.out.println( "[" +new Date()+ "] Connection write error [" +sc.socket().getInetAddress()+ "] [" +client.getDebugString()+ "]: " +t.getMessage() ); listener.connectionError( client, t ); return( false ); } } public void selectFailure( VirtualChannelSelector selector, SocketChannel sc, Object attachment, Throwable msg ) { if ( !destroyed ){ msg.printStackTrace(); } listener.connectionError( client, msg ); } }; write_selector.register( client.getSocketChannel(), write_listener, null ); //start writing back to the connection write_selector.pauseSelects( client.getSocketChannel() ); //wait until we've got something to send before selecting read_selector.register( client.getSocketChannel(), read_listener, null ); //start reading from the connection } private void doConnectionTimeoutChecks() { //check timeouts long time = System.currentTimeMillis(); if( time < last_timeout_check_time || time - last_timeout_check_time > TIMEOUT_CHECK_INTERVAL_MS ) { ArrayList timed_out = new ArrayList(); try { connections_mon.enter(); long current_time = System.currentTimeMillis(); for( int i=0; i < connections.size(); i++ ) { ClientConnection vconn = (ClientConnection)connections.get( i ); if( current_time < vconn.getLastActivityTime() ) { //time went backwards! vconn.resetLastActivityTime(); } else{ if( current_time - vconn.getLastActivityTime() > activity_timeout_period_ms || ( close_delay_period_ms > 0 && current_time - vconn.getLastActivityTime() > close_delay_period_ms )){ timed_out.add( vconn ); //do actual removal outside the check loop } } } } finally { connections_mon.exit(); } for( int i=0; i < timed_out.size(); i++ ) { ClientConnection vconn = (ClientConnection)timed_out.get( i ); // don't change the exception text - it is used elsewhere listener.connectionError( vconn, new Exception( "Timeout" )); } last_timeout_check_time = System.currentTimeMillis(); } } public void sendMessage( ClientMessage message ) { ClientConnection vconn = message.getClient(); boolean still_connected; try { connections_mon.enter(); still_connected = connections.contains( vconn ); } finally { connections_mon.exit(); } if( !still_connected ) { // System.out.println( "[" +new Date()+ "] Connection message send error [connection no longer connected]: " +vconn.getDebugString()+ "]" ); message.reportFailed( new Exception("No longer connected" )); //listener.connectionError( vconn ); //no need to call this, as there is no connection to remove return; } Message reply = new AZGenericMapPayload( message.getMessageID(), message.getPayload(), (byte)1 ); vconn.sendMessage( message, reply ); write_selector.resumeSelects( vconn.getSocketChannel() ); //start write selecting now that there's something to send } public interface ServiceListener { public void messageReceived( ClientMessage message ); public void connectionError( ClientConnection connection, Throwable error ); } }