/***************************************************************************
* Copyright (C) 2009 by Fabrizio Montesi <famontesi@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Library 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 Library 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. *
* *
* For details about the authors of this software, see the AUTHORS file. *
***************************************************************************/
package jolie.net;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimerTask;
import jolie.ExecutionThread;
import jolie.Interpreter;
import jolie.runtime.FaultException;
import jolie.runtime.Value;
public abstract class AbstractCommChannel extends CommChannel
{
private static final long RECEIVER_KEEP_ALIVE = 20000; // msecs
private final Map< Long, CommMessage > pendingResponses =
new HashMap< Long, CommMessage >();
private final Map< Long, ResponseContainer > waiters =
new HashMap< Long, ResponseContainer >();
private final List< CommMessage > pendingGenericResponses =
new LinkedList< CommMessage >();
private final Object responseRecvMutex = new Object();
private static class ResponseContainer
{
private ResponseContainer() {}
private CommMessage response = null;
}
public CommMessage recvResponseFor( CommMessage request )
throws IOException
{
CommMessage response;
ResponseContainer monitor = null;
synchronized( responseRecvMutex ) {
response = pendingResponses.remove( request.id() );
if ( response == null ) {
if ( pendingGenericResponses.isEmpty() ) {
assert( waiters.containsKey( request.id() ) == false );
monitor = new ResponseContainer();
waiters.put( request.id(), monitor );
//responseRecvMutex.notify();
} else {
response = pendingGenericResponses.remove( 0 );
}
}
}
if ( response == null ) {
synchronized( responseRecvMutex ) {
if ( responseReceiver == null ) {
responseReceiver = new ResponseReceiver( this, ExecutionThread.currentThread() );
Interpreter.getInstance().commCore().startCommChannelHandler( responseReceiver );
} else {
responseReceiver.wakeUp();
}
}
synchronized( monitor ) {
if ( monitor.response == null ) {
try {
monitor.wait();
} catch( InterruptedException e ) {
Interpreter.getInstance().logSevere( e );
}
}
response = monitor.response;
}
}
return response;
}
private ResponseReceiver responseReceiver = null;
private static class ResponseReceiver implements Runnable
{
private final AbstractCommChannel parent;
private final ExecutionThread ethread;
private boolean keepRun;
private TimerTask timeoutTask;
private void timeout()
{
synchronized( parent.responseRecvMutex ) {
if ( keepRun == false ) {
if ( parent.waiters.isEmpty() ) {
timeoutTask = null;
parent.responseReceiver = null;
} else {
keepRun = true;
}
parent.responseRecvMutex.notify();
}
}
}
private void wakeUp()
{
if ( timeoutTask != null ) {
timeoutTask.cancel();
}
keepRun = true;
parent.responseRecvMutex.notify();
}
private void sleep()
{
final ResponseReceiver receiver = this;
timeoutTask = new TimerTask() {
public void run()
{
receiver.timeout();
}
};
ethread.interpreter().schedule( timeoutTask, RECEIVER_KEEP_ALIVE );
try {
keepRun = false;
parent.responseRecvMutex.wait();
} catch( InterruptedException e ) {
Interpreter.getInstance().logSevere( e );
}
}
private ResponseReceiver( AbstractCommChannel parent, ExecutionThread ethread )
{
this.ethread = ethread;
this.parent = parent;
this.keepRun = true;
this.timeoutTask = null;
}
private void handleGenericMessage( CommMessage response )
{
ResponseContainer monitor;
if ( parent.waiters.isEmpty() ) {
parent.pendingGenericResponses.add( response );
} else {
Entry< Long, ResponseContainer > entry =
parent.waiters.entrySet().iterator().next();
monitor = entry.getValue();
parent.waiters.remove( entry.getKey() );
synchronized( monitor ) {
monitor.response = new CommMessage(
entry.getKey(),
response.operationName(),
response.resourcePath(),
response.value(),
response.fault()
);
monitor.notify();
}
}
}
private void handleMessage( CommMessage response )
{
ResponseContainer monitor;
if ( (monitor=parent.waiters.remove( response.id() )) == null ) {
parent.pendingResponses.put( response.id(), response );
} else {
synchronized( monitor ) {
monitor.response = response;
monitor.notify();
}
}
}
private void throwIOExceptionFault( IOException e )
{
if ( parent.waiters.isEmpty() == false ) {
ResponseContainer monitor;
for( Entry< Long, ResponseContainer > entry : parent.waiters.entrySet() ) {
monitor = entry.getValue();
synchronized( monitor ) {
monitor.response = new CommMessage(
entry.getKey(),
"",
"/",
Value.create(),
new FaultException( "IOException", e )
);
monitor.notify();
}
}
parent.waiters.clear();
}
}
public void run()
{
/*
* Warning: the following line implies that this
* whole thing is safe iff the CommChannel is used only for outputs,
* otherwise we are messing with correlation set checking.
*/
CommChannelHandler.currentThread().setExecutionThread( ethread ); // TODO: this is hacky..
CommMessage response;
while( keepRun ) {
synchronized( parent.responseRecvMutex ) {
try {
response = parent.recv();
if ( response != null ) {
if ( response.hasGenericId() ) {
handleGenericMessage( response );
} else {
handleMessage( response );
}
}
if ( parent.waiters.isEmpty() ) {
sleep();
}
} catch( IOException e ) {
throwIOExceptionFault( e );
keepRun = false;
parent.responseReceiver = null;
}
}
}
}
}
}