/** * Verify that the protocols are compatible, switch to a different protocol version, if we need * to. */ private Protocol checkProtocol(Protocol protocol) throws IOException, ProtocolException { ClientVersion clientVersion = protocol.read(namedChannel.getChannel(), Protocol.ClientVersion.class); clientId = clientVersion.getNodeId(); FeederManager.Lease lease = feederManager.leases.get(clientId); if (lease != null) { dbBackup = lease.terminate(); } feederManager.feeders.put(clientId, this); if (clientVersion.getVersion() != protocol.getVersion()) { String message = "Client requested protocol version: " + clientVersion.getVersion() + " but the server version is " + protocol.getVersion(); /* * Simply log the difference on the server, it's up to the client * to reject the protocol version, if it can't accommodate it. */ LoggerUtils.warning(logger, feederManager.getEnvImpl(), message); } protocol.write(protocol.new ServerVersion(), namedChannel); /* In future we may switch protocol versions to accommodate the client. * For now, simply return the one and only version. */ return protocol; }
/** Sets up the channel to facilitate efficient transfer of large log files. */ private SocketChannel configureChannel() throws IOException { namedChannel.getChannel().configureBlocking(true); LoggerUtils.fine( logger, feederManager.getEnvImpl(), "Log File Feeder accepted connection from " + namedChannel); namedChannel.getChannel().socket().setSoTimeout(SOCKET_TIMEOUT_MS); /* * Enable Nagle's algorithm since throughput is important for the large * files we will be transferring. */ namedChannel.getChannel().socket().setTcpNoDelay(false); return namedChannel.getChannel(); }
/** * Processes the request for the list of files that constitute a valid backup. If a leased * DbBackup instance is available, it uses it, otherwise it creates a new instance and uses it * instead. */ private void sendFileList(Protocol protocol) throws IOException, ProtocolException, DatabaseException { /* Wait for the request message. */ protocol.read(namedChannel.getChannel(), Protocol.FileListReq.class); if (dbBackup == null) { dbBackup = new DbBackup(feederManager.getEnvImpl()); dbBackup.startBackup(); } else { feederManager.leaseRenewalCount++; } /* * Remove the subdirectory header of the log files, because the nodes * that need to copy those log files may not configure the spreading * log files into sub directories feature. */ String[] files = dbBackup.getLogFilesInBackupSet(); for (int i = 0; i < files.length; i++) { if (files[i].contains(File.separator)) { files[i] = files[i].substring(files[i].indexOf(File.separator) + 1, files[i].length()); } } protocol.write(protocol.new FileListResp(files), namedChannel); }
/** The main driver loop that enforces the protocol message sequence and implements it. */ @Override public void run() { /* The initial protocol */ Protocol protocol = new Protocol(feederManager.nameIdPair, Protocol.VERSION, feederManager.getEnvImpl()); try { configureChannel(); protocol = checkProtocol(protocol); checkFeeder(protocol); sendFileList(protocol); sendRequestedFiles(protocol); /* Done, cleanup */ dbBackup.endBackup(); dbBackup = null; } catch (ClosedByInterruptException e) { LoggerUtils.fine( logger, feederManager.getEnvImpl(), "Ignoring ClosedByInterruptException normal shutdown"); } catch (IOException e) { LoggerUtils.warning(logger, feederManager.getEnvImpl(), " IO Exception: " + e.getMessage()); } catch (ProtocolException e) { LoggerUtils.severe( logger, feederManager.getEnvImpl(), " Protocol Exception: " + e.getMessage()); } catch (Exception e) { throw new EnvironmentFailureException( feederManager.getEnvImpl(), EnvironmentFailureReason.UNCAUGHT_EXCEPTION, e); } finally { try { namedChannel.getChannel().close(); } catch (IOException e) { LoggerUtils.warning( logger, feederManager.getEnvImpl(), "Log File feeder io exception on " + "channel close: " + e.getMessage()); } shutdown(); if (dbBackup != null) { if (feederManager.shutdown.get()) { dbBackup.endBackup(); } else { /* * Establish lease so client can resume within the lease * period. */ feederManager.new Lease(clientId, feederManager.leaseDuration, dbBackup); LoggerUtils.info( logger, feederManager.getEnvImpl(), "Lease created for node: " + clientId); } } LoggerUtils.info( logger, feederManager.getEnvImpl(), "Log file feeder for client: " + clientId + " exited"); } }
/** * Sends over the contents of the file and computes the SHA-1 hash. Note that the method does not * rely on EOF detection, but rather on the promised file size, since the final log file might be * growing while the transfer is in progress. The client uses the length sent in the FileResp * message to maintain its position in the network stream. It expects to see a StatResp once it * has read the agreed upon number of bytes. * * <p>Since JE log files are append only, there is no danger that we will send over any * uninitialized file blocks. * * @param file the log file to be sent. * @param the number of bytes to send * @return the digest associated with the file that was sent * @throws IOException */ private byte[] sendFileContents(File file, long length) throws IOException { final LogVerifier verifier = new LogVerifier(feederManager.getEnvImpl(), file.getName(), -1L); final FileInputStream fileStream = new FileInputStream(file); try { final FileChannel fileChannel = fileStream.getChannel(); messageDigest.reset(); final ByteBuffer buffer = ByteBuffer.allocateDirect(TRANSFER_BYTES); final byte[] array = (buffer.hasArray()) ? buffer.array() : new byte[TRANSFER_BYTES]; int transmitBytes = 0; while (true) { buffer.clear(); if (fileChannel.read(buffer) < 0) { verifier.verifyAtEof(); break; } buffer.flip(); final int lim = buffer.limit(); final int off; if (buffer.hasArray()) { off = buffer.arrayOffset(); } else { off = 0; buffer.get(array, 0, lim); buffer.rewind(); } verifier.verify(array, off, lim); messageDigest.update(array, off, lim); transmitBytes += namedChannel.getChannel().write(buffer); } if (transmitBytes != length) { String msg = "File length:" + length + " does not match the " + "number of bytes that were transmitted:" + transmitBytes; throw new IllegalStateException(msg); } LoggerUtils.info( logger, feederManager.getEnvImpl(), "Sent file: " + file + " Length: " + length + " bytes"); } finally { fileStream.close(); } return messageDigest.digest(); }
/** * Implements the message exchange used to determine whether this feeder is suitable for use the * client's backup needs. The feeder may be unsuitable if it's already busy, or it's not current * enough to service the client's needs. */ private void checkFeeder(Protocol protocol) throws IOException, DatabaseException { protocol.read(namedChannel.getChannel(), FeederInfoReq.class); int feeders = feederManager.getActiveFeederCount() - 1 /* Exclude this one */; VLSN rangeFirst = VLSN.NULL_VLSN; VLSN rangeLast = VLSN.NULL_VLSN; if (feederManager.getEnvImpl() instanceof RepImpl) { /* Include replication stream feeders as a load component. */ RepImpl repImpl = (RepImpl) feederManager.getEnvImpl(); feeders += repImpl.getRepNode().feederManager().activeReplicaCount(); VLSNRange range = repImpl.getVLSNIndex().getRange(); rangeFirst = range.getFirst(); rangeLast = range.getLast(); } protocol.write(protocol.new FeederInfoResp(feeders, rangeFirst, rangeLast), namedChannel); }
/** * Send files in response to request messages. The request sequence looks like the following: * * <p>[FileReq | StatReq]+ Done * * <p>The response sequence to a FileReq looks like: * * <p>FileStart <file byte stream> FileEnd * * <p>and that for a StatReq, is simply a StatResp */ private void sendRequestedFiles(Protocol protocol) throws IOException, ProtocolException, DatabaseException { try { while (true) { FileReq fileReq = protocol.read(namedChannel.getChannel(), FileReq.class); final String fileName = fileReq.getFileName(); /* * Calculate the full path for a specified log file name, * especially when this Feeder is configured to run with sub * directories. */ FileManager fMgr = feederManager.getEnvImpl().getFileManager(); File file = new File(fMgr.getFullFileName(fileName)); if (!file.exists()) { throw EnvironmentFailureException.unexpectedState("Log file not found: " + fileName); } /* Freeze the length and last modified date. */ final long length = file.length(); final long lastModified = file.lastModified(); byte digest[] = null; FileInfoResp resp = null; Protocol.FileInfoResp cachedResp = feederManager.statResponses.get(fileName); byte cachedDigest[] = ((cachedResp != null) && (cachedResp.getFileLength() == length) && (cachedResp.getLastModifiedTime() == lastModified)) ? cachedResp.getDigestSHA1() : null; if (fileReq instanceof FileInfoReq) { if (cachedDigest != null) { digest = cachedDigest; } else if (((FileInfoReq) fileReq).getNeedSHA1()) { digest = getSHA1Digest(file, length).digest(); } else { // Digest not requested digest = new byte[0]; } resp = protocol.new FileInfoResp(fileName, length, lastModified, digest); } else { protocol.write(protocol.new FileStart(fileName, length, lastModified), namedChannel); digest = sendFileContents(file, length); if ((cachedDigest != null) && !Arrays.equals(cachedDigest, digest)) { throw EnvironmentFailureException.unexpectedState( "Inconsistent cached and computed digests"); } resp = protocol.new FileEnd(fileName, length, lastModified, digest); } /* Cache for subsequent requests, if it was computed. */ if (digest.length > 0) { feederManager.statResponses.put(fileName, resp); } protocol.write(resp, namedChannel); } } catch (ProtocolException pe) { if (pe.getUnexpectedMessage() instanceof Protocol.Done) { return; } throw pe; } }