Package org.apache.hadoop.hdfs.notifier

Source Code of org.apache.hadoop.hdfs.notifier.ConnectionManager$ServerTracker

/**
* 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.hadoop.hdfs.notifier;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hdfs.notifier.NamespaceNotifierClient.NotConnectedToServerException;
import org.apache.hadoop.hdfs.notifier.NamespaceNotifierClient.ServerAlreadyKnownException;
import org.apache.hadoop.hdfs.notifier.NamespaceNotifierClient.ServerNotKnownException;
import org.apache.hadoop.hdfs.notifier.EventType;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.apache.thrift.transport.TTransportFactory;

import com.google.common.collect.Maps;

/**
* Used by the client to subscribe to the namespace notifier server.
*/
public class NamespaceNotifierClient implements Runnable {
  public static final Log LOG = LogFactory.getLog(NamespaceNotifierClient.class);
 
  // The object that will get our callbacks
  Watcher watcher;
 
  String listeningHost;
  int listeningPort;

  // All the watches that this client has placed.
  // Mapping event to the last transaction id received
  ConcurrentMap<NamespaceEventKey, Long> watchedEvents;
 
  // When we should shutdown
  volatile boolean shouldShutdown = false;
 
  // The thrift handler implementation
  ClientHandler.Iface handler;
 
  TServer tserver;
 
  ConnectionManager connectionManager;
 
  Random generator;

 
  /**
   * Constructor used when the Namespace Notification Server is running just
   * on one machine.
   *
   * @param watcher The notified component
   * @param host The notification server hostname or IP address
   * @param port The notification server listening port
   * @param listeningPort the port on which this client should start the
   *        thrift service.
   * @throws TException when failing to connect to the server.
   */
  public NamespaceNotifierClient(Watcher watcher,
      String host, int port, int listeningPort) throws TException {
    this(watcher, Arrays.asList(host), port, listeningPort);
  }


  /**
   * Constructor used when the Namespace Notification server is running on
   * multiple machines, but on all machines it's listening on the same port
   * number.
   *
   * @param watcher The notified component
   * @param hosts The notification servers hostnames or IP addresses
   * @param port The notification servers listening port
   * @param listeningPort the port on which this client should start the
   *        thrift service.
   * @throws TException when failing to connect to the server.
   */
  public NamespaceNotifierClient(Watcher watcher,
      List<String> hosts, int port, int listeningPort)
          throws TException {
    this(watcher, hosts, Arrays.asList(port), listeningPort);
  }


