All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tachyon.worker.nio.NIODataServer Maven / Gradle / Ivy

/*
 * Licensed to the University of California, Berkeley 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 tachyon.worker.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;

import tachyon.Constants;
import tachyon.Sessions;
import tachyon.conf.TachyonConf;
import tachyon.exception.BlockDoesNotExistException;
import tachyon.util.network.NetworkAddressUtils;
import tachyon.worker.DataServer;
import tachyon.worker.DataServerMessage;
import tachyon.worker.block.BlockDataManager;
import tachyon.worker.block.io.BlockReader;

/**
 * The Server to serve data file read requests from remote machines. The current implementation is
 * based on non-blocking NIO.
 */
public final class NIODataServer implements Runnable, DataServer {
  private static final Logger LOG = LoggerFactory.getLogger(Constants.LOGGER_TYPE);

  // The host:port combination to listen on
  private final InetSocketAddress mAddress;

  // The channel on which we will accept connections
  private ServerSocketChannel mServerChannel;

  // The selector we will be monitoring.
  private Selector mSelector;

  // Instance of TachyonConf
  private final TachyonConf mTachyonConf;

  private final Map mSendingData =
      Collections.synchronizedMap(new HashMap());
  private final Map mReceivingData =
      Collections.synchronizedMap(new HashMap());

  // The block data manager.
  private final BlockDataManager mDataManager;
  private final Thread mListenerThread;

  private volatile boolean mShutdown = false;
  private volatile boolean mShutdownComplete = false;

  /**
   * Creates a data server with direct access to worker storage.
   *
   * @param address the address of the data server
   * @param dataManager the lock system for lock blocks
   * @param tachyonConf Tachyon configuration
   */
  public NIODataServer(final InetSocketAddress address, final BlockDataManager dataManager,
      TachyonConf tachyonConf) {
    LOG.info("Starting DataServer @ " + address);
    mTachyonConf = Preconditions.checkNotNull(tachyonConf);
    NetworkAddressUtils.assertValidPort(Preconditions.checkNotNull(address), mTachyonConf);
    mAddress = address;
    mDataManager = Preconditions.checkNotNull(dataManager);
    try {
      mSelector = initSelector();
      mListenerThread = new Thread(this);
      mListenerThread.start();
    } catch (IOException e) {
      LOG.error(e.getMessage() + mAddress, e);
      throw Throwables.propagate(e);
    }
  }

  private void accept(SelectionKey key) throws IOException {
    // For an accept to be pending the channel must be a server socket channel
    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

    // Accept the connection and make it non-blocking
    SocketChannel socketChannel = serverSocketChannel.accept();
    socketChannel.configureBlocking(false);

    // Register the new SocketChannel with our Selector, indicating that we'd like to be notified
    // when there is data waiting to be read.
    socketChannel.register(mSelector, SelectionKey.OP_READ);
  }

  @Override
  public void close() throws IOException {
    mShutdown = true;
    mServerChannel.close();
    mSelector.close();
  }

  @Override
  public String getBindHost() {
    return mServerChannel.socket().getLocalSocketAddress().toString();
  }

  @Override
  public int getPort() {
    return mServerChannel.socket().getLocalPort();
  }

  @Override
  public boolean isClosed() {
    return mShutdownComplete;
  }

  private Selector initSelector() throws IOException {
    // Create a new selector
    Selector socketSelector = SelectorProvider.provider().openSelector();

    // Create a new non-blocking server socket channel
    try {
      mServerChannel = ServerSocketChannel.open();
      mServerChannel.configureBlocking(false);

      // Bind the server socket to the specified address and port
      mServerChannel.socket().bind(mAddress);

      // Register the server socket channel, indicating an interest in accepting new connections.
      mServerChannel.register(socketSelector, SelectionKey.OP_ACCEPT);

      return socketSelector;
    } catch (IOException e) {
      // we want to throw the original IO issue, not the close issue, so don't throw
      // #close IOException.
      try {
        socketSelector.close();
      } catch (IOException ex) {
        // ignore, we want the other exception
        LOG.warn("Unable to close socket selector", ex);
      }
      throw e;
    } catch (RuntimeException e) {
      // we want to throw the original IO issue, not the close issue, so don't throw
      // #close IOException.
      try {
        socketSelector.close();
      } catch (IOException ex) {
        // ignore, we want the other exception
        LOG.warn("Unable to close socket selector", ex);
      }
      throw e;
    }
  }

