/* * Created on 31-Jul-2004 * Created by Paul Gardner * 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 org.gudy.azureus2.core3.disk.impl.access.impl; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.gudy.azureus2.core3.disk.*; import org.gudy.azureus2.core3.disk.impl.DiskManagerHelper; import org.gudy.azureus2.core3.disk.impl.access.*; import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList; import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapEntry; import org.gudy.azureus2.core3.logging.*; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.diskmanager.access.DiskAccessController; import com.aelitis.azureus.core.diskmanager.access.DiskAccessRequest; import com.aelitis.azureus.core.diskmanager.access.DiskAccessRequestListener; import com.aelitis.azureus.core.diskmanager.cache.*; /** * @author parg * */ public class DMReaderImpl implements DMReader { private static final LogIDs LOGID = LogIDs.DISK; private DiskManagerHelper disk_manager; private DiskAccessController disk_access; private int async_reads; private Set read_requests = new HashSet(); private AESemaphore async_read_sem = new AESemaphore("DMReader:asyncReads"); private boolean started; private boolean stopped; private long total_read_ops; private long total_read_bytes; protected AEMonitor this_mon = new AEMonitor( "DMReader" ); public DMReaderImpl( DiskManagerHelper _disk_manager ) { disk_manager = _disk_manager; disk_access = disk_manager.getDiskAccessController(); } public void start() { try{ this_mon.enter(); if ( started ){ throw( new RuntimeException("can't start twice" )); } if ( stopped ){ throw( new RuntimeException("already been stopped" )); } started = true; }finally{ this_mon.exit(); } } public void stop() { int read_wait; try{ this_mon.enter(); if ( stopped || !started ){ return; } stopped = true; read_wait = async_reads; }finally{ this_mon.exit(); } long log_time = SystemTime.getCurrentTime(); for (int i=0;i<read_wait;i++){ long now = SystemTime.getCurrentTime(); if ( now < log_time ){ log_time = now; }else{ if ( now - log_time > 1000 ){ log_time = now; if ( Logger.isEnabled()){ Logger.log(new LogEvent(disk_manager, LOGID, "Waiting for reads to complete - " + (read_wait-i) + " remaining" )); } } } async_read_sem.reserve(); } } public DiskManagerReadRequest createReadRequest( int pieceNumber, int offset, int length ) { return( new DiskManagerReadRequestImpl( pieceNumber, offset, length )); } public boolean hasOutstandingReadRequestForPiece( int piece_number ) { try{ this_mon.enter(); Iterator it = read_requests.iterator(); while( it.hasNext()){ DiskManagerReadRequest request = (DiskManagerReadRequest)((Object[])it.next())[0]; if ( request.getPieceNumber() == piece_number ){ return( true ); } } return( false ); }finally{ this_mon.exit(); } } public long[] getStats() { return( new long[]{ total_read_ops, total_read_bytes }); } // returns null if the read can't be performed public DirectByteBuffer readBlock( int pieceNumber, int offset, int length ) { DiskManagerReadRequest request = createReadRequest( pieceNumber, offset, length ); final AESemaphore sem = new AESemaphore( "DMReader:readBlock" ); final DirectByteBuffer[] result = {null}; readBlock( request, new DiskManagerReadRequestListener() { public void readCompleted( DiskManagerReadRequest request, DirectByteBuffer data ) { result[0] = data; sem.release(); } public void readFailed( DiskManagerReadRequest request, Throwable cause ) { sem.release(); } public int getPriority() { return( -1 ); } public void requestExecuted(long bytes) { } }); sem.reserve(); return( result[0] ); } public void readBlock( final DiskManagerReadRequest request, final DiskManagerReadRequestListener _listener ) { request.requestStarts(); final DiskManagerReadRequestListener listener = new DiskManagerReadRequestListener() { public void readCompleted( DiskManagerReadRequest request, DirectByteBuffer data ) { request.requestEnds( true ); _listener.readCompleted( request, data ); } public void readFailed( DiskManagerReadRequest request, Throwable cause ) { request.requestEnds( false ); _listener.readFailed( request, cause ); } public int getPriority() { return( _listener.getPriority()); } public void requestExecuted(long bytes) { _listener.requestExecuted( bytes ); } }; DirectByteBuffer buffer = null; try{ int length = request.getLength(); buffer = DirectByteBufferPool.getBuffer( DirectByteBuffer.AL_DM_READ,length ); if ( buffer == null ) { // Fix for bug #804874 Debug.out("DiskManager::readBlock:: ByteBufferPool returned null buffer"); listener.readFailed( request, new Exception( "Out of memory" )); return; } int pieceNumber = request.getPieceNumber(); int offset = request.getOffset(); DMPieceList pieceList = disk_manager.getPieceList(pieceNumber); // temporary fix for bug 784306 if ( pieceList.size() == 0 ){ Debug.out("no pieceList entries for " + pieceNumber); listener.readCompleted( request, buffer ); return; } long previousFilesLength = 0; int currentFile = 0; long fileOffset = pieceList.get(0).getOffset(); while (currentFile < pieceList.size() && pieceList.getCumulativeLengthToPiece(currentFile) < offset) { previousFilesLength = pieceList.getCumulativeLengthToPiece(currentFile); currentFile++; fileOffset = 0; } // update the offset (we're in the middle of a file) fileOffset += offset - previousFilesLength; List chunks = new ArrayList(); int buffer_position = 0; while ( buffer_position < length && currentFile < pieceList.size()) { DMPieceMapEntry map_entry = pieceList.get( currentFile ); int length_available = map_entry.getLength() - (int)( fileOffset - map_entry.getOffset()); //explicitly limit the read size to the proper length, rather than relying on the underlying file being correctly-sized //see long DMWriterAndCheckerImpl::checkPiece note int entry_read_limit = buffer_position + length_available; // now bring down to the required read length if this is shorter than this // chunk of data entry_read_limit = Math.min( length, entry_read_limit ); // this chunk denotes a read up to buffer offset "entry_read_limit" chunks.add( new Object[]{ map_entry.getFile().getCacheFile(), new Long(fileOffset), new Integer( entry_read_limit )}); buffer_position = entry_read_limit; currentFile++; fileOffset = 0; } if ( chunks.size() == 0 ){ Debug.out("no chunk reads for " + pieceNumber); listener.readCompleted( request, buffer ); return; } // this is where we go async and need to start counting requests for the sake // of shutting down tidily // have to wrap the request as we can validly have >1 for same piece/offset/length and // the request type itself overrides object equiv based on this... final Object[] request_wrapper = { request }; DiskManagerReadRequestListener l = new DiskManagerReadRequestListener() { public void readCompleted( DiskManagerReadRequest request, DirectByteBuffer data ) { complete(); listener.readCompleted( request, data ); } public void readFailed( DiskManagerReadRequest request, Throwable cause ) { complete(); listener.readFailed( request, cause ); } public int getPriority() { return( _listener.getPriority()); } public void requestExecuted(long bytes) { _listener.requestExecuted( bytes ); } protected void complete() { try{ this_mon.enter(); async_reads--; if ( !read_requests.remove( request_wrapper )){ Debug.out( "request not found" ); } if ( stopped ){ async_read_sem.release(); } }finally{ this_mon.exit(); } } }; try{ this_mon.enter(); if ( stopped ){ buffer.returnToPool(); listener.readFailed( request, new Exception( "Disk reader has been stopped" )); return; } async_reads++; read_requests.add( request_wrapper ); }finally{ this_mon.exit(); } new requestDispatcher( request, l, buffer, chunks ); }catch( Throwable e ){ if ( buffer != null ){ buffer.returnToPool(); } disk_manager.setFailed( "Disk read error - " + Debug.getNestedExceptionMessage(e)); Debug.printStackTrace( e ); listener.readFailed( request, e ); } } protected class requestDispatcher implements DiskAccessRequestListener { private DiskManagerReadRequest dm_request; private DiskManagerReadRequestListener listener; private DirectByteBuffer buffer; private List chunks; private int buffer_length; private int chunk_index; private int chunk_limit; protected requestDispatcher( DiskManagerReadRequest _request, DiskManagerReadRequestListener _listener, DirectByteBuffer _buffer, List _chunks ) { dm_request = _request; listener = _listener; buffer = _buffer; chunks = _chunks; /* String str = "Read: " + dm_request.getPieceNumber()+"/"+dm_request.getOffset()+"/"+dm_request.getLength()+":"; for (int i=0;i<chunks.size();i++){ Object[] entry = (Object[])chunks.get(i); String str2 = entry[0] + "/" + entry[1] +"/" + entry[2]; str += (i==0?"":",") + str2; } System.out.println( str ); */ buffer_length = buffer.limit( DirectByteBuffer.SS_DR ); dispatch(); } protected void dispatch() { try{ if ( chunk_index == chunks.size()){ buffer.limit( DirectByteBuffer.SS_DR, buffer_length ); buffer.position( DirectByteBuffer.SS_DR, 0 ); listener.readCompleted( dm_request, buffer ); }else{ if ( chunk_index == 1 && chunks.size() > 32 ){ // for large numbers of chunks drop the recursion approach and // do it linearly (but on the async thread) for (int i=1;i<chunks.size();i++){ final AESemaphore sem = new AESemaphore( "DMR:dispatch:asyncReq" ); final Throwable[] error = {null}; doRequest( new DiskAccessRequestListener() { public void requestComplete( DiskAccessRequest request ) { sem.release(); } public void requestCancelled( DiskAccessRequest request ) { Debug.out( "shouldn't get here" ); } public void requestFailed( DiskAccessRequest request, Throwable cause ) { error[0] = cause; sem.release(); } public int getPriority() { return( listener.getPriority()); } public void requestExecuted(long bytes) { if ( bytes > 0 ){ total_read_bytes += bytes; total_read_ops ++; } listener.requestExecuted( bytes ); } }); sem.reserve(); if ( error[0] != null ){ throw( error[0] ); } } buffer.limit( DirectByteBuffer.SS_DR, buffer_length ); buffer.position( DirectByteBuffer.SS_DR, 0 ); listener.readCompleted( dm_request, buffer ); }else{ doRequest( this ); } } }catch( Throwable e ){ failed( e ); } } protected void doRequest( DiskAccessRequestListener l ) { Object[] stuff = (Object[])chunks.get( chunk_index++ ); if ( chunk_index > 0 ){ buffer.position( DirectByteBuffer.SS_DR, chunk_limit ); } chunk_limit = ((Integer)stuff[2]).intValue(); buffer.limit( DirectByteBuffer.SS_DR, chunk_limit ); short cache_policy = dm_request.getUseCache()?CacheFile.CP_READ_CACHE:CacheFile.CP_NONE; if ( dm_request.getFlush()){ cache_policy |= CacheFile.CP_FLUSH; } disk_access.queueReadRequest( (CacheFile)stuff[0], ((Long)stuff[1]).longValue(), buffer, cache_policy, l ); } public void requestComplete( DiskAccessRequest request ) { dispatch(); } public void requestCancelled( DiskAccessRequest request ) { // we never cancel so nothing to do here Debug.out( "shouldn't get here" ); } public void requestFailed( DiskAccessRequest request, Throwable cause ) { failed( cause ); } public int getPriority() { return( listener.getPriority()); } public void requestExecuted(long bytes) { if ( bytes > 0 ){ total_read_bytes += bytes; total_read_ops ++; } listener.requestExecuted( bytes ); } protected void failed( Throwable cause ) { buffer.returnToPool(); disk_manager.setFailed( "Disk read error - " + Debug.getNestedExceptionMessage(cause)); Debug.printStackTrace( cause ); listener.readFailed( dm_request, cause ); } } }