  /**
   * Constructor used when the Namespace Notification server is running on
   * multiple machines and it's not listening on the same port number on all
   * machines.
   *
   * @param watcher The notified component
   * @param hosts The notification servers hostnames or IP addresses
   * @param ports The notification servers listening ports
   * @param listeningPort the port on which this client should start the
   *        thrift service.
   * @throws TException when failing to connect to the server.
   */
  public NamespaceNotifierClient(Watcher watcher,
      List<String> hosts, List<Integer> ports, int listeningPort)
          throws TException {
    this.watcher = watcher;
    watchedEvents = new ConcurrentHashMap<NamespaceEventKey, Long>();
   
    try {
       listeningHost = InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e) {
      throw new TException(e);
    }
    this.listeningPort = listeningPort;
    handler = new ClientHandlerImpl(this);
   
    // Ensure pseudo-random seed between clients
    long seed = System.currentTimeMillis() + listeningPort +
        listeningHost.hashCode();
    if (LOG.isDebugEnabled()) {
      LOG.debug(listeningPort + ": using seed " + seed);
    }
    generator = new Random(seed);
   
    // Setup the Thrift server
    TProtocolFactory protocolFactory = new TBinaryProtocol.Factory();
    TTransportFactory transportFactory = new TFramedTransport.Factory();
    TNonblockingServerTransport serverTransport;
    ClientHandler.Processor<ClientHandler.Iface> processor =
        new ClientHandler.Processor<ClientHandler.Iface>(handler);
    serverTransport = new TNonblockingServerSocket(listeningPort);
   
    TNonblockingServer.Args serverArgs =
        new TNonblockingServer.Args(serverTransport);
    serverArgs.processor(processor).transportFactory(transportFactory)
        .protocolFactory(protocolFactory);
    tserver = new TNonblockingServer(serverArgs);
   
    connectionManager = new ConnectionManager(hosts, ports, this);
    LOG.info(listeningPort + ": Successfully initialized namespace" +
        " notifier client");
  }
 
 
  /**
   * Called by the ConnectionManager when the connection state changed.
   * The connection lock is hold when calling this method, so no
   * other methods from the ConnectionManager should be called here.
   *
   * @param newState the new state
   * @return when the new state is DISCONNECTED_HIDDEN or DISCONNECTED_VISIBLE,
   *         then the return value is ignored. If the new state is CONNECTED,
   *         then the return value shows if the NamespaceNotifierClient accepts
   *         or not the new server. If it isn't accepted, the ConnectionManager
   *         will try connecting to another server.
   */
  boolean connectionStateChanged(int newState) {
    switch (newState) {
      case ConnectionManager.CONNECTED:
        LOG.info(listeningPort + ": Switched to CONNECTED state.");
        // Try to resubscribe all the watched events
        try {
          return resubscribe();
        } catch (Exception e) {
          LOG.error(listeningPort + ": Resubscribing failed", e);
          return false;
        }
      case ConnectionManager.DISCONNECTED_VISIBLE:
        LOG.info(listeningPort + ": Switched to DISCONNECTED_VISIBLE state");
        for (NamespaceEventKey eventKey : watchedEvents.keySet())
          watchedEvents.put(eventKey, -1L);
        watcher.connectionFailed();
        break;
      case ConnectionManager.DISCONNECTED_HIDDEN:
        LOG.info(listeningPort + ": Switched to DISCONNECTED_HIDDEN state.");
    }

    return true;
  }
 
 
  long getCurrentConnectionToken() {
    return connectionManager.getConnectionToken();
  }
 
 
  /**
   * Adds the specified server to the pool of known servers.
   * @param host
   * @param port
   * @throws ServerAlreadyKnownException if the server is already in the pool
   *         of known servers.
   */
  public void addServer(String host, int port)
      throws ServerAlreadyKnownException {
    connectionManager.addServer(host, port);
  }
 
 
  /**
   * Removes the specified server from the pool of known servers.
   * @param host
   * @param port
   * @throws ServerNotKnownException if the server isn't in the pool of
   *         known servers.
   */
  public void removeServer(String host, int port)
      throws ServerNotKnownException {
    connectionManager.removeServer(host, port);
  }
 
 
  /**
   * Sets the value of the timeout after which we will consider a server
   * failed.
   * @param timeout the value in milliseconds for the timeout after which
   *        we will consider the server failed. Defaults to 50000 (50 seconds).
   */
  public void setServerTimeout(long timeout) {
    connectionManager.setServerTimeout(timeout);
  }
 
 
  /**
   * Sets the value of the time between consecutive connect retries. The
   * connection is retried only after Watcher.connectionFailed was called.
   * @param retryTime the retry time in milliseconds.
   */
  public void setConnectRetryTime(long retryTime) {
    connectionManager.setConnectRetryTime(retryTime);
  }
 
 
  /**
   * The watcher (given in the constructor) will be notified when an
   * event of the given type and at the given path will happen. He will
   * keep receiving notifications until the watch is removed with
   * {@link #removeWatch(String, EventType)}.
   *
   * The subscription is considered done if the method doesn't throw an
   * exception (even if Watcher.connectionFailed is called before this
   * method returns).
   *
   * @param path the path where the watch is placed. For the FILE_ADDED event
   *        type, this represents the path of the directory under which the
   *        file will be created.
   * @param watchType the type of the event for which we want to receive the
   *        notifications.
   * @param transactionId the transaction id of the last received notification.
   *        Notifications will from and excluding the notification with this
   *        transaction id. If this is -1, then all notifications that
   *        happened after this method returns will be received and some
   *        of the notifications between the time the method was called
   *        and the time the method returns may be received.
   * @throws WatchAlreadyPlacedException if the watch already exists for this
   *         path and type.
   * @throws NotConnectedToServerException when the Watcher.connectionSuccessful
   *         method was not called (the connection to the server isn't
   *         established yet) at start-up or after a Watcher.connectionFailed
   *         call. The Watcher.connectionFailed could of happened anytime
   *         since the last Watcher.connectionSuccessful call until this
   *         method returns.
   * @throws TransactionIdTooOldException when the requested transaction id
   *         is too old and not loosing notifications can't be guaranteed.
   *         A solution would be a manual scanning and then calling the
   *         method again with -1 as the transactionId parameter.
   */
  public void placeWatch(String path, EventType watchType,
      long transactionId) throws TransactionIdTooOldException,
      NotConnectedToServerException, InterruptedException,
      WatchAlreadyPlacedException {
    NamespaceEventKey eventKey = new NamespaceEventKey(path, watchType);
    Object connectionLock = connectionManager.getConnectionLock();
   
    LOG.info(listeningPort + ": Placing watch: " +
        NotifierUtils.asString(eventKey) + " ...");
    if (watchedEvents.containsKey(eventKey)) {
      LOG.warn(listeningPort + ": Watch already exists at " +
          NotifierUtils.asString(eventKey));
      throw new WatchAlreadyPlacedException();
    }
   
    synchronized (connectionLock) {
      connectionManager.waitForTransparentConnect();
     
      if (!subscribe(path, watchType, transactionId)) {
        connectionManager.failConnection(true);
        connectionManager.waitForTransparentConnect();
        if (!subscribe(path, watchType, transactionId)) {
          // Since we are failing visible to the client, then there isn't
          // a need to request from a given txId
          watchedEvents.put(eventKey, -1L);
          connectionManager.failConnection(false);
          return;
        }
      }
     
      watchedEvents.put(eventKey, transactionId);
    }
  }


