/*
* Created on Jul 19, 2004
* Created by Alon Rohter
* Copyright (C) 2004, 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.peermanager.utils;
import java.util.*;
import org.gudy.azureus2.core3.disk.*;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.util.*;
import com.aelitis.azureus.core.networkmanager.OutgoingMessageQueue;
import com.aelitis.azureus.core.peermanager.messaging.*;
import com.aelitis.azureus.core.peermanager.messaging.bittorrent.*;
/**
* Front-end manager for handling requested outgoing bittorrent Piece messages.
* Peers often make piece requests in batch, with multiple requests always
* outstanding, all of which won't necessarily be honored (i.e. we choke them),
* so we don't want to waste time reading in the piece data from disk ahead
* of time for all the requests. Thus, we only want to perform read-aheads for a
* small subset of the requested data at any given time, which is what this handler
* does, before passing the messages onto the outgoing message queue for transmission.
*/
public class OutgoingBTPieceMessageHandler {
private final PEPeer peer;
private final OutgoingMessageQueue outgoing_message_queue;
private byte piece_version;
private final LinkedList<DiskManagerReadRequest> requests = new LinkedList<DiskManagerReadRequest>();
private final ArrayList<DiskManagerReadRequest> loading_messages = new ArrayList<DiskManagerReadRequest>();
private final HashMap<BTPiece,DiskManagerReadRequest> queued_messages = new HashMap<BTPiece, DiskManagerReadRequest>();
private final AEMonitor lock_mon = new AEMonitor( "OutgoingBTPieceMessageHandler:lock");
private boolean destroyed = false;
private int request_read_ahead = 2;
private OutgoingBTPieceMessageHandlerAdapter adapter;
/**
* Create a new handler for outbound piece messages,
* reading piece data from the given disk manager
* and transmitting the messages out the given message queue.
* @param disk_manager
* @param outgoing_message_q
*/
public
OutgoingBTPieceMessageHandler(
PEPeer _peer,
OutgoingMessageQueue _outgoing_message_q,
OutgoingBTPieceMessageHandlerAdapter _adapter,
byte _piece_version )
{
peer = _peer;
outgoing_message_queue = _outgoing_message_q;
adapter = _adapter;
piece_version = _piece_version;
outgoing_message_queue.registerQueueListener( sent_message_listener );
}
public void
setPieceVersion(
byte version )
{
piece_version = version;
}
private final DiskManagerReadRequestListener read_req_listener = new DiskManagerReadRequestListener() {
public void readCompleted( DiskManagerReadRequest request, DirectByteBuffer data ) {
try{
lock_mon.enter();
if( !loading_messages.contains( request ) || destroyed ) { //was canceled
data.returnToPool();
return;
}
loading_messages.remove( request );
BTPiece msg = new BTPiece( request.getPieceNumber(), request.getOffset(), data, piece_version );
queued_messages.put( msg, request );
outgoing_message_queue.addMessage( msg, true );
}
finally{
lock_mon.exit();
}
outgoing_message_queue.doListenerNotifications();
}
public void
readFailed(
DiskManagerReadRequest request,
Throwable cause )
{
try{
lock_mon.enter();
if( !loading_messages.contains( request ) || destroyed ) { //was canceled
return;
}
loading_messages.remove( request );
}finally{
lock_mon.exit();
}
peer.sendRejectRequest( request );
}
public int
getPriority()
{
return( -1 );
}
public void
requestExecuted(long bytes)
{
adapter.diskRequestCompleted( bytes );
}
};
private final OutgoingMessageQueue.MessageQueueListener sent_message_listener = new OutgoingMessageQueue.MessageQueueListener() {
public boolean messageAdded( Message message ) { return true; }
public void messageSent( Message message ) {
if( message.getID().equals( BTMessage.ID_BT_PIECE ) ) {
try{
lock_mon.enter();
// due to timing issues we can get in here with a message already removed
queued_messages.remove( message );
}finally{
lock_mon.exit();
}
/*
if ( peer.getIp().equals( "64.71.5.2" )){
outgoing_message_queue.setTrace( true );
// BTPiece p = (BTPiece)message;
// TimeFormatter.milliTrace( "obt sent: " + p.getPieceNumber() + "/" + p.getPieceOffset());
}
*/
doReadAheadLoads();
}
}
public void messageQueued( Message message ) {/*nothing*/}
public void messageRemoved( Message message ) {/*nothing*/}
public void protocolBytesSent( int byte_count ) {/*ignore*/}
public void dataBytesSent( int byte_count ) {/*ignore*/}
public void flush(){}
};
/**
* Register a new piece data request.
* @param piece_number
* @param piece_offset
* @param length
*/
public boolean addPieceRequest( int piece_number, int piece_offset, int length ) {
if( destroyed ) return( false );
DiskManagerReadRequest dmr = peer.getManager().getDiskManager().createReadRequest( piece_number, piece_offset, length );
try{
lock_mon.enter();
requests.addLast( dmr );
}finally{
lock_mon.exit();
}
doReadAheadLoads();
return( true );
}
/**
* Remove an outstanding piece data request.
* @param piece_number
* @param piece_offset
* @param length
*/
public void removePieceRequest( int piece_number, int piece_offset, int length ) {
if( destroyed ) return;
DiskManagerReadRequest dmr = peer.getManager().getDiskManager().createReadRequest( piece_number, piece_offset, length );
boolean inform_rejected = false;
try{
lock_mon.enter();
if( requests.contains( dmr ) ) {
requests.remove( dmr );
inform_rejected = true;
return;
}
if( loading_messages.contains( dmr ) ) {
loading_messages.remove( dmr );
inform_rejected = true;
return;
}
for( Iterator i = queued_messages.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
if( entry.getValue().equals( dmr ) ) { //it's already been queued
BTPiece msg = (BTPiece)entry.getKey();
if( outgoing_message_queue.removeMessage( msg, true ) ) {
inform_rejected = true;
i.remove();
}
break; //do manual listener notify
}
}
}finally{
lock_mon.exit();
if ( inform_rejected ){
peer.sendRejectRequest( dmr );
}
}
outgoing_message_queue.doListenerNotifications();
}
/**
* Remove all outstanding piece data requests.
*/
public void removeAllPieceRequests() {
if( destroyed ) return;
List<DiskManagerReadRequest> removed = new ArrayList<DiskManagerReadRequest>();
try{
lock_mon.enter();
// removed this trace as Alon can't remember why the trace is here anyway and as far as I can
// see there's nothing to stop a piece being delivered to transport and removed from
// the message queue before we're notified of this and thus it is entirely possible that
// our view of queued messages is lagging.
// String before_trace = outgoing_message_queue.getQueueTrace();
/*
int num_queued = queued_messages.size();
int num_removed = 0;
for( Iterator i = queued_messages.keySet().iterator(); i.hasNext(); ) {
BTPiece msg = (BTPiece)i.next();
if( outgoing_message_queue.removeMessage( msg, true ) ) {
i.remove();
num_removed++;
}
}
if( num_removed < num_queued -2 ) {
Debug.out( "num_removed[" +num_removed+ "] < num_queued[" +num_queued+ "]:\nBEFORE:\n" +before_trace+ "\nAFTER:\n" +outgoing_message_queue.getQueueTrace() );
}
*/
for( Iterator<BTPiece> i = queued_messages.keySet().iterator(); i.hasNext(); ) {
BTPiece msg = i.next();
if (outgoing_message_queue.removeMessage( msg, true )){
removed.add( queued_messages.get( msg ));
}
}
queued_messages.clear(); // this replaces stuff above
removed.addAll( requests );
requests.clear();
removed.addAll( loading_messages );
loading_messages.clear();
}
finally{
lock_mon.exit();
}
for ( DiskManagerReadRequest request: removed ){
peer.sendRejectRequest( request );
}
outgoing_message_queue.doListenerNotifications();
}
public void setRequestReadAhead( int num_to_read_ahead ) {
request_read_ahead = num_to_read_ahead;
}
public void destroy() {
try{
lock_mon.enter();
removeAllPieceRequests();
queued_messages.clear();
destroyed = true;
outgoing_message_queue.cancelQueueListener(sent_message_listener);
}
finally{
lock_mon.exit();
}
}
private void doReadAheadLoads() {
List to_submit = null;
try{
lock_mon.enter();
while( loading_messages.size() + queued_messages.size() < request_read_ahead && !requests.isEmpty() && !destroyed ) {
DiskManagerReadRequest dmr = (DiskManagerReadRequest)requests.removeFirst();
loading_messages.add( dmr );
if( to_submit == null ) to_submit = new ArrayList();
to_submit.add( dmr );
}
}finally{
lock_mon.exit();
}
/*
if ( peer.getIp().equals( "64.71.5.2")){
TimeFormatter.milliTrace( "obt read_ahead: -> " + (to_submit==null?0:to_submit.size()) +
" [lo=" + loading_messages.size() + ",qm=" + queued_messages.size() + ",re=" + requests.size() + ",rl=" + request_read_ahead + "]");
}
*/
if ( to_submit != null ){
for (int i=0;i<to_submit.size();i++){
peer.getManager().getAdapter().enqueueReadRequest( peer, (DiskManagerReadRequest)to_submit.get(i), read_req_listener );
}
}
}
/**
* Get a list of piece numbers being requested
*
* @return list of Long values
*/
public int[] getRequestedPieceNumbers() {
if( destroyed ) return new int[0];
/** Cheap hack to reduce (but not remove all) the # of duplicate entries */
int iLastNumber = -1;
int pos = 0;
int[] pieceNumbers;
try {
lock_mon.enter();
// allocate max size needed (we'll shrink it later)
pieceNumbers = new int[queued_messages.size() + loading_messages.size() + requests.size()];
for (Iterator iter = queued_messages.keySet().iterator(); iter.hasNext();) {
BTPiece msg = (BTPiece) iter.next();
if (iLastNumber != msg.getPieceNumber()) {
iLastNumber = msg.getPieceNumber();
pieceNumbers[pos++] = iLastNumber;
}
}
for (Iterator iter = loading_messages.iterator(); iter.hasNext();) {
DiskManagerReadRequest dmr = (DiskManagerReadRequest) iter.next();
if (iLastNumber != dmr.getPieceNumber()) {
iLastNumber = dmr.getPieceNumber();
pieceNumbers[pos++] = iLastNumber;
}
}
for (Iterator iter = requests.iterator(); iter.hasNext();) {
DiskManagerReadRequest dmr = (DiskManagerReadRequest) iter.next();
if (iLastNumber != dmr.getPieceNumber()) {
iLastNumber = dmr.getPieceNumber();
pieceNumbers[pos++] = iLastNumber;
}
}
} finally {
lock_mon.exit();
}
int[] trimmed = new int[pos];
System.arraycopy(pieceNumbers, 0, trimmed, 0, pos);
return trimmed;
}
public int
getRequestCount()
{
return( queued_messages.size() + loading_messages.size() + requests.size());
}
public boolean
isStalledPendingLoad()
{
return( queued_messages.size() == 0 && loading_messages.size() > 0 );
}
}