  private void read(SelectionKey key) throws Exception {
    SocketChannel socketChannel = (SocketChannel) key.channel();

    DataServerMessage tMessage;
    if (mReceivingData.containsKey(socketChannel)) {
      tMessage = mReceivingData.get(socketChannel);
    } else {
      tMessage = DataServerMessage.createBlockRequestMessage();
      mReceivingData.put(socketChannel, tMessage);
    }

    // Attempt to read off the channel
    int numRead;
    try {
      numRead = tMessage.recv(socketChannel);
    } catch (IOException e) {
      // The remote forcibly closed the connection, cancel the selection key and close the channel.
      key.cancel();
      socketChannel.close();
      mReceivingData.remove(socketChannel);
      mSendingData.remove(socketChannel);
      return;
    }

    if (numRead == -1) {
      // Remote entity shut the socket down cleanly. Do the same from our end and cancel the
      // channel.
      key.channel().close();
      key.cancel();
      mReceivingData.remove(socketChannel);
      mSendingData.remove(socketChannel);
      return;
    }

    if (tMessage.isMessageReady()) {
      if (tMessage.getBlockId() <= 0) {
        LOG.error("Invalid block id " + tMessage.getBlockId());
        return;
      }

      key.interestOps(SelectionKey.OP_WRITE);
      final long blockId = tMessage.getBlockId();
      LOG.info("Get request for blockId: {}", blockId);

      long lockId = mDataManager.lockBlock(Sessions.DATASERVER_SESSION_ID, blockId);
      BlockReader reader =
          mDataManager.readBlockRemote(Sessions.DATASERVER_SESSION_ID, blockId, lockId);
      ByteBuffer data;
      int dataLen = 0;
      try {
        data = reader.read(tMessage.getOffset(), tMessage.getLength());
        mDataManager.accessBlock(Sessions.DATASERVER_SESSION_ID, blockId);
        dataLen = data.limit();
      } catch (Exception e) {
        LOG.error(e.getMessage(), e);
        data = null;
      } finally {
        reader.close();
      }
      DataServerMessage tResponseMessage = DataServerMessage.createBlockResponseMessage(true,
          blockId, tMessage.getOffset(), dataLen, data);
      tResponseMessage.setLockId(lockId);
      mSendingData.put(socketChannel, tResponseMessage);
    }
  }

  @Override
  public void run() {
    while (!mShutdown) {
      try {
        // Wait for an event one of the registered channels.
        mSelector.select();
        if (mShutdown) {
          break;
        }

        // Iterate over the set of keys for which events are available
        Iterator selectKeys = mSelector.selectedKeys().iterator();
        while (selectKeys.hasNext()) {
          SelectionKey key = selectKeys.next();
          selectKeys.remove();

          if (!key.isValid()) {
            continue;
          }

          // Check what event is available and deal with it.
          // TODO(dcapwell): These should be multi-thread.
          if (key.isAcceptable()) {
            accept(key);
          } else if (key.isReadable()) {
            read(key);
          } else if (key.isWritable()) {
            write(key);
          }
        }
      } catch (Exception e) {
        LOG.error(e.getMessage(), e);
        if (mShutdown) {
          break;
        }
        // Close server before exiting loop.
        try {
          close();
        } catch (Exception e2) {
          LOG.error("Exception when closing data server. message: " + e2.getMessage());
        }
        // Mark the server as shut down.
        mShutdownComplete = true;
        throw new RuntimeException(e);
      }
    }
    mShutdownComplete = true;
  }

  private void write(SelectionKey key) {
    SocketChannel socketChannel = (SocketChannel) key.channel();

    DataServerMessage sendMessage = mSendingData.get(socketChannel);

    boolean closeChannel = false;
    try {
      sendMessage.send(socketChannel);
    } catch (IOException e) {
      closeChannel = true;
      LOG.error(e.getMessage());
    }

    if (sendMessage.finishSending() || closeChannel) {
      try {
        key.channel().close();
      } catch (IOException e) {
        LOG.error(e.getMessage());
      }
      key.cancel();
      mReceivingData.remove(socketChannel);
      mSendingData.remove(socketChannel);
      sendMessage.close();
      // TODO(calvin): Reconsider how we handle this exception.
      try {
        mDataManager.unlockBlock(sendMessage.getLockId());
      } catch (BlockDoesNotExistException ioe) {
        LOG.error("Failed to unlock block: " + sendMessage.getBlockId(), ioe);
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy