/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cassandra.streaming; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.FileChannel; import java.nio.channels.SocketChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.utils.Pair; public class IncomingStreamReader { private static final Logger logger = LoggerFactory.getLogger(IncomingStreamReader.class); private final PendingFile localFile; private final PendingFile remoteFile; private final SocketChannel socketChannel; private final StreamInSession session; public IncomingStreamReader(StreamHeader header, SocketChannel socketChannel) throws IOException { this.socketChannel = socketChannel; InetSocketAddress remoteAddress = (InetSocketAddress)socketChannel.socket().getRemoteSocketAddress(); session = StreamInSession.get(remoteAddress.getAddress(), header.sessionId); session.addFiles(header.pendingFiles); // set the current file we are streaming so progress shows up in jmx session.setCurrentFile(header.file); session.setTable(header.table); // pendingFile gets the new context for the local node. remoteFile = header.file; localFile = remoteFile != null ? StreamIn.getContextMapping(remoteFile) : null; } public void read() throws IOException { if (remoteFile != null) readFile(); session.closeIfFinished(); } private void readFile() throws IOException { if (logger.isDebugEnabled()) { logger.debug("Receiving stream"); logger.debug("Creating file for {}", localFile.getFilename()); } FileOutputStream fos = new FileOutputStream(localFile.getFilename(), true); FileChannel fc = fos.getChannel(); long offset = 0; try { for (Pair<Long, Long> section : localFile.sections) { long length = section.right - section.left; long bytesRead = 0; while (bytesRead < length) { long toRead = Math.min(FileStreamTask.CHUNK_SIZE, length - bytesRead); long lastRead = fc.transferFrom(socketChannel, offset + bytesRead, toRead); // if the other side fails, we will not get an exception, but instead transferFrom will constantly return 0 byte read // and we would thus enter an infinite loop. So intead, if no bytes are tranferred we assume the other side is dead and // raise an exception (that will be catch belove and 'the right thing' will be done). if (lastRead == 0) throw new IOException("Transfer failed for remote file " + remoteFile); bytesRead += lastRead; remoteFile.progress += lastRead; } offset += length; } } catch (IOException ex) { /* Ask the source node to re-stream this file. */ session.retry(remoteFile); /* Delete the orphaned file. */ FileUtils.deleteWithConfirm(new File(localFile.getFilename())); throw ex; } finally { fc.close(); } session.finished(remoteFile, localFile); } }