/** Receive a file. */ public void transfer(RepositoryChannel fileChannel, Role role, Mode mode, Allocator allocator) throws Exception { /* Initialise transfer parameters. */ _role = role; _bytesTransferred = 0; _blockLog = new BlockLog(this); _fileChannel = fileChannel; _allocator = allocator; _reservedSpace = 0; _spaceUsed = 0; _status = "None"; DigestThread digestThread = null; /* Startup the transfer. The transfer is performed on a single * thread, no matter the number of streams. * * Checksum computation is performed on a different * thread. The checksum computation thread is not allowed to * overtake the transfer thread, but we also ensure that the * checksum thread does not fall too far behind. This is to * increase the chance that data has not yet been evicted from * the cache. */ _multiplexer = new Multiplexer(this); try { _inProgress = true; digestThread = createDigestThread(); if (digestThread != null) { Object o = _cell.getDomainContext().get(READ_AHEAD_KEY); if (o != null && ((String) o).length() > 0) { try { digestThread.setReadAhead(Long.parseLong((String) o)); } catch (NumberFormatException e) { esay("Failed parsing read ahead: " + e.getMessage()); } } say("Initiated checksum computation thread"); digestThread.start(); } _multiplexer.add(mode); say("Entering event loop"); _multiplexer.loop(); } catch (ClosedByInterruptException e) { /* Many NIO operations throw a ClosedByInterruptException * rather than InterruptedException. We rethrow this as an * InterruptedException and clear the interrupt flag on * the thread. */ Thread.interrupted(); throw new InterruptedException(); } catch (InterruptedException | FTPException e) { throw e; } catch (Exception e) { esay(e); throw e; } finally { _inProgress = false; /* It is important that this is done before joining the * digest thread, since otherwise the digest thread would * not terminate. */ _blockLog.setEof(); /* Close all open channels. */ say("Left event loop and closing channels"); _multiplexer.close(); /* Wait for checksum computation to finish before * returning. Otherwise getActualChecksum() could * possibly return an incomplete checksum. * * REVISIT: If the mover gets killed here, we break out * with an InterruptedException. This is as such not a * major problem, since everything after this point is not * essential for clean up. It is however unfortunate that * the job gets killed because we wait for checksum * computation (in particular because the checksum * computation may be the cause of the timeout if it is * very slow). */ if (digestThread != null) { digestThread.join(); } /* Log some useful information about the transfer. */ long amount = getBytesTransferred(); long time = getTransferTime(); if (time > 0) { say( String.format( "Transfer finished: %d bytes transferred in %.2f seconds = %.3f MB/s", amount, time / 1000.0, (1000.0 * amount) / (time * 1024 * 1024))); } else { say(String.format("Transfer finished: %d bytes transferred in less than 1 ms", amount)); } } /* REVISIT: Error reporting from the digest thread is not * optimal. In case of errors, they are not detected until * here. It would be better if digestThread could shutdown the * multiplexer. Maybe we should simply embed the DigestThread * class into the Mover. */ if (digestThread != null && digestThread.getLastError() != null) { esay(digestThread.getLastError()); throw digestThread.getLastError(); } /* Check that we receive the whole file. */ if (!_blockLog.isComplete()) { throw new CacheException(44, "Incomplete file detected"); } }