  /**
   * Removes a previously placed watch for a particular event type from the
   * given path. If the watch is not actually present at that path before
   * calling the method, nothing will happen.
   *
   * To remove the watch for all event types at this path, use
   * {@link #removeAllWatches(String)}.
   *
   * @param path the path from which the watch is removed. For the FILE_ADDED event
   *        type, this represents the path of the directory under which the
   *        file will be created.
   * @param watchType the type of the event for which don't want to receive
   *        notifications from now on.
   * @return true if successfully removed watch. false if the watch wasn't
   *         placed before calling this method.
   * @throws WatchNotPlacedException if the watch wasn't placed before calling
   *         this method.
   * @throws NotConnectedToServerException when the Watcher.connectionSuccessful
   *         method was not called (the connection to the server isn't
   *         established yet) at start-up or after a Watcher.connectionFailed
   *         call. The Watcher.connectionFailed could of happened anytime
   *         since the last Watcher.connectionSuccessfull call until this
   *         method returns.
   */
  public void removeWatch(String path, EventType watchType)
      throws NotConnectedToServerException, InterruptedException,
      WatchNotPlacedException {
    NamespaceEvent event = new NamespaceEvent(path, watchType.getByteValue());
    NamespaceEventKey eventKey = new NamespaceEventKey(path, watchType);
    Object connectionLock = connectionManager.getConnectionLock();
    ServerHandler.Client server;
   
    LOG.info(listeningPort + ": removeWatch: Removing watch from " +
        NotifierUtils.asString(eventKey) + " ...");
    if (!watchedEvents.containsKey(eventKey)) {
      LOG.warn(listeningPort + ": removeWatch: watch doesen't exist at " +
          NotifierUtils.asString(eventKey) + " ...");
      throw new WatchNotPlacedException();
    }
   
    synchronized (connectionLock) {
      connectionManager.waitForTransparentConnect();
      server = connectionManager.getServer();
     
      try {
        server.unsubscribe(connectionManager.getId(), event);
      } catch (InvalidClientIdException e1) {
        LOG.warn(listeningPort + ": removeWatch: server deleted us", e1);
        connectionManager.failConnection(true);
      } catch (ClientNotSubscribedException e2) {
        LOG.error(listeningPort + ": removeWatch: event not subscribed", e2);
      } catch (TException e3) {
        LOG.error(listeningPort + ": removeWatch: failed communicating to" +
            " server", e3);
        connectionManager.failConnection(true);
      }

      watchedEvents.remove(eventKey);
    }
   
    if (LOG.isDebugEnabled()) {
      LOG.debug(listeningPort + ": Unsubscribed from " +
          NotifierUtils.asString(eventKey));
    }
  }


