Package com.sun.sgs.impl.service.channel

Source Code of com.sun.sgs.impl.service.channel.ChannelImpl

/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.sun.sgs.impl.service.channel;

import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.Delivery;
import com.sun.sgs.app.DeliveryNotSupportedException;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedObjectRemoval;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.MessageRejectedException;
import com.sun.sgs.app.NameNotBoundException;
import com.sun.sgs.app.ObjectNotFoundException;
import com.sun.sgs.app.ResourceUnavailableException;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TransactionException;
import com.sun.sgs.app.util.ManagedSerializable;
import com.sun.sgs.impl.service.session.ClientSessionImpl;
import com.sun.sgs.impl.service.session.ClientSessionWrapper;
import com.sun.sgs.impl.service.session.NodeAssignment;
import com.sun.sgs.impl.sharedutil.HexDumper;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.impl.util.AbstractKernelRunnable;
import com.sun.sgs.impl.util.BindingKeyedCollections;
import com.sun.sgs.impl.util.BindingKeyedMap;
import com.sun.sgs.impl.util.BindingKeyedSet;
import com.sun.sgs.impl.util.IoRunnable;
import com.sun.sgs.impl.util.ManagedQueue;
import com.sun.sgs.service.DataService;
import com.sun.sgs.service.Node;
import com.sun.sgs.service.TaskService;
import com.sun.sgs.service.Transaction;
import com.sun.sgs.service.WatchdogService;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Channel implementation for use within a single transaction.
*
* <p>This implementation uses several {@link BindingKeyedCollections}
* as follows:
*
* <dl style="margin-left: 1em">
*
* <dt> <i>Map:</i> <b><code>channelsMap</code></b> <br>
<i>Prefix:</i> <code>{@value #CHANNELS_MAP_PREFIX}</code> <br>
<i>Key:</i> <i>{@code name}</i><br>
<i>Value:</i> <b>{@link ChannelImpl}</b>
*
* <dd style="padding-top: .5em">Map for accessing a {@code ChannelImpl}
*   by name. <p>
*
* <dt> <i>Map:</i> <b><code>eventQueuesMap</code></b> <br>
<i>Prefix:</i> <code>{@value
*  #EVENT_QUEUE_MAP_PREFIX}<i>coordinatorNodeId.</i></code> <br>
<i>Key:</i> <i>{@code channelId}</i> (as string form of
*  {@code BigInteger})<br>
<i>Value:</i> <b>{@code EventQueue}</b>
*
* <dd style="padding-top: .5em">Map for accessing an event queue for a
*   channel on a given node.  The map is also used during recovery to
*   determine which channels are coordinated on a failed node so that
*   each channel can be reassigned a new coordinator.<p>
*
* <dt> <i>Set:</i> <b><code>sessionSet</code></b><br>
<i>Prefix:</i> <code>{@value
*  #SESSION_SET_PREFIX}<i>channelId.nodeId.</i></code> <br>
<i>Element:</i> <i>{@code sessionId}</i> (as {@code BigInteger})
*
* <dd style="padding-top: .5em">Set containing the IDs of a channel's
*  member sessions connected to a given node.  All members of a given
*   channel can be accessed using a set with the prefix {@value
*   #SESSION_SET_PREFIX}<i>{@code channelId.}</i><p>
*
* </dl> <p>
*/
abstract class ChannelImpl implements ManagedObject, Serializable {

    /** The serialVersionUID for this class. */
    private static final long serialVersionUID = 1L;

    /** The logger for this class. */
    protected static final LoggerWrapper logger =
  new LoggerWrapper(
      Logger.getLogger(ChannelImpl.class.getName()));

    /** The package name. */
    private static final String PKG_NAME = "com.sun.sgs.impl.service.channel.";

    /** The channels map prefix. */
    static final String CHANNELS_MAP_PREFIX = PKG_NAME + "name.";

    /** An event queue map prefix. */
    static final String EVENT_QUEUE_MAP_PREFIX = PKG_NAME + "eventQueue.";

    /** A prefix for a channel's session set. */
    static final String SESSION_SET_PREFIX = PKG_NAME + "session.";

    /** The random number generator for choosing a new coordinator. */
    private static final Random random = new Random();

    /** The map of channels, keyed by name. */
    private static BindingKeyedMap<ChannelImpl> channelsMap = null;

    /** The channel name. */
    protected final String name;

    /** The ID from a managed reference to this instance. */
    protected final BigInteger channelRefId;

    /** The wrapped channel instance. */
    private final ManagedReference<ChannelWrapper> wrappedChannelRef;

    /** The reference to this channel's listener. */
    private final ManagedReference<ChannelListener> listenerRef;

    /** The delivery requirement for messages sent on this channel. */
    protected final Delivery delivery;

    /** The node IDs of ChannelServers that have locally connected
     * members of this channel. */
    private final Set<Long> servers = new HashSet<Long>();
   
    /**
     * The node ID of the coordinator for this channel.  At first, it
     * is the node that the channel was created on.  If the
     * coordinator node fails, then the coordinator becomes one of the
     * member's nodes or the node performing recovery if there are no
     * members.
     */
    private long coordNodeId;

    /** The transaction. */
    private transient Transaction txn;

    /** Flag that is 'true' if this channel is closed. */
    private boolean isClosed = false;

    /**
     * The maximum channel message length supported by sessions joined to this
     * channel.
     */
    private int maxMessageLength = Integer.MAX_VALUE;
   
    /**
     * The maximum number of message bytes that can be queued for delivery on
     * this channel.
     */
    private final int writeBufferCapacity;

    /** The event queue reference. */
    private final ManagedReference<EventQueue> eventQueueRef;

    /**
     * Constructs an instance of this class with the specified
     * {@code name}, {@code listener}, {@code delivery} guarantee.
     * and write buffer capacity.
     *
     * @param name a channel name
     * @param listener a channel listener
     * @param delivery a delivery guarantee
     * @param writeBufferCapacity the capacity of the write buffer, in bytes
     */
    protected ChannelImpl(String name, ChannelListener listener,
        Delivery delivery, int writeBufferCapacity)
    {
  if (name == null) {
      throw new NullPointerException("null name");
  }
  this.name = name;
  DataService dataService = getDataService();
  if (listener != null) {
      if (!(listener instanceof Serializable)) {
    throw new IllegalArgumentException("non-serializable listener");
      } else if (!(listener instanceof ManagedObject)) {
    listener = new ManagedSerializableChannelListener(listener);
      }
      this.listenerRef = dataService.createReference(listener);
  } else {
      this.listenerRef = null;
  }
  this.delivery = delivery;
  this.writeBufferCapacity = writeBufferCapacity;
  this.txn = ChannelServiceImpl.getTransaction();
  ManagedReference<ChannelImpl> ref = dataService.createReference(this);
  this.wrappedChannelRef =
      dataService.createReference(new ChannelWrapper(ref));
  this.channelRefId = ref.getId();
  this.coordNodeId = getLocalNodeId();
  if (logger.isLoggable(Level.FINER)) {
      logger.log(Level.FINER, "Created ChannelImpl:{0}",
           HexDumper.toHexString(channelRefId.toByteArray()));
  }
  getChannelsMap().put(name, this);
  EventQueue eventQueue = new EventQueue(this);
  eventQueueRef = dataService.createReference(eventQueue);
  getEventQueuesMap(coordNodeId).
      put(channelRefId.toString(), eventQueue);
    }

    /** Returns the data service. */
    private static DataService getDataService() {
  return ChannelServiceImpl.getDataService();
    }

    /**
     * Returns the channels map, keyed by channel name.
     */
    private static synchronized  BindingKeyedMap<ChannelImpl> getChannelsMap() {
  if (channelsMap == null) {
      channelsMap = newMap(CHANNELS_MAP_PREFIX);

  }
  return channelsMap;
    }

    /**
     * Returns the event queues map for the specified coordinator {@code
     * nodeId}, keyed by channel ID.  In the returned map, each key is a
     * channel ID (as a BigInteger converted to string form) and is mapped
     * to its corresponding {@code EventQueue}.
     */
    private static BindingKeyedMap<EventQueue>
  getEventQueuesMap(long nodeId)
    {
  return newMap(EVENT_QUEUE_MAP_PREFIX + Long.toString(nodeId) + ".");
    }

    /* -- Factory methods -- */

    /**
     * Constructs a new {@code Channel} with the given {@code name}, {@code
     * listener}, {@code delivery} guarantee and write-buffer capacity.
     */
    static Channel newInstance(String name,
             ChannelListener listener,
             Delivery delivery,
                               int writeBufferCapacity)
    {
  ChannelImpl channel =
      delivery.equals(Delivery.UNRELIABLE) ?
      new UnreliableChannel(name, listener, delivery,
          writeBufferCapacity) :
      new OrderedChannel(name, listener, delivery,
        writeBufferCapacity);
  return channel.getWrappedChannel();
    }

    /**
     * Returns a channel with the given {@code name}.
     */
    static Channel getInstance(String name) {
  ChannelImpl channelImpl = getChannelsMap().get(name);
  if (channelImpl != null) {
      return channelImpl.getWrappedChannel();
  } else {
      throw new NameNotBoundException("channel not found: " + name);
  }
    }

    /* -- Implement Channel -- */

    /** Implements {@link Channel#getName}. */
    String getName() {
  checkContext();
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "getName returns {0}", name);
  }
  return name;
    }

    /** Implements {@link Channel#getDelivery}. */
    Delivery getDelivery() {
  checkContext();
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "getDelivery returns {0}", delivery);
  }
  return delivery;
    }

    /** Implements {@link Channel#hasSessions}. */
    boolean hasSessions() {
  checkClosed();
  return !servers.isEmpty();
    }

    /** Implements {@link Channel#getSessions}. */
    Iterator<ClientSession> getSessions() {
  checkClosed();
  return new ClientSessionIterator(channelRefId);
    }

    /** Implements {@link Channel#join(ClientSession)}. */
    void join(final ClientSession session) {
  try {
      checkClosed();
      if (session == null) {
    throw new NullPointerException("null session");
      }
      checkDelivery(session);
     
      /*
       * Enqueue join request with underlying (unwrapped) client
       * session object.
       */
      addEvent(new JoinEvent(unwrapSession(session)));

      logger.log(Level.FINEST, "join session:{0} returns", session);

  } catch (RuntimeException e) {
      logger.logThrow(Level.FINEST, e, "join throws");
      throw e;
  }
    }

    /** Implements {@link Channel#join(Set)}.
     *
     * Enqueues a join event to this channel's event queue and notifies
     * this channel's coordinator to service the event.
     */
    void join(final Set<ClientSession> sessions) {
  try {
      checkClosed();
      if (sessions == null) {
    throw new NullPointerException("null sessions");
      }

      /*
       * Check for null elements, and check that sessions support
       * this channel's delivery guarantee.
       */
      for (ClientSession session : sessions) {
    if (session == null) {
        throw new NullPointerException(
      "sessions contains a null element");
    }
    checkDelivery(session);
      }
     
      /*
       * Enqueue join requests, each with underlying (unwrapped)
       * client session object.
       *
       * TBD: (optimization) add a single event instead of one for
       * each session.
       */
      for (ClientSession session : sessions) {
    addEvent(new JoinEvent(unwrapSession(session)));
      }
      logger.log(Level.FINEST, "join sessions:{0} returns", sessions);

  } catch (RuntimeException e) {
      logger.logThrow(Level.FINEST, e, "join throws");
      throw e;
  }
    }

    /**
     * Throws {@code DeliveryNotSupportedException} if the specified {@code
     * session} does not support this channel's delivery guarantee.
     *
     * @param  session a client session
     * @throws  DeliveryNotSupportedException if the specified {@code session}
     *    does not support this channel's delivery guarantee
     */
    private void checkDelivery(ClientSession session) {
  for (Delivery d : session.supportedDeliveries()) {
      if (d.supportsDelivery(delivery)) {
    return;
      }
  }
  throw new DeliveryNotSupportedException(
      "client session:" + session +
      " does not support delivery guarantee",
      delivery);
    }
   
    /**
     * Returns the underlying {@code ClientSession} for the specified
     * {@code session}.  Note: The client session service wraps each client
     * session object that it hands out to the application.  The channel
     * service implementation relies on the assumption that a client
     * session's {@code ManagedObject} ID is the client session's ID (used
     * for identifying the client session, e.g. for sending messages to
     * the client session).  This method is invoked by the {@code join} and
     * {@code leave} methods in order to access the underlying {@code
     * ClientSession} so that the correct client session ID can be obtained.
     */
    private ClientSession unwrapSession(ClientSession session) {
  assert session instanceof ClientSessionWrapper;
  return ((ClientSessionWrapper) session).getClientSession();
    }

    /**
     * Adds the specified channel {@code event} to this channel's event queue
     * and notifies the coordinator that there is an event to service.  As
     * an optimization, if the local node is the coordinator for this channel
     * and the event queue is empty, then service the event immediately
     * without adding it to the event queue.
     */
    protected void addEvent(ChannelEvent event) {

  EventQueue eventQueue = eventQueueRef.get();

  if (isCoordinator() && eventQueue.isEmpty()) {
      // TBD: This invocation circumvents the write buffer capacity
      // check in the EventQueue.offer method.  Is this OK?
      event.serviceEvent(this);
  } else if (eventQueue.offer(event)) {
      notifyServiceEventQueue(eventQueue);
  } else {
      throw new ResourceUnavailableException(
       "not enough resources to add channel event");
  }
    }

    /**
     * If the coordinator is the local node, services events locally;
     * otherwise, schedules a task to send a request to this channel's
     * coordinator to service the event queue.
     *
     * @param  eventQueue this channel's event queue
     */
    private void notifyServiceEventQueue(EventQueue eventQueue) {

  if (isCoordinator()) {
      eventQueue.serviceEvent();

  } else {

      final ChannelServer coordinator = getChannelServer(coordNodeId);
      if (coordinator == null) {
    /*
     * If the ChannelServer for the coordinator's node has been
     * removed, then the coordinator's node has failed and will
     * be reassigned during recovery.  When recovery for the
     * failed node completes, the newly chosen coordinator will
     * restart the processing of channel events.
     */
    return;
      }
      final long coord = coordNodeId;
      final ChannelServiceImpl channelService =
    ChannelServiceImpl.getChannelService();
      final BigInteger channelRefId = eventQueue.getChannelRefId();
      channelService.getTaskService().scheduleNonDurableTask(
          new AbstractKernelRunnable("SendServiceEventQueue") {
      public void run() {
          channelService.runIoTask(
       new IoRunnable() {
        public void run() throws IOException {
            coordinator.serviceEventQueue(channelRefId);
        } }, coord);
      }
    }, false);
  }
    }

    /** Implements {@link Channel#leave(ClientSession)}.
     *
     * Enqueues a leave event to this channel's event queue and notifies
     * this channel's coordinator to service the event.
     */
    void leave(final ClientSession session) {
  try {
      checkClosed();
      if (session == null) {
    throw new NullPointerException("null client session");
      }

      /*
       * Enqueue leave request with underlying (unwrapped) client
       * session object.
       */
      addEvent(new LeaveEvent(unwrapSession(session)));
      logger.log(Level.FINEST, "leave session:{0} returns", session);

  } catch (RuntimeException e) {
      logger.logThrow(Level.FINEST, e, "leave throws");
      throw e;
  }
    }

    /** Implements {@link Channel#leave(Set)}.
     *
     * Enqueues leave event(s) to this channel's event queue and notifies
     * this channel's coordinator to service the event(s).
     */
    void leave(final Set<ClientSession> sessions) {
  try {
      checkClosed();
      if (sessions == null) {
    throw new NullPointerException("null sessions");
      }

      /*
       * Enqueue leave requests, each with underlying (unwrapped)
       * client session object.
       *
       * TBD: (optimization) add a single event instead of one for
       * each session.
       */
      for (ClientSession session : sessions) {
    addEvent(new LeaveEvent(unwrapSession(session)));
      }
      logger.log(Level.FINEST, "leave sessions:{0} returns", sessions);

  } catch (RuntimeException e) {
      logger.logThrow(Level.FINEST, e, "leave throws");
      throw e;
  }
    }

    /** Implements {@link Channel#leaveAll}.
     *
     * Enqueues a leaveAll event to this channel's event queue and notifies
     * this channel's coordinator to service the event.
     */
    void leaveAll() {
  try {
      checkClosed();

      /*
       * Enqueue leaveAll request.
       */
      addEvent(new LeaveAllEvent());
      logger.log(Level.FINEST, "leaveAll returns");

  } catch (RuntimeException e) {
      logger.logThrow(Level.FINEST, e, "leave throws");
      throw e;
  }
    }

    /** Implements {@link Channel#send}.
     *
     * Enqueues a send event to this channel's event queue and notifies
     * this channel's coordinator to service the event.
     */
    void send(ClientSession sender, ByteBuffer message) {
  try {
      checkClosed();
      if (message == null) {
    throw new NullPointerException("null message");
      }
      message = message.asReadOnlyBuffer();
           
            if (message.remaining() > maxMessageLength) {
                throw new IllegalArgumentException(
                    "message too long: " + message.remaining() + " > " +
                    maxMessageLength);
            }
      /*
       * Enqueue send request.
       */
            byte[] msgBytes = new byte[message.remaining()];
            message.get(msgBytes);
      BigInteger senderRefId =
    sender != null ?
    getSessionRefId(unwrapSession(sender)) :
    null;
      handleSendEvent(new SendEvent(senderRefId, msgBytes));

      if (logger.isLoggable(Level.FINEST)) {
    logger.log(Level.FINEST, "send channel:{0} message:{1} returns",
         this, HexDumper.format(msgBytes, 0x50));
      }

  } catch (RuntimeException e) {
      if (logger.isLoggable(Level.FINEST)) {
    logger.logThrow(
        Level.FINEST, e, "send channel:{0} message:{1} throws",
        this, HexDumper.format(message, 0x50));
      }
      throw e;
  }
    }

    /**
     * Handles a send event containing a message and a sender (which may be
     * {@code null}.  A subclass should handle the send event according to
     * the channel's delivery guarantee.
     *
     * @param  sendEvent a send event
     */
    protected abstract void handleSendEvent(SendEvent sendEvent);

    /**
     * If this channel has a null {@code ChannelListener}, forwards the
     * specified {@code message} to the channel by invoking this channel's
     * {@code send} method with the specified {@code sender} and {@code
     * message}; otherwise, invokes the {@code ChannelListener}'s {@code
     * receivedMessage} method with this channel, the specified {@code sender},
     * and {@code message}.
     */
    private void receivedMessage(ClientSession sender, ByteBuffer message) {

  assert sender instanceof ClientSessionWrapper;

  if (listenerRef == null) {
      send(sender, message);
  } else {
      // TBD: exception handling?
      listenerRef.get().receivedMessage(
    getWrappedChannel(), sender, message.asReadOnlyBuffer());
  }
    }

    /**
     * Enqueues a close event to this channel's event queue and notifies
     * this channel's coordinator to service the event.  This method
     * is invoked by this channel's {@code ChannelWrapper} when the
     * application removes the wrapper object.
     */
    void close() {
  checkContext();
  getDataService().markForUpdate(this);
  if (!isClosed) {
      /*
       * Enqueue close event.
       */
      addEvent(new CloseEvent());
      isClosed = true;
  }
    }

    /* -- Implement Object -- */

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object obj) {
  // TBD: Because this is a managed object, does an "==" check
  // suffice here?
  return
      (this == obj) ||
      (obj != null && obj.getClass() == this.getClass() &&
       channelRefId.equals(((ChannelImpl) obj).channelRefId));
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
  return channelRefId.hashCode();
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
  return getClass().getName() +
      "[" + HexDumper.toHexString(channelRefId.toByteArray()) + "]";
    }

    /* -- Serialization methods -- */

    private void writeObject(ObjectOutputStream out) throws IOException {
  out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in)
  throws IOException, ClassNotFoundException
    {
  in.defaultReadObject();
  txn = ChannelServiceImpl.getTransaction();
    }

    /* -- Other methods -- */

    /**
     * Returns the ID for the specified {@code session}.
     */
    private static BigInteger getSessionRefId(ClientSession session) {
  return getDataService().createReference(session).getId();
    }

    /**
     * Returns the node ID for the specified  {@code session}.
     */
    private static long getNodeId(ClientSession session) {
  if (session instanceof NodeAssignment) {
      return ((NodeAssignment) session).getNodeId();
  } else {
      throw new IllegalArgumentException(
    "session does not implement NodeAssignment: " +
    session.getClass());
  }
    }

    /**
     * Returns the wrapped channel for this instance.
     */
    protected ChannelWrapper getWrappedChannel() {
  return wrappedChannelRef.get();
    }

    /**
     * Checks that this channel's transaction is currently active,
     * throwing TransactionNotActiveException if it isn't.
     */
    private void checkContext() {
  ChannelServiceImpl.checkTransaction(txn);
    }

    /**
     * Checks the context, and then checks that this channel is not
     * closed, throwing an IllegalStateException if the channel is
     * closed.
     */
    private void checkClosed() {
  checkContext();
  if (isClosed) {
      throw new IllegalStateException("channel is closed");
  }
    }

    /**
     * Returns {@code true} if this node is the coordinator for this
     * channel, otherwise returns {@code false}.
     */
    private boolean isCoordinator() {
  return coordNodeId == getLocalNodeId();
    }

    /**
     * Returns a new {@code BindingKeyedSet} with the specified {@code
     * keyPrefix}.
     *
     * @param  <V> the value type
     * @param  keyPrefix a key prefix for the set's service bindings
     * @return  a new {@code BindingKeyedSet}
     */
    private static <V> BindingKeyedSet<V> newSet(String keyPrefix) {
  return ChannelServiceImpl.getCollectionsFactory().
      newSet(keyPrefix);
    }

    /**
     * Returns a new {@code BindingKeyedMap} with the specified {@code
     * keyPrefix}.
     *
     * @param  <V> the value type
     * @param  keyPrefix a key prefix for the map's service bindings
     * @return  a new {@code BindingKeyedMap}
     */
    private static <V> BindingKeyedMap<V> newMap(String keyPrefix) {
  return ChannelServiceImpl.getCollectionsFactory().
      newMap(keyPrefix);
    }
    /**
     * Returns the sessions set for the specified {@code nodeId}, creating
     * one if not already present.
     */
    private Set<BigInteger> getOrCreateSessionSet(long nodeId) {
  if (servers.add(nodeId)) {
      getDataService().markForUpdate(this);
  }
  return getSessionSet(nodeId);
    }
   
    /**
     * Returns the sessions set for the specified {@code nodeId}.
     */
    private Set<BigInteger> getSessionSet(long nodeId) {
  return
      newSet(
    SESSION_SET_PREFIX +
    channelRefId.toString() + "." +
    Long.toString(nodeId) + ".");
    }

    /**
     * Removes the sessions set for the specified {@code nodeId}.
     */
    private void removeSessionSet(long nodeId) {
  getSessionSet(nodeId).clear();
  getDataService().markForUpdate(this);
  servers.remove(nodeId);
    }

    /**
     * If the specified {@code session} is not already a member of
     * this channel, adds the session to this channel and
     * returns {@code true}; otherwise if the specified {@code
     * session} is already a member of this channel, returns {@code
     * false}.
     *
     * @return  {@code true} if the session was added to the channel,
     *    and {@code false} if the session is already a member
     */
    private boolean addSession(ClientSession session) {
  boolean sessionAdded =
      getOrCreateSessionSet(getNodeId(session)).
          add(getSessionRefId(session));
        if (sessionAdded && session.getMaxMessageLength() < maxMessageLength) {
            maxMessageLength = session.getMaxMessageLength();
        }
  return sessionAdded;
    }

    /**
     * Removes from this channel the member session with the specified
     * {@code sessionRefId} that is connected to the node with the
     * specified {@code nodeId}, notifies the session's server that the
     * session left the channel, and returns {@code true} if the
     * session was a member of this channel when this method was
     * invoked.  If the session is not a member of this channel, then no
     * action is taken and {@code false} is returned.
     */
    private boolean removeSession(
  final long nodeId, final BigInteger sessionRefId)
    {
  Set<BigInteger> sessionSet = getSessionSet(nodeId);
  if (sessionSet != null) {
      if (sessionSet.remove(sessionRefId)) {
    sendLeaveNotification(nodeId, sessionRefId);
    if (sessionSet.isEmpty()) {
        removeSessionSet(nodeId);
    }
    return true;
      }
  }
  return false;
    }

    /**
     * Reassigns the channel coordinator as follows:
     *
     * 1) Reassigns the channel's coordinator from the node specified by
     * the {@code failedCoordNodeId} to another server node (if there are
     * channel members), or the local node (if there are no channel
     * members) and rebinds the event queue to the new coordinator's key.
     *
     * 2) Marks the event queue to indicate that a refresh request for this
     * channel must be sent this channel's channel servers to notify each
     * server to reread this channel's membership list (for the given
     * channel server's local node) before servicing any more events.
     *
     * 3} Finally, sends out a 'serviceEventQueue' request to the new
     * coordinator to restart this channel's event processing.
     */
    private void reassignCoordinator(long failedCoordNodeId) {
  DataService dataService = getDataService();
  dataService.markForUpdate(this);
  if (coordNodeId != failedCoordNodeId) {
      logger.log(
    Level.SEVERE,
    "attempt to reassign coordinator:{0} for channel:{1} " +
    "that is not the failed node:{2}",
    coordNodeId, failedCoordNodeId, this);
      return;
  }
 
  removeSessionSet(failedCoordNodeId);

  /*
   * Assign a new coordinator, and store event queue in new
   * coordinator's event queue map.
   */
  coordNodeId = chooseCoordinatorNode();
  if (logger.isLoggable(Level.FINE)) {
      logger.log(
    Level.FINE,
    "channel:{0} reassigning coordinator from:{1} to:{2}",
    HexDumper.toHexString(channelRefId.toByteArray()),
    failedCoordNodeId,
    coordNodeId);
  }
  EventQueue eventQueue = eventQueueRef.get();
  getEventQueuesMap(coordNodeId).
      put(channelRefId.toString(), eventQueue);

  /*
   * Mark the event queue to indicate that a refresh must be sent to
   * all channel servers for this channel before servicing any events
   * in the queue, and then send a 'serviceEventQueue' notification
   * to the new coordinator.
   */
  eventQueue.setSendRefresh();
  notifyServiceEventQueue(eventQueue);
    }

    /**
     * Chooses a node to be the new coordinator for this channel, and
     * returns the ID for the chosen node.  If there is one or more
     * channel server(s) for this channel that are currently alive, this
     * method chooses one of those server nodes at random to be the new
     * coordinator.  If there are no live channel servers for this channel,
     * then the local node is chosen to be the coordinator.
     *
     * This method should be called within a transaction.
     */
    private long chooseCoordinatorNode() {
  if (!servers.isEmpty()) {
      int numServers = servers.size();
      Long[] serverIds = servers.toArray(new Long[numServers]);
      int startIndex = random.nextInt(numServers);
      WatchdogService watchdogService =
    ChannelServiceImpl.getWatchdogService();
      for (int i = 0; i < numServers; i++) {
    int tryIndex = (startIndex + i) % numServers;
    long candidateId = serverIds[tryIndex];
    Node coordCandidate = watchdogService.getNode(candidateId);
    if (coordCandidate != null && coordCandidate.isAlive()) {
        return candidateId;
    }
      }
  }
  return getLocalNodeId();
    }

    /**
     * Removes the client session with the specified {@code sessionRefId}
     * from the channel with the specified {@code channelRefId}.  This
     * method is invoked when a session is disconnected from this node
     * (with the specified {@code nodeId}) gracefully or otherwise, or if
     * this node is recovering for a failed node whose sessions all became
     * disconnected.  The {@code nodeId} specifies the node that the
     * session was previously connected to, which is not necessarily
     * the local node's ID.
     *
     * This method should be called within a transaction.
     */
    static void removeSessionFromChannel(
  long nodeId, BigInteger sessionRefId, BigInteger channelRefId)
    {
  ChannelImpl channel = (ChannelImpl) getObjectForId(channelRefId);
  if (channel != null) {
      channel.removeSession(nodeId, sessionRefId);
  } else {
      if (logger.isLoggable(Level.FINE)) {
    logger.log(Level.FINE, "channel already removed:{0}",
        HexDumper.toHexString(channelRefId.toByteArray()));
      }
  }
    }

    /**
     * Returns the local node's ID.
     */
    private static long getLocalNodeId() {
  return ChannelServiceImpl.getLocalNodeId();
    }

    /**
     * Returns the channel server for the specified {@code nodeId}.
     */
    private static ChannelServer getChannelServer(long nodeId) {
  return ChannelServiceImpl.getChannelService().getChannelServer(nodeId);
    }

    /**
     * Returns the managed object with the specified {@code refId}, or {@code
     * null} if there is no object with the specified {@code refId}.
     *
     * @param  refId the object's identifier as obtained by
     *    {@link ManagedReference#getId ManagedReference.getId}
     *
     * @throws  TransactionException if the operation failed because of a
     *    problem with the current transaction
     */
    private static Object getObjectForId(BigInteger refId) {
  try {
      return getDataService().createReferenceForId(refId).get();
  } catch (ObjectNotFoundException e) {
      return null;
  }
    }

    /**
     * Returns a set containing the node IDs of the channel servers for
     * this channel.
     */
    private Set<Long> getMemberNodeIds() {
  return new HashSet<Long>(servers);
    }
   
    /**
     * Removes all sessions from this channel and clears the list of
     * channel servers for this channel.  This method should be called
     * when all sessions leave the channel.
     */
    private void removeAllSessions() {

  for (long nodeId : getMemberNodeIds()) {
      for (BigInteger sessionRefId : getSessionSet(nodeId)) {
    sendLeaveNotification(nodeId, sessionRefId);
      }
      removeSessionSet(nodeId);
  }
    }

    /**
     * Sends a leave notification for the session with the specified {@code
     * sessionRefId} to the channel server with the specified {@code
     * nodeId}.
     */
    private void sendLeaveNotification(long nodeId,
               final BigInteger sessionRefId)
    {
  final ChannelServer server = getChannelServer(nodeId);
  /*
   * If there is no channel server for the session's node,
   * then the session's node has failed and the session is
   * now disconnected.  There is no need to send a 'leave'
   * notification (to update the channel membership cache) to
   * the failed server.
   */
  if (server != null) {
      ChannelServiceImpl.getChannelService().addChannelTask(
    channelRefId,
    new IoRunnable() {
        public void run() throws IOException {
      server.leave(channelRefId, sessionRefId);
        } },
    nodeId);
  }
    }

    /**
     * Removes all sessions from this channel, removes the channel
     * object and its binding, the channel listener wrapper (if we
     * created a wrapper for it), and the event queue and associated
     * binding from the data store.  This method should be called when
     * the channel is closed.
     */
    private void removeChannel() {
  DataService dataService = getDataService();
  removeAllSessions();
  getChannelsMap().removeOverride(name);
  dataService.removeObject(this);
  if (listenerRef != null) {
      ChannelListener maybeWrappedListener = listenerRef.get();
      if (maybeWrappedListener instanceof ManagedSerializable) {
    dataService.removeObject(maybeWrappedListener);
      }
  }
  BindingKeyedMap<EventQueue> eventQueuesMap =
      getEventQueuesMap(coordNodeId);
  eventQueuesMap.removeOverride(channelRefId.toString());
  EventQueue eventQueue = eventQueueRef.get();
  dataService.removeObject(eventQueue);
    }

    /**
     * Returns {@code true} if the specified client {@code session} is
     * a member of this channel.
     */
    private boolean hasSession(long nodeId, BigInteger sessionRefId) {
  Set<BigInteger> sessionSet = getSessionSet(nodeId);
  return sessionSet != null && sessionSet.contains(sessionRefId);
    }

    /* -- Other classes -- */

    /**
     * An iterator for {@code ClientSession}s of a given channel.
     */
    private static class ClientSessionIterator
  implements Iterator<ClientSession>
    {
  /** The iterator for sessions. */
  private Iterator<BigInteger> iterator;

  /** The client session to be returned by {@code next}. */
  private ClientSession nextSession = null;

  /**
   * Constructs an instance of this class with the specified
   * {@code channelRefId}.
   */
  ClientSessionIterator(BigInteger channelRefId) {
      Set<BigInteger> sessionSet =
    newSet(SESSION_SET_PREFIX + channelRefId.toString() + ".");
      iterator = sessionSet.iterator();
  }

  /** {@inheritDoc} */
  public boolean hasNext() {
      if (!iterator.hasNext()) {
    return false;
      }
      if (nextSession != null) {
    return true;
      }
      ClientSessionImpl session = (ClientSessionImpl)
    getObjectForId(iterator.next());
      if (session == null) {
    return hasNext();
      } else {
    nextSession = session.getWrappedClientSession();
    return true;
      }
  }

  /** {@inheritDoc} */
  public ClientSession next() {
      try {
    if (nextSession == null && !hasNext()) {
        throw new NoSuchElementException();
    }
    return nextSession;
      } finally {
    nextSession = null;
      }
  }

  /** {@inheritDoc} */
  public void remove() {
      throw new UnsupportedOperationException("remove is not supported");
  }
    }

    /**
     * A wrapper for a {@code ChannelListener} that is serializable,
     * but not managed.
     */
    private static class ManagedSerializableChannelListener
  extends ManagedSerializable<ChannelListener>
  implements ChannelListener
    {
  private static final long serialVersionUID = 1L;

  /** Constructs an instance with the specified {@code listener}. */
  ManagedSerializableChannelListener(ChannelListener listener) {
      super(listener);
  }

  /** {@inheritDoc} */
  public void receivedMessage(
      Channel channel, ClientSession sender, ByteBuffer message)
  {
      assert sender instanceof ClientSessionWrapper;
      get().receivedMessage(channel, sender, message);
  }
    }

    /**
     * The channel's event queue.
     */
    private static class EventQueue
  implements ManagedObjectRemoval, Serializable
    {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  /** The managed reference to the queue's channel. */
  private final ManagedReference<ChannelImpl> channelRef;
  /** The managed reference to the managed queue. */
  private final ManagedReference<ManagedQueue<ChannelEvent>> queueRef;
  /** The sequence number for events on this channel. */
  private long seq = 0;
  /** If {@code true}, a refresh should be sent to all channel's
   * servers before servicing the next event.
   */
  private boolean sendRefresh = false;

  /**
   * The number of bytes of the write buffer that are currently
   * available.
   */
  private int writeBufferAvailable;

  /**
   * Constructs an event queue for the specified {@code channel}.
   */
  EventQueue(ChannelImpl channel) {
      DataService dataService = getDataService();
      channelRef = dataService.createReference(channel);
      queueRef = dataService.createReference(
    new ManagedQueue<ChannelEvent>());
      writeBufferAvailable = channel.getWriteBufferCapacity();
  }

  /**
   * Attempts to enqueue the specified {@code event}, and returns
   * {@code true} if successful, and {@code false} otherwise.
   *
   * @param event the event
   * @return {@code true} if successful, and {@code false} otherwise
   * @throws MessageRejectedException if the cost of the event
   *         exceeds the available buffer space in the queue
   */
  boolean offer(ChannelEvent event) {
      int cost = event.getCost();
      if (cost > writeBufferAvailable) {
          throw new MessageRejectedException(
              "Not enough queue space: " + writeBufferAvailable +
        " bytes available, " + cost + " requested");
      }
      boolean success = getQueue().offer(event);
      if (success && (cost > 0)) {
    getDataService().markForUpdate(this);
                writeBufferAvailable -= cost;
                if (logger.isLoggable(Level.FINEST)) {
                    logger.log(Level.FINEST,
                        "{0} reserved {1,number,#} leaving {2,number,#}",
                        this, cost, writeBufferAvailable);
                }
      }
      return success;
  }

  /**
   * Returns the channel for this queue.
   */
  ChannelImpl getChannel() {
      return channelRef.get();
  }

  /**
   * Returns the channel ID for this queue.
   */
  BigInteger getChannelRefId() {
      return channelRef.getId();
  }

  /**
   * Returns the managed queue object.
   */
  ManagedQueue<ChannelEvent> getQueue() {
      return queueRef.get();
  }

  /**
   * Returns {@code true} if the event queue is empty.
   */
  boolean isEmpty() {
      return getQueue().isEmpty();
  }

  /**
   * Throws a retryable exception if the event queue is not in a
   * state to process the next event (e.g., it hasn't received
   * an expected acknowledgment that all channel servers have
   * received a specified 'send' request).
   */
  void checkState() {
      // FIXME: This needs to be implemented in order to support
      // reliable messages in the face of channel coordinator crash.
  }

  /**
   * Marks this queue to indicate that a 'refresh' notification needs
   * to be sent to the associated channel's servers before servicing
   * any more events.  This action is taken when the associated
   * channel's coordinator is reassigned during the previous channel
   * coordinator's recovery.
   */
  void setSendRefresh() {
      getDataService().markForUpdate(this);
      sendRefresh = true;
  }

  /**
   * Processes (at least) the first event in the queue.
   */
  void serviceEvent() {
      checkState();
      ChannelImpl channel = getChannel();
      if (!channel.isCoordinator()) {
    // TBD: should a serviceEventQueue request be forwarded to
    // the true channel coordinator?
    logger.log(
        Level.WARNING,
        "Attempt at node:{0} channel:{1} to service events; " +
        "instead of current coordinator:{2}",
        getLocalNodeId(),
        HexDumper.toHexString(channel.channelRefId.toByteArray()),
        channel.coordNodeId);
    return;
      }
      ChannelServiceImpl channelService =
    ChannelServiceImpl.getChannelService();
      DataService dataService = getDataService();
     
      /*
       * If a new coordinator has taken over (i.e., 'sendRefresh' is
       * true), then all pending events should to be serviced, since
       * a 'serviceEventQueue' request may have been missed when the
       * former channel coordinator failed and was in the process of
       * being reassigned.  So, assign the 'serviceAllEvents' flag
       * to indicate whether or not all pending events should be
       * processed below.
       */
      boolean serviceAllEvents = sendRefresh;
      if (sendRefresh) {
    final BigInteger channelRefId = getChannelRefId();
    final String channelName = channel.name;
    final Delivery channelDelivery = channel.delivery;
    if (logger.isLoggable(Level.FINEST)) {
        logger.log(
      Level.FINEST, "sending refresh, channel:{0}",
      HexDumper.toHexString(channelRefId.toByteArray()));
    }
    for (final long nodeId : channel.servers) {
        channelService.addChannelTask(
          channelRefId,
      new IoRunnable() {
          public void run() throws IOException {
        ChannelServer server = getChannelServer(nodeId);
        if (server != null) {
            server.refresh(channelName, channelRefId,
               channelDelivery);
        }
          } },
      nodeId);
    }
    dataService.markForUpdate(this);
    sendRefresh = false;
      }

      /*
       * Process channel events.  If the 'serviceAllEvents' flag is
       * true, then service all pending events.
       */
      int eventsToService = channelService.eventsPerTxn;
      ManagedQueue<ChannelEvent> eventQueue = getQueue();
      do {
    ChannelEvent event = eventQueue.poll();
    if (event == null) {
        return;
    }

                logger.log(Level.FINEST, "processing event:{0}", event);
                int cost = event.getCost();
    if (cost > 0) {
        dataService.markForUpdate(this);
        writeBufferAvailable += cost;

        if (logger.isLoggable(Level.FINEST)) {
            logger.log(Level.FINEST,
           "{0} cleared reservation of " +
           "{1,number,#} bytes, leaving {2,number,#}",
           this, cost, writeBufferAvailable);
        }
    }
    event.serviceEvent(getChannel());

      } while (serviceAllEvents || --eventsToService > 0);
  }

  /* -- Implement ManagedObjectRemoval -- */

  /** {@inheritDoc} */
  public void removingObject() {
      try {
    getDataService().removeObject(queueRef.get());
      } catch (ObjectNotFoundException e) {
    // already removed.
      }
  }
    }

    /**
     * Represents an event on a channel.
     */
    abstract static class ChannelEvent
  implements ManagedObject, Serializable
    {

  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  /**
   * Services this event, taken from the head of the event queue
   * whose reference is specified during construction.
   */
  abstract void serviceEvent(ChannelImpl channel);

  /**
   * Returns the cost of this event, which the {@code EventQueue}
   * may use to reject events when the total cost is too large.
   * The default implementation returns a cost of zero.
   *
   * @return the cost of this event
   */
  int getCost() {
      return 0;
  }
    }

    /**
     * A channel join event.
     */
    private static class JoinEvent extends ChannelEvent {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  private final BigInteger sessionRefId;

  /**
   * Constructs a join event with the specified {@code session}.
   */
  JoinEvent(ClientSession session) {
      sessionRefId = getSessionRefId(session);
  }

  /** {@inheritDoc} */
  public void serviceEvent(final ChannelImpl channel) {

      ClientSession session =
    (ClientSession) getObjectForId(sessionRefId);
      if (session == null) {
    logger.log(
        Level.FINE,
        "unable to obtain client session for ID:{0}", this);
    return;
      }
      if (!channel.addSession(session)) {
    return;
      }
      long nodeId = getNodeId(session);
      final ChannelServer server = getChannelServer(nodeId);
      if (server == null) {
    /*
     * If there is no channel server for the session's node,
     * then the session's node has failed and the session is
     * now disconnected.  There is no need to send a 'join'
     * notification (to update the channel membership cache) to
     * the failed server.
     */
    return;
      }
      final String channelName = channel.name;
      final BigInteger channelRefId = channel.channelRefId;
      final Delivery channelDelivery = channel.delivery;
      ChannelServiceImpl.getChannelService().addChannelTask(
    channelRefId,
    new IoRunnable() {
        public void run() throws IOException {
      server.join(channelName, channelRefId,
            channelDelivery, sessionRefId);
        } },
    nodeId);
  }

  /** {@inheritDoc} */
        @Override
  public String toString() {
      return getClass().getName() + ": " +
    HexDumper.toHexString(sessionRefId.toByteArray());
  }
    }

    /**
     * A channel leave event.
     */
    private static class LeaveEvent extends ChannelEvent {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  private final BigInteger sessionRefId;

  /**
   * Constructs a leave event with the specified {@code session}.
   */
  LeaveEvent(ClientSession session) {
      sessionRefId = getSessionRefId(session);
  }

  /** {@inheritDoc} */
  public void serviceEvent(ChannelImpl channel) {

      ClientSession session =
    (ClientSession) getObjectForId(sessionRefId);
      if (session == null) {
    logger.log(
        Level.FINE,
        "unable to obtain client session for ID:{0}", this);
    return;
      }
      if (!channel.removeSession(getNodeId(session), sessionRefId)) {
    return;
      }
  }

  /** {@inheritDoc} */
        @Override
  public String toString() {
      return getClass().getName() + ": " +
    HexDumper.toHexString(sessionRefId.toByteArray());
  }
    }

    /**
     * A channel leaveAll event.
     */
    private static class LeaveAllEvent extends ChannelEvent {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  /**
   * Constructs a leaveAll event.
   */
  LeaveAllEvent() {
  }

  /** {@inheritDoc} */
  public void serviceEvent(ChannelImpl channel) {

      Set<Long> serverNodeIds = channel.getMemberNodeIds();
      channel.removeAllSessions();
      ChannelServiceImpl channelService =
    ChannelServiceImpl.getChannelService();
      final BigInteger channelRefId = channel.channelRefId;
      for (final long nodeId : serverNodeIds) {
    channelService.addChannelTask(
        channelRefId,               
        new IoRunnable() {
      public void run() throws IOException {
          ChannelServer server = getChannelServer(nodeId);
          if (server != null) {
        server.leaveAll(channelRefId);
          }
      } },
        nodeId);
      }
  }

  /** {@inheritDoc} */
        @Override
  public String toString() {
      return getClass().getName();
  }
    }

    /**
     * A channel send event.
     */
    static class SendEvent extends ChannelEvent {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  private final byte[] message;
  /** The sender's session ID, or null. */
  private final BigInteger senderRefId;

  /**
   * Constructs a send event with the given {@code senderRefId} and
   * {@code message}.
   *
   * @param senderRefId a sender's session ID, or {@code null}
   * @param message a message
   */
  SendEvent(BigInteger senderRefId, byte[] message) {
      this.senderRefId = senderRefId;
      this.message = message;
  }

  /** {@inheritDoc}
   *
   * TBD: (optimization) this should handle sending
   * multiple messages to a given channel.  Here, we
   * could peek at the next event in the queue, and if
   * it is a send, that event could be batched with this
   * send event.  This could be repeated for multiple
   * send events appearing in the queue.
   */
  public void serviceEvent(ChannelImpl channel) {

      /*
       * Verfiy that the sending session (if any) is a member of this
       * channel.
       */
      if (senderRefId != null) {
    ClientSession sender =
        (ClientSession) getObjectForId(senderRefId);
    if (sender == null ||
        !channel.hasSession(getNodeId(sender), senderRefId))
    {
        return;
    }
      }
      /*
       * Enqueue a channel task to forward the message to the
       * channel's servers for delivery.
       */
      ChannelServiceImpl channelService =
    ChannelServiceImpl.getChannelService();
      final BigInteger channelRefId = channel.channelRefId;
      final byte deliveryOrdinal = (byte) channel.delivery.ordinal();
      for (final long nodeId : channel.servers) {
    channelService.addChannelTask(
        channelRefId,
        new IoRunnable() {
      public void run() throws IOException {
          ChannelServer server = getChannelServer(nodeId);
          if (server != null) {
        server.send(channelRefId, message,
              deliveryOrdinal);
          }
      } },
        nodeId);
      }

      // TBD: need to add a task to update queue that all
      // channel servers have been notified of
      // the 'send'.
  }

  /** Use the message length as the cost for sending messages. */
  @Override
  int getCost() {
      return message.length;
  }

  /** {@inheritDoc} */
        @Override
  public String toString() {
      return getClass().getName();
  }
    }

    /**
     * Returns the write buffer capacity for this channel.
     *
     * @return the write buffer capacity
     */
    int getWriteBufferCapacity() {
        return writeBufferCapacity;
    }

    /**
     * A channel close event.
     */
    private static class CloseEvent extends ChannelEvent {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  /**
   * Constructs a close event.
   */
  CloseEvent() {
  }

  /** {@inheritDoc} */
  public void serviceEvent(ChannelImpl channel) {

      final BigInteger channelRefId = channel.channelRefId;
      Set<Long> serverNodeIds = channel.getMemberNodeIds();
      channel.removeChannel();
      final ChannelServiceImpl channelService =
    ChannelServiceImpl.getChannelService();
      for (final long nodeId : serverNodeIds) {
    channelService.addChannelTask(
        channelRefId,
        new IoRunnable() {
      public void run() throws IOException {
          ChannelServer server = getChannelServer(nodeId);
          if (server != null) {
        server.close(channelRefId);
          }
      } },
        nodeId);
      }
     
      channelService.addChannelTask(
    channelRefId,
    new AbstractKernelRunnable("NotifyChannelClosed") {
        public void run() {
      channelService.closedChannel(channelRefId);
        } });
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
      return getClass().getName();
  }
    }

    /**
     * Returns the event queue for the channel that has the specified
     * {@code channelRefId} and coordinator {@code nodeId}.
     */
    private static EventQueue getEventQueue(
   long nodeId, BigInteger channelRefId)
    {
  EventQueue eventQueue =
      getEventQueuesMap(nodeId).get(channelRefId.toString());
  if (eventQueue == null) {
      logger.log(
    Level.WARNING,
    "Event queue for channel:{0} does not exist",
    HexDumper.toHexString(channelRefId.toByteArray()));
  }
  return eventQueue;
    }

    /* -- Static method invoked by ChannelServiceImpl -- */

    /**
     * Handles a channel {@code message} that the specified {@code session}
     * is sending on the channel with the specified {@code channelRefId}.
     *
     * @param  channelRefId the channel ID, as a {@code BigInteger}
     * @param  session the client session sending the channel message
     * @param  message the channel message
     */
    static void handleChannelMessage(
  BigInteger channelRefId, ClientSession session, ByteBuffer message)
    {
  assert session instanceof ClientSessionWrapper;
  ChannelImpl channel = (ChannelImpl) getObjectForId(channelRefId);
  if (channel != null) {
      channel.receivedMessage(session, message);
  } else {
      // Ignore message received for unknown channel.
      if (logger.isLoggable(Level.FINE)) {
    logger.log(
         Level.FINE,
        "Dropping message:{0}: from:{1} for unknown channel: {2}",
        HexDumper.format(message), session,
        HexDumper.toHexString(channelRefId.toByteArray()));
      }
  }
    }

    /**
     * Services the event queue for the channel with the specified {@code
     * channelRefId}.
     */
    static void serviceEventQueue(BigInteger channelRefId) {
  EventQueue eventQueue = getEventQueue(getLocalNodeId(), channelRefId);
  if (eventQueue != null) {
      eventQueue.serviceEvent();
  }
    }

    /**
     * Returns an iterator for channels that are coordinated on the node with
     * the specified {@code nodeId}.
     */
    private static Iterator<String> getChannelsIterator(long nodeId) {
  return getEventQueuesMap(nodeId).keySet().iterator();
    }
   
    /**
     * A persistent task to reassign channel coordinators on a failed node
     * to another node. In a single task, only one failed coordinator is
     * reassigned.  A task for one coordinator schedules a task for the
     * next reassignment, if there are coordinators on the failed node left
     * to be reassigned.
     */
    static class ReassignCoordinatorsTask
  implements Task, Serializable
    {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;

  /** The node ID of the failed node. */
  private final long failedNodeId;

  /** The iterator for channels on the failed node. */
  private final Iterator<String> channelIter;

  /**
   * Constructs an instance of this class with the specified
   * {@code failedNodeId}.
   */
  ReassignCoordinatorsTask(long failedNodeId) {
      this.failedNodeId = failedNodeId;
      this.channelIter = getChannelsIterator(failedNodeId);
  }

  /**
   * Reassigns the next coordinator for the {@code failedNodeId} to
   * another node with member sessions (or the local node if there
   * are no member sessions), schedules a task to remove failed
   * sessions for the channel, and then reschedules this task to
   * reassign the next coordinator.  If there are no more
   * coordinators for the specified {@code failedNodeId}, then no
   * action is taken.
   */
  public void run() {
      if (!channelIter.hasNext()) {
    return;
      }

      WatchdogService watchdogService =
    ChannelServiceImpl.getWatchdogService();
      TaskService taskService = ChannelServiceImpl.getTaskService();
      BigInteger channelRefId = new BigInteger(channelIter.next());
      channelIter.remove();
      ChannelImpl channel = (ChannelImpl) getObjectForId(channelRefId);
      if (channel != null) {
    channel.reassignCoordinator(failedNodeId);
   
    /*
     * If other channel servers have failed, remove their sessions
     * from the channel too.  This covers the case where a channel
     * coordinator (informed of a node failure) fails before it has
     * a chance to schedule a task to remove member sessions for
     * another failed node (cascading failure during recovery).
     */
    for (long serverNodeId : channel.getMemberNodeIds()) {
        Node serverNode = watchdogService.getNode(serverNodeId);
        if (serverNode == null || !serverNode.isAlive()) {
      channel.removeSessionSet(serverNodeId);
        }
    }
      }

      /*
       * Schedule a task to reassign the next channel coordinator, or
       * if done with coordinator reassignment, remove the recovered
       * node's  mapping from the node-to-event-queues map.
       */
      if (channelIter.hasNext()) {
    taskService.scheduleTask(this);
      }
  }
    }

    /**
     * A persistent task to remove sessions, connected to a failed node,
     * from locally coordinated channels.  This task handles a single
     * locally-coordinated channel, and then schedules another task to
     * handle the next one.
     */
    static class RemoveFailedSessionsFromLocalChannelsTask
  implements Task, Serializable
    {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;
 
  /** The failed node. */
  private final long failedNodeId;
 
  /** The iterator for locally-coordinated channels.*/
  private final Iterator<String> iter;
 
  /**
   * Constructs an instance with the specified {@code localNodeId}
   * and {@code failedNodeId}.
   */
  RemoveFailedSessionsFromLocalChannelsTask(
      long localNodeId, long failedNodeId)
  {
      this.failedNodeId = failedNodeId;
      this.iter = getChannelsIterator(localNodeId);
  }

  /**
   * Finds the next locally-coordinated channel and schedules a
   * task to remove the failed sessions from that channel, and
   * reschedules this task to handle the next locally-coordinated
   * channel. If there are no more locally-coordinated channels,
   * then this task takes no action.
   */
  public void run() {
      if (iter == null || !iter.hasNext()) {
    return;
      }
     
      BigInteger channelRefId = new BigInteger(iter.next());
      TaskService taskService = ChannelServiceImpl.getTaskService();
      taskService.scheduleTask(
    new RemoveFailedSessionsFromChannelTask(
        failedNodeId, channelRefId));
       
      // Schedule a task to remove failed sessions from next
      // locally coordinated channel.
      if (iter.hasNext()) {
    taskService.scheduleTask(this);
      }
  }
    }

    /**
     * A persistent task to remove all failed sessions on a given node
     * for a channel.
     */
    private static class RemoveFailedSessionsFromChannelTask
  implements Task, Serializable
    {
  /** The serialVersionUID for this class. */
  private static final long serialVersionUID = 1L;
 
  private final BigInteger channelRefId;
  private final long failedNodeId;
 
  /** Constructs an instance. */
  RemoveFailedSessionsFromChannelTask(
       long failedNodeId, BigInteger channelRefId)
  {
      this.channelRefId = channelRefId;
      this.failedNodeId = failedNodeId;
  }

  /** {@inheritDoc} */
  public void run() {
      ChannelImpl channel =
    (ChannelImpl) getObjectForId(channelRefId);
      channel.removeSessionSet(failedNodeId);
  }
    }

    /**
     * Returns a set containing session identifiers (as obtained by
     * {@link ManagedReference#getId ManagedReference.getId}) for all
     * sessions that are a member of the channel with the specified
     * {@code channelRefId} and are last known to have been connected
     * to node {@code nodeId}.
     */
    static Set<BigInteger> getSessionRefIdsForNode(
  BigInteger channelRefId, long nodeId)
    {
  ChannelImpl channel = (ChannelImpl) getObjectForId(channelRefId);
  return channel.getSessionSet(nodeId);
    }
}
TOP

Related Classes of com.sun.sgs.impl.service.channel.ChannelImpl

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.