  /**
   * Tests if a watch is placed at the given path and of the given type.
   *
   * @param path the path where we should test if a watch is placed. For the
   *        FILE_ADDED event type, this represents the path of the directory
   *        under which the file will be created.
   * @param watchType the type of the event for which we test if a watch is
   *        present.
   * @return <code>true</code> if a watch is placed, <code>false</code>
   *         otherwise.
   */
  public boolean haveWatch(String path, EventType watchType) {
    return watchedEvents.containsKey(new NamespaceEventKey(path, watchType));
  }
 
 
  /**
   * @return true if notifications were received for all subscribed events,
   *         false otherwise.
   */
  boolean receivedNotificationsForAllEvents() {
    return !watchedEvents.values().contains(-1L);
  }
 
 
  /**
   * Called right after a reconnect to resubscribe to all events. Must be
   * called with the connection lock acquired.
   */
  private boolean resubscribe() throws TransactionIdTooOldException,
      InterruptedException {
    for (NamespaceEventKey eventKey : watchedEvents.keySet()) {
      NamespaceEvent event = eventKey.getEvent();
      if (!subscribe(event.getPath(), EventType.fromByteValue(event.getType()),
          watchedEvents.get(eventKey))) {
        return false;
      }
    }
    return true;
  }
 
 
  /**
   * Should be called with the connection lock acquired and only in the
   * <code>CONNECTED</code> state.
   * @param path
   * @param watchType
   * @param transactionId
   * @return true if connected, false otherwise.
   * @throws TransactionIdTooOldException
   */
  private boolean subscribe(String path, EventType watchType,
      long transactionId)
          throws TransactionIdTooOldException, InterruptedException {
    ServerHandler.Client server = connectionManager.getServer();
    NamespaceEvent event =  new NamespaceEvent(path, watchType.getByteValue());
   
    if (LOG.isDebugEnabled()) {
      LOG.debug(listeningPort + ": subscribe: Trying to subscribe for " +
          NotifierUtils.asString(event) + " ... from txId " + transactionId);
    }
   
    for (int retries = 0; retries < 3; retries ++) {
      try {
        server.subscribe(connectionManager.getId(), event, transactionId);
        if (LOG.isDebugEnabled()) {
          LOG.debug(listeningPort + ": subscribe: successful");
        }
        return true;
      } catch (TransactionIdTooOldException e) {
        LOG.warn(listeningPort + ": Failed to subscribe [1]", e);
        throw e;
      } catch (InvalidClientIdException e) {
        LOG.warn(listeningPort + ": Failed to subscribe [2]", e);
      } catch (TException e) {
        LOG.warn(listeningPort + ": Failed to subscribe [3]", e);
        Thread.sleep(1000);
      }
    }
   
    return false;
  }


  @Override
  public void run() {
    LOG.info(listeningPort + ": Running ...");
    new Thread(connectionManager).start();
    LOG.info(listeningPort + ": Starting thrift server on port " + listeningPort);
    tserver.serve();
  }
 
 
  public void shutdown() {
    shouldShutdown = true;
    ServerHandler.Client server = connectionManager.getServer();
    connectionManager.shutdown();
   
    try {
      server.unregisterClient(connectionManager.getId());
    } catch (InvalidClientIdException e1) {
      LOG.warn(listeningPort + ": Server deleted us before shutdown", e1);
    } catch (TException e2) {
      LOG.warn(listeningPort + ": Failed to unregister client gracefully", e2);
    }
   
    tserver.stop();
  }

 
  /**
   * Raised when the client tries server related operations, but the
   * Watcher.connectionSuccessful method was not called.
   */
  static public class NotConnectedToServerException extends Exception {
    private static final long serialVersionUID = 1L;
   
    public NotConnectedToServerException(String arg) {
      super(arg);
    }
   
    public NotConnectedToServerException() {
      super();
    }
  }
 
 
  /**
   * Called when the placeWatch method is called, but the watch is already
   * present.
   */
  static public class WatchAlreadyPlacedException extends Exception {
    private static final long serialVersionUID = 1L;
   
    public WatchAlreadyPlacedException(String arg) {
      super(arg);
    }
   
    public WatchAlreadyPlacedException() {
      super();
    }
  }
 
 
  /**
   * Called when the removeWatch method is called, but the watch wasn't placed.
   */
  static public class WatchNotPlacedException extends Exception {
    private static final long serialVersionUID = 1L;
   
    public WatchNotPlacedException(String arg) {
      super(arg);
    }
   
    public WatchNotPlacedException() {
      super();
    }
  }
 
 
  /**
   * Called when the addServer method tries to add a server which is already
   * stored in the internal data structures.
   */
  static public class ServerAlreadyKnownException extends Exception {
    private static final long serialVersionUID = 1L;
   
    public ServerAlreadyKnownException(String arg) {
      super(arg);
    }
   
    public ServerAlreadyKnownException() {
      super();
    }
  }
 
 
  /**
   * Called when the removeServer method tries to remove a server which is not
   * stored in the internal data structures.
   */
  static public class ServerNotKnownException extends Exception {
    private static final long serialVersionUID = 1L;
   
    public ServerNotKnownException(String arg) {
      super(arg);
    }
   
    public ServerNotKnownException() {
      super();
    }
  }
}

class ConnectionManager implements Runnable {
  public static final Log LOG = LogFactory.getLog(NamespaceNotifierClient.class);

  static final int SOCKET_TIMEOUT = 35000;
  static final long DEFAULT_SERVER_TIMEOUT = 50000;
  static final int DEFAULT_CONNECT_RETRY_TIME = 1000;
 
  public static final int CONNECTED = 0;
  public static final int DISCONNECTED_HIDDEN = 1;
  public static final int DISCONNECTED_VISIBLE = 2;
 
  private int state = DISCONNECTED_VISIBLE;
 
  int listeningPort;
 
  // This lock is hold when doing operations that may modify the
  // connection state
  private Object connectionLock = new Object();
 
  // Used to wait and notify of when we should start retrying the connection
  // with the server.
  private Object retryConnectionCondition = new Object();
 
  // Connection to notification servers information
  private List<Map.Entry<String, Integer>> servers;
 
  // The server thrift object (if we are connected to a server)
  private ServerHandler.Client server = null;
 
  private long connectionToken;
 
  // The id of the server we are currently connected to
  volatile String serverId;
 
  // The id currently assigned to us by the server we are connected to.
  volatile long id;

  private volatile long serverTimeout = DEFAULT_SERVER_TIMEOUT;
 
  private volatile int connectRetryTime = DEFAULT_CONNECT_RETRY_TIME;

  ServerTracker tracker;

  NamespaceNotifierClient notifierClient;

 
  public ConnectionManager(List<String> hosts, List<Integer> ports,
      NamespaceNotifierClient notifierClient) {
    serverId = null;
    id = -1;
    tracker = new ServerTracker();
    this.notifierClient = notifierClient;
    this.listeningPort = notifierClient.listeningPort;
   
    servers = new ArrayList<Map.Entry<String,Integer>>();
    for (int i = 0; i < hosts.size(); i ++) {
      String host = hosts.get(i);
      int port = ports.get(ports.size() == 0 ? 0 : i);
      servers.add(Maps.immutableEntry(host, port));
    }
   
    // So clients try have different priority for servers, avoiding
    // all the clients connecting to one server.
    Collections.shuffle(servers, notifierClient.generator);
  }
 

  private int getServerPosition(String host, int port) {
    for (int i = 0; i < servers.size(); i ++) {
      Map.Entry<String, Integer> serverEntry = servers.get(i);
      if (serverEntry.getKey().equals(host) && serverEntry.getValue() == port) {
        return i;
      }
    }
    return -1;
  }
 
 
  void addServer(String host, int port)
      throws ServerAlreadyKnownException {
    if (getServerPosition(host, port) != -1) {
      throw new ServerAlreadyKnownException("Already got " + host + ":" +
          port);
    }
   
    // Put in a random position to ensure load balancing across servers
    int position = notifierClient.generator.nextInt(servers.size() + 1);
    servers.add(position, Maps.immutableEntry(host, port));
  }
 
 
  void removeServer(String host, int port)
      throws ServerNotKnownException {
    int position = getServerPosition(host, port);
    if (position == -1) {
      throw new ServerNotKnownException("Unknown host " + host + ":" + port);
    }
    servers.remove(position);
  }
 
 
  void setServerTimeout(long timeout) {
    serverTimeout = timeout;
  }
 
 
  void setConnectRetryTime(long retryTime) {
    setConnectRetryTime(retryTime);
  }
 
 
  long getId() {
    return id;
  }
 
 
  String getServerId() {
    return serverId;
  }
 
 
  ServerHandler.Client getServer() {
    return server;
  }
 
 
  /**
   * Gets the current connection state.
   * @param connectionLockHold if the connection lock returned by
   *        getConnectionLock is being hold at the moment within
   *        a synchronized block.
   * @return the current state (CONNECTED, DISCONNECTED_HIDDEN or
   *         DISCONNECTED_VISIBLE).
   */
  int getConnectionState(boolean connectionLockHold) {
    if (connectionLockHold) {
      return state;
    }
    synchronized (connectionLock) {
      return state;
    }
  }
 
 
  /**
   * @return The most recently generated connection token.
   */
  long getConnectionToken() {
    return connectionToken;
  }
 
 
  /**
   * @return An object that is being hold with synchronized() when doing
   *         operations that may change the connection state. Holding
   *         this object thus guarantees that no connection state changes
   *         will occur while doing so.
   */
  Object getConnectionLock() {
    return connectionLock;
  }
 
 
  /**
   * Must be called holding the connection lock returned by getConnectionLock.
   * It waits until the current connection state is CONNECTED. If it ever
   * gets to DISCONNECTED_VISIBLE it will raise an exception. If the current
   * state is CONNECTED, then it will return without waiting.
   *
   * @throws InterruptedException
   * @throws NotConnectedToServerException when we got into a
   *         DISCONNECTED_VISIBLE state.
   */
  void waitForTransparentConnect() throws InterruptedException,
      NotConnectedToServerException {
    if (state == DISCONNECTED_VISIBLE) {
      LOG.warn(listeningPort + ": waitForTransparentConnect: got visible" +
          " disconnected state");
      throw new NotConnectedToServerException();
    }
   
    // Wait until we are not hidden disconnected
    while (state != CONNECTED) {
      connectionLock.wait();
      switch (state) {
        case CONNECTED:
          break;
        case DISCONNECTED_HIDDEN:
          continue;
        case DISCONNECTED_VISIBLE:
          LOG.warn(listeningPort + ": waitForTransparentConnect: got visible" +
              " disconnected state");
          throw new NotConnectedToServerException();
      }
    }
  }
 
 
  private boolean connect() {
    LOG.info(listeningPort + ": Connecting ...");
   
    synchronized (connectionLock) {
      // Ensure there are no potential lost messages.
      if (state == DISCONNECTED_HIDDEN &&
          !notifierClient.receivedNotificationsForAllEvents()) {
        LOG.info(listeningPort + ": Didn't received notifications for" +
            " all events");
        failConnection(false);
        return false;
      } else {
        LOG.info(listeningPort + ": Received notifications for all events.");
      }
     
      for (Map.Entry<String, Integer> serverAddr : servers) {
        String host = serverAddr.getKey();
        int port = serverAddr.getValue();
        LOG.info(listeningPort + ": Trying to connect to " + host + ":" +
            port);
       
        ServerHandler.Client serverObj;
        try {
          serverObj = getServerConnection(host, port);
        } catch (Exception e) {
          LOG.error(listeningPort + ": Failed to connect to server at " +
              host + ":" + port, e);
          continue;
        }
       
        // The server must answer with this token
        connectionToken = notifierClient.generator.nextLong();
        LOG.info(listeningPort + ": Generated token: " + connectionToken);
         
        try {
          // Before this function returns, the server should make the
          // registerServer call which if he answers with the correct token,
          // it will set the serverId to his.
          LOG.info(listeningPort + ": calling registerClient");
          serverObj.registerClient(notifierClient.listeningHost,
              notifierClient.listeningPort, connectionToken);
          LOG.info(listeningPort + ": registerClient call successful");
        } catch (RampUpException e1) {
          LOG.info(listeningPort + ": Server " + host + ":" + port +
              " in ramp up phase");
          continue;
        } catch (ClientConnectionException e2) {
          LOG.error(listeningPort + ": The server failed to connect to us", e2);
          continue;
        } catch (Exception e) {
          LOG.error(listeningPort + ": Server " + host + ":" + port +
              " communication failure", e);
          continue;
        }
       
        if (serverId == null || serverId.isEmpty()) {
          LOG.info(listeningPort + ": The server answered with a bad token." +
              " trying next server ...");
          continue;
        }
        LOG.info(listeningPort + ": The server answered with correct token.");
        server = serverObj;
       
        state = CONNECTED;
        if (!notifierClient.connectionStateChanged(state)) {
          try {
            server.unregisterClient(id);
          } catch (Exception e) {}
          failConnection(false);
          return false;
        }
        tracker.messageReceived();
        LOG.info(listeningPort + ": Connection status: SUCCESS");
        return true;
      }
     
    }
   
    LOG.info(listeningPort + ": Connection status: FAILED");
    return false;
  }


  void failConnection(boolean hiddenToClient) {
    LOG.info(listeningPort + ": Failing connection. Hidden to client=" +
        hiddenToClient);
    serverId = null;
    id = -1;
    server = null;
    if (hiddenToClient) {
      state = DISCONNECTED_HIDDEN;
    } else {
      state = DISCONNECTED_VISIBLE;
    }
    notifierClient.connectionStateChanged(state);

    synchronized (retryConnectionCondition) {
      retryConnectionCondition.notify();
    }
  } 


  @Override
  public void run() {
    // Initial connect
    forceConnect();
    new Thread(tracker).start();
   
    // Retry on failure
    while (!notifierClient.shouldShutdown) {
      synchronized (retryConnectionCondition) {
        try {
          retryConnectionCondition.wait();
        } catch (InterruptedException e) {
          if (notifierClient.shouldShutdown) {
            break;
          }
          continue;
        }
      }
     
      if (getConnectionState(false) == DISCONNECTED_VISIBLE) {
        notifierClient.watcher.connectionFailed();
      }
     
      if (notifierClient.shouldShutdown) {
        break;
      }
     
      forceConnect();
    }
  }
 
 
  void shutdown() {
    synchronized (retryConnectionCondition) {
      retryConnectionCondition.notify();
    }
  }
 
 
  private void forceConnect() {
    LOG.info(listeningPort + ": ConnectionChecker forcing connect ...");
    while (true) {
      LOG.info(listeningPort + ": forceConnect loop start ...");
      try {
        Thread.sleep(connectRetryTime);
      } catch (InterruptedException e) {}
     
      LOG.info(listeningPort + ": forceConnect trying connect ...");
      int prevState = getConnectionState(false);
      if (connect()) {
        if (prevState == DISCONNECTED_VISIBLE) {
          notifierClient.watcher.connectionSuccesful();
        }
        break;
      }
      LOG.info(listeningPort + ": forceConnect done trying connect");
    }
    LOG.info(listeningPort + ": forceConnect done");
  }
 
 
  private ServerHandler.Client getServerConnection(String host, int port)
      throws TTransportException, IOException {
    TTransport transport;
    TProtocol protocol;
    ServerHandler.Client serverObj;
   
    transport = new TFramedTransport(new TSocket(host, port, SOCKET_TIMEOUT));
    protocol = new TBinaryProtocol(transport);
    serverObj = new ServerHandler.Client(protocol);
    transport.open();
   
    return serverObj;
  }
 
 
  class ServerTracker implements Runnable {
    volatile long lastReceivedTimestamp = -1;
   
    /**
     * Should be called when a message was received from the server.
     */
    public void messageReceived() {
      lastReceivedTimestamp = System.currentTimeMillis();
    }
   
    @Override
    public void run() {
      lastReceivedTimestamp = System.currentTimeMillis();
      while (!notifierClient.shouldShutdown) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {}
       
        synchronized (connectionLock) {
          if (state != CONNECTED) {
            continue;
          }
         
          if (System.currentTimeMillis() > lastReceivedTimestamp + serverTimeout) {
            LOG.info(listeningPort + ": ServerTracker: Server timeout." +
                " Failing connection ...");
            failConnection(true);
          }
        }
      }
    }
  }
}
TOP

Related Classes of org.apache.hadoop.hdfs.notifier.ConnectionManager$ServerTracker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.