예제 #1
0
 public Q(int limit) {
     this.q = new LinkedList<>();
     this.limit = limit;
     this.lock = new ReentrantLock();
     this.full = lock.newCondition();
     this.empty = lock.newCondition();
 }
public class ConcatThread extends Thread {

  private static String text = ""; // global variable
  private String toe;
  private Lock lock = new ReentrantLock();
  private Condition hasconcat = lock.newCondition();
  private boolean isBusy = false;

  public ConcatThread(String toeArg) {
    this.toe = toeArg;
  }

  public void run() {
    text = text.concat(toe);
  }

  public static void main(String[] args) {
    (new ConcatThread("one;")).start();
    try {
      sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    (new ConcatThread("two;")).start();
    try {
      sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println(text);
  }
}
예제 #3
0
public class OneMoreSemaphore {

  private Lock lock = new ReentrantLock();
  private Condition queue = lock.newCondition();
  private int permits;
  private int counter = 0;

  OneMoreSemaphore(int p) {
    permits = p;
  }

  public void acquire() throws InterruptedException {
    lock.lock();
    try {
      while (!(counter < permits)) queue.await();
      ++counter;
    } finally {
      lock.unlock();
    }
  }

  public void release() {
    lock.lock();
    try {
      if (counter > 0) --counter;
    } finally {
      lock.unlock();
    }
  }
}
예제 #4
0
  public Runway() {
    L = new ReentrantLock();
    C = L.newCondition();

    wind_direction = 0;
    runway_number = 0;
    turn = 0;
    runway_direction = "";
  }
예제 #5
0
  // An inner class for account
  private static class Account {
    private static Lock lock = new ReentrantLock(); // Create a new lock

    // Create a condition
    private static Condition newDeposit = lock.newCondition();

    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void withdraw(int amount) {
      lock.lock(); // Acquire the lock
      try {
        while (balance < amount) {
          System.out.println("\t\t\tWait for a deposit");
          newDeposit.await();
        }

        balance -= amount;
        System.out.println("\t\t\tWithdraw " + amount + "\t\t" + getBalance());
      } catch (InterruptedException ex) {
        ex.printStackTrace();
      } finally {
        lock.unlock(); // Release the lock
      }
    }

    public void deposit(int amount) {
      lock.lock(); // Acquire the lock
      try {
        balance += amount;
        System.out.println("Deposit " + amount + "\t\t\t\t\t" + getBalance());

        newDeposit.signalAll();
      } finally {
        lock.unlock(); // Release the lock
      }
    }
  }
public class RandomCharacterGenerator extends Thread implements CharacterSource {
  private static char[] chars;
  private static String charArray = "abcdefghijklmnopqrstuvwxyz0123456789";

  static {
    chars = charArray.toCharArray();
  }

  private Random random;
  private CharacterEventHandler handler;
  private boolean done = true;
  private Lock lock = new ReentrantLock();
  private Condition cv = lock.newCondition();

  public RandomCharacterGenerator() {
    random = new Random();
    handler = new CharacterEventHandler();
  }

  public int getPauseTime() {
    return (int) (Math.max(1000, 5000 * random.nextDouble()));
  }

  public void addCharacterListener(CharacterListener cl) {
    handler.addCharacterListener(cl);
  }

  public void removeCharacterListener(CharacterListener cl) {
    handler.removeCharacterListener(cl);
  }

  public void nextCharacter() {
    handler.fireNewCharacter(this, (int) chars[random.nextInt(chars.length)]);
  }

  public void run() {
    try {
      lock.lock();
      while (true) {
        try {
          if (done) {
            cv.await();
          } else {
            nextCharacter();
            cv.await(getPauseTime(), TimeUnit.MILLISECONDS);
          }
        } catch (InterruptedException ie) {
          return;
        }
      }
    } finally {
      lock.unlock();
    }
  }

  public void setDone(boolean b) {
    try {
      lock.lock();
      done = b;

      if (!done) cv.signal();
    } finally {
      lock.unlock();
    }
  }
}
/** IGFS worker for removal from the trash directory. */
public class IgfsDeleteWorker extends IgfsThread {
  /** Awake frequency, */
  private static final long FREQUENCY = 1000;

  /** How many files/folders to delete at once (i.e in a single transaction). */
  private static final int MAX_DELETE_BATCH = 100;

  /** IGFS context. */
  private final IgfsContext igfsCtx;

  /** Metadata manager. */
  private final IgfsMetaManager meta;

  /** Data manager. */
  private final IgfsDataManager data;

  /** Event manager. */
  private final GridEventStorageManager evts;

  /** Logger. */
  private final IgniteLogger log;

  /** Lock. */
  private final Lock lock = new ReentrantLock();

  /** Condition. */
  private final Condition cond = lock.newCondition();

  /** Force worker to perform actual delete. */
  private boolean force;

  /** Cancellation flag. */
  private volatile boolean cancelled;

  /** Message topic. */
  private Object topic;

  /**
   * Constructor.
   *
   * @param igfsCtx IGFS context.
   */
  IgfsDeleteWorker(IgfsContext igfsCtx) {
    super(
        "igfs-delete-worker%"
            + igfsCtx.igfs().name()
            + "%"
            + igfsCtx.kernalContext().localNodeId()
            + "%");

    this.igfsCtx = igfsCtx;

    meta = igfsCtx.meta();
    data = igfsCtx.data();

    evts = igfsCtx.kernalContext().event();

    String igfsName = igfsCtx.igfs().name();

    topic = F.isEmpty(igfsName) ? TOPIC_IGFS : TOPIC_IGFS.topic(igfsName);

    assert meta != null;
    assert data != null;

    log = igfsCtx.kernalContext().log(IgfsDeleteWorker.class);
  }

  /** {@inheritDoc} */
  @Override
  protected void body() throws InterruptedException {
    if (log.isDebugEnabled()) log.debug("Delete worker started.");

    while (!cancelled) {
      lock.lock();

      try {
        if (!cancelled && !force) cond.await(FREQUENCY, TimeUnit.MILLISECONDS);

        force = false; // Reset force flag.
      } finally {
        lock.unlock();
      }

      if (!cancelled) delete();
    }
  }

  /** Notify the worker that new entry to delete appeared. */
  void signal() {
    lock.lock();

    try {
      force = true;

      cond.signalAll();
    } finally {
      lock.unlock();
    }
  }

  void cancel() {
    cancelled = true;

    interrupt();
  }

  /** Perform cleanup of the trash directory. */
  private void delete() {
    IgfsFileInfo info = null;

    try {
      info = meta.info(TRASH_ID);
    } catch (ClusterTopologyServerNotFoundException e) {
      LT.warn(log, e, "Server nodes not found.");
    } catch (IgniteCheckedException e) {
      U.error(log, "Cannot obtain trash directory info.", e);
    }

    if (info != null) {
      for (Map.Entry<String, IgfsListingEntry> entry : info.listing().entrySet()) {
        IgniteUuid fileId = entry.getValue().fileId();

        if (log.isDebugEnabled())
          log.debug(
              "Deleting IGFS trash entry [name=" + entry.getKey() + ", fileId=" + fileId + ']');

        try {
          if (!cancelled) {
            if (delete(entry.getKey(), fileId)) {
              if (log.isDebugEnabled())
                log.debug(
                    "Sending delete confirmation message [name="
                        + entry.getKey()
                        + ", fileId="
                        + fileId
                        + ']');

              sendDeleteMessage(new IgfsDeleteMessage(fileId));
            }
          } else break;
        } catch (IgniteInterruptedCheckedException ignored) {
          // Ignore this exception while stopping.
        } catch (IgniteCheckedException e) {
          U.error(log, "Failed to delete entry from the trash directory: " + entry.getKey(), e);

          sendDeleteMessage(new IgfsDeleteMessage(fileId, e));
        }
      }
    }
  }

  /**
   * Remove particular entry from the TRASH directory.
   *
   * @param name Entry name.
   * @param id Entry ID.
   * @return {@code True} in case the entry really was deleted form the file system by this call.
   * @throws IgniteCheckedException If failed.
   */
  private boolean delete(String name, IgniteUuid id) throws IgniteCheckedException {
    assert name != null;
    assert id != null;

    while (true) {
      IgfsFileInfo info = meta.info(id);

      if (info != null) {
        if (info.isDirectory()) {
          deleteDirectory(TRASH_ID, id);

          if (meta.delete(TRASH_ID, name, id)) return true;
        } else {
          assert info.isFile();

          // Delete file content first.
          // In case this node crashes, other node will re-delete the file.
          data.delete(info).get();

          boolean ret = meta.delete(TRASH_ID, name, id);

          if (evts.isRecordable(EVT_IGFS_FILE_PURGED)) {
            if (info.path() != null)
              evts.record(
                  new IgfsEvent(
                      info.path(),
                      igfsCtx.kernalContext().discovery().localNode(),
                      EVT_IGFS_FILE_PURGED));
            else LT.warn(log, null, "Removing file without path info: " + info);
          }

          return ret;
        }
      } else return false; // Entry was deleted concurrently.
    }
  }

  /**
   * Remove particular entry from the trash directory or subdirectory.
   *
   * @param parentId Parent ID.
   * @param id Entry id.
   * @throws IgniteCheckedException If delete failed for some reason.
   */
  private void deleteDirectory(IgniteUuid parentId, IgniteUuid id) throws IgniteCheckedException {
    assert parentId != null;
    assert id != null;

    while (true) {
      IgfsFileInfo info = meta.info(id);

      if (info != null) {
        assert info.isDirectory();

        Map<String, IgfsListingEntry> listing = info.listing();

        if (listing.isEmpty()) return; // Directory is empty.

        Map<String, IgfsListingEntry> delListing;

        if (listing.size() <= MAX_DELETE_BATCH) delListing = listing;
        else {
          delListing = new HashMap<>(MAX_DELETE_BATCH, 1.0f);

          int i = 0;

          for (Map.Entry<String, IgfsListingEntry> entry : listing.entrySet()) {
            delListing.put(entry.getKey(), entry.getValue());

            if (++i == MAX_DELETE_BATCH) break;
          }
        }

        GridCompoundFuture<Object, ?> fut = new GridCompoundFuture<>();

        // Delegate to child folders.
        for (IgfsListingEntry entry : delListing.values()) {
          if (!cancelled) {
            if (entry.isDirectory()) deleteDirectory(id, entry.fileId());
            else {
              IgfsFileInfo fileInfo = meta.info(entry.fileId());

              if (fileInfo != null) {
                assert fileInfo.isFile();

                fut.add(data.delete(fileInfo));
              }
            }
          } else return;
        }

        fut.markInitialized();

        // Wait for data cache to delete values before clearing meta cache.
        try {
          fut.get();
        } catch (IgniteFutureCancelledCheckedException ignore) {
          // This future can be cancelled only due to IGFS shutdown.
          cancelled = true;

          return;
        }

        // Actual delete of folder content.
        Collection<IgniteUuid> delIds = meta.delete(id, delListing);

        if (delListing == listing && delListing.size() == delIds.size())
          break; // All entries were deleted.
      } else break; // Entry was deleted concurrently.
    }
  }

  /**
   * Send delete message to all meta cache nodes in the grid.
   *
   * @param msg Message to send.
   */
  private void sendDeleteMessage(IgfsDeleteMessage msg) {
    assert msg != null;

    Collection<ClusterNode> nodes = meta.metaCacheNodes();

    for (ClusterNode node : nodes) {
      try {
        igfsCtx.send(node, topic, msg, GridIoPolicy.SYSTEM_POOL);
      } catch (IgniteCheckedException e) {
        U.warn(
            log,
            "Failed to send IGFS delete message to node [nodeId="
                + node.id()
                + ", msg="
                + msg
                + ", err="
                + e.getMessage()
                + ']');
      }
    }
  }
}
예제 #8
0
public class LockTest {

  private Lock reentrantLock = new ReentrantLock();
  private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  private Lock readLock = readWriteLock.readLock();
  private Lock writeLock = readWriteLock.writeLock();
  // can not newCondition with readLock
  private Condition condition = reentrantLock.newCondition();
  private List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 0);

  private Runnable task =
      new Runnable() {
        @Override
        public void run() {
          reentrantLock.lock();
          // if pool-1-thread-1 is the last, no thread will notify it;
          while (Thread.currentThread().getName().equals("pool-1-thread-1")) {
            try {
              condition.await();

            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          for (Integer integer : list) {
            System.out.println(integer);
            System.out.println(Thread.currentThread().getName());
          }
          condition.signalAll();
          reentrantLock.unlock();
        }
      };

  private ExecutorService executorService = Executors.newFixedThreadPool(3);

  public void execute() {
    executorService.execute(task);
    executorService.execute(task);
    executorService.execute(task);
    executorService.shutdown();
    System.out.println("---");
  }

  public static void main(String[] args) throws InterruptedException {
    new LockTest().execute();

    new AtomicInteger().incrementAndGet();
    ThreadLocalRandom.current().nextInt();
  }

  public synchronized void test() {
    // do something
    synchronized (this) {
      // do something
      synchronized (this) {
        // do something
        synchronized (this) {
          // do something
        }
      }
    }
  }
}
/**
 * Cache sequence implementation.
 *
 * @author 2012 Copyright (C) GridGain Systems
 * @version 4.0.2c.12042012
 */
public final class GridCacheAtomicSequenceImpl extends GridMetadataAwareAdapter
    implements GridCacheAtomicSequenceEx, Externalizable {
  /** Deserialization stash. */
  private static final ThreadLocal<GridTuple2<GridCacheContext, String>> stash =
      new ThreadLocal<GridTuple2<GridCacheContext, String>>() {
        @Override
        protected GridTuple2<GridCacheContext, String> initialValue() {
          return F.t2();
        }
      };

  /** Logger. */
  private GridLogger log;

  /** Sequence name. */
  private String name;

  /** Removed flag. */
  private volatile boolean rmvd;

  /** Sequence key. */
  private GridCacheInternalStorableKey key;

  /** Sequence projection. */
  private GridCacheProjection<GridCacheInternalStorableKey, GridCacheAtomicSequenceValue> seqView;

  /** Cache context. */
  private volatile GridCacheContext ctx;

  /** Local value of sequence. */
  private long locVal;

  /** Upper bound of local counter. */
  private long upBound;

  /** Sequence batch size */
  private volatile int batchSize;

  /** Synchronization lock. */
  private final Lock lock = new ReentrantLock();

  /** Await condition. */
  private Condition cond = lock.newCondition();

  /** Callable for execution {@link #incrementAndGet} operation in async and sync mode. */
  private final Callable<Long> incAndGetCall = internalUpdate(1, true);

  /** Callable for execution {@link #getAndIncrement} operation in async and sync mode. */
  private final Callable<Long> getAndIncCall = internalUpdate(1, false);

  /** Add and get cache call guard. */
  private final AtomicBoolean updateGuard = new AtomicBoolean();

  /** Empty constructor required by {@link Externalizable}. */
  public GridCacheAtomicSequenceImpl() {
    // No-op.
  }

  /**
   * Default constructor.
   *
   * @param name Sequence name.
   * @param key Sequence key.
   * @param seqView Sequence projection.
   * @param ctx CacheContext.
   * @param locVal Local counter.
   * @param upBound Upper bound.
   */
  public GridCacheAtomicSequenceImpl(
      String name,
      GridCacheInternalStorableKey key,
      GridCacheProjection<GridCacheInternalStorableKey, GridCacheAtomicSequenceValue> seqView,
      GridCacheContext ctx,
      long locVal,
      long upBound) {
    assert key != null;
    assert seqView != null;
    assert ctx != null;
    assert locVal <= upBound;

    batchSize = ctx.config().getAtomicSequenceReserveSize();
    this.ctx = ctx;
    this.key = key;
    this.seqView = seqView;
    this.upBound = upBound;
    this.locVal = locVal;
    this.name = name;

    log = ctx.gridConfig().getGridLogger().getLogger(getClass());
  }

  /** {@inheritDoc} */
  @Override
  public String name() {
    return name;
  }

  /** {@inheritDoc} */
  @Override
  public long get() throws GridException {
    checkRemoved();

    lock.lock();

    try {
      return locVal;
    } finally {
      lock.unlock();
    }
  }

  /** {@inheritDoc} */
  @Override
  public long incrementAndGet() throws GridException {
    return internalUpdate(1, incAndGetCall, true);
  }

  /** {@inheritDoc} */
  @Override
  public long getAndIncrement() throws GridException {
    return internalUpdate(1, getAndIncCall, false);
  }

  /** {@inheritDoc} */
  @Override
  public GridFuture<Long> incrementAndGetAsync() throws GridException {
    return internalUpdateAsync(1, incAndGetCall, true);
  }

  /** {@inheritDoc} */
  @Override
  public GridFuture<Long> getAndIncrementAsync() throws GridException {
    return internalUpdateAsync(1, getAndIncCall, false);
  }

  /** {@inheritDoc} */
  @Override
  public long addAndGet(long l) throws GridException {
    A.ensure(l > 0, " Parameter mustn't be less then 1: " + l);

    return internalUpdate(l, null, true);
  }

  /** {@inheritDoc} */
  @Override
  public GridFuture<Long> addAndGetAsync(long l) throws GridException {
    A.ensure(l > 0, " Parameter mustn't be less then 1: " + l);

    return internalUpdateAsync(l, null, true);
  }

  /** {@inheritDoc} */
  @Override
  public long getAndAdd(long l) throws GridException {
    A.ensure(l > 0, " Parameter mustn't be less then 1: " + l);

    return internalUpdate(l, null, false);
  }

  /** {@inheritDoc} */
  @Override
  public GridFuture<Long> getAndAddAsync(long l) throws GridException {
    A.ensure(l > 0, " Parameter mustn't be less then 1: " + l);

    return internalUpdateAsync(l, null, false);
  }

  /**
   * Synchronous sequence update operation. Will add given amount to the sequence value.
   *
   * @param l Increment amount.
   * @param updateCall Cache call that will update sequence reservation count in accordance with l.
   * @param updated If {@code true}, will return sequence value after update, otherwise will return
   *     sequence value prior to update.
   * @return Sequence value.
   * @throws GridException If update failed.
   */
  private long internalUpdate(long l, @Nullable Callable<Long> updateCall, boolean updated)
      throws GridException {
    checkRemoved();

    assert l > 0;

    lock.lock();

    try {
      // If reserved range isn't exhausted.
      if (locVal + l <= upBound) {
        long curVal = locVal;

        locVal += l;

        return updated ? locVal : curVal;
      }
    } finally {
      lock.unlock();
    }

    if (updateCall == null) updateCall = internalUpdate(l, updated);

    while (true) {
      if (updateGuard.compareAndSet(false, true)) {
        try {
          // This call must be outside lock.
          return CU.outTx(updateCall, ctx);
        } finally {
          lock.lock();

          try {
            updateGuard.set(false);

            cond.signalAll();
          } finally {
            lock.unlock();
          }
        }
      } else {
        lock.lock();

        try {
          while (locVal >= upBound && updateGuard.get()) {
            try {
              cond.await(500, MILLISECONDS);
            } catch (InterruptedException e) {
              throw new GridInterruptedException(e);
            }
          }

          checkRemoved();

          // If reserved range isn't exhausted.
          if (locVal + l <= upBound) {
            long curVal = locVal;

            locVal += l;

            return updated ? locVal : curVal;
          }
        } finally {
          lock.unlock();
        }
      }
    }
  }

  /**
   * Asynchronous sequence update operation. Will add given amount to the sequence value.
   *
   * @param l Increment amount.
   * @param updateCall Cache call that will update sequence reservation count in accordance with l.
   * @param updated If {@code true}, will return sequence value after update, otherwise will return
   *     sequence value prior to update.
   * @return Future indicating sequence value.
   * @throws GridException If update failed.
   */
  private GridFuture<Long> internalUpdateAsync(
      long l, @Nullable Callable<Long> updateCall, boolean updated) throws GridException {
    checkRemoved();

    A.ensure(l > 0, " Parameter mustn't be less then 1: " + l);

    lock.lock();

    try {
      // If reserved range isn't exhausted.
      if (locVal + l <= upBound) {
        long curVal = locVal;

        locVal += l;

        return new GridFinishedFuture<Long>(ctx.kernalContext(), updated ? locVal : curVal);
      }
    } finally {
      lock.unlock();
    }

    if (updateCall == null) updateCall = internalUpdate(l, updated);

    while (true) {
      if (updateGuard.compareAndSet(false, true)) {
        try {
          // This call must be outside lock.
          return ctx.closures().callLocalSafe(updateCall, true);
        } finally {
          lock.lock();

          try {
            updateGuard.set(false);

            cond.signalAll();
          } finally {
            lock.unlock();
          }
        }
      } else {
        lock.lock();

        try {
          while (locVal >= upBound && updateGuard.get()) {
            try {
              cond.await(500, MILLISECONDS);
            } catch (InterruptedException e) {
              throw new GridInterruptedException(e);
            }
          }

          checkRemoved();

          // If reserved range isn't exhausted.
          if (locVal + l <= upBound) {
            long curVal = locVal;

            locVal += l;

            return new GridFinishedFuture<Long>(ctx.kernalContext(), updated ? locVal : curVal);
          }
        } finally {
          lock.unlock();
        }
      }
    }
  }

  /**
   * Get local batch size for this sequences.
   *
   * @return Sequence batch size.
   */
  @Override
  public int batchSize() {
    return batchSize;
  }

  /**
   * Set local batch size for this sequences.
   *
   * @param size Sequence batch size. Must be more then 0.
   */
  @Override
  public void batchSize(int size) {
    A.ensure(size > 0, " Batch size can't be less then 0: " + size);

    lock.lock();

    try {
      batchSize = size;
    } finally {
      lock.unlock();
    }
  }

  /**
   * Check removed status.
   *
   * @throws GridException If removed.
   */
  private void checkRemoved() throws GridException {
    if (rmvd)
      throw new GridCacheDataStructureRemovedException("Sequence was removed from cache: " + name);
  }

  /** {@inheritDoc} */
  @Override
  public boolean onRemoved() {
    return rmvd = true;
  }

  /** {@inheritDoc} */
  @Override
  public void onInvalid(@Nullable Exception err) {
    // No-op.
  }

  /** {@inheritDoc} */
  @Override
  public GridCacheInternalStorableKey key() {
    return key;
  }

  /** {@inheritDoc} */
  @Override
  public boolean removed() {
    return rmvd;
  }

  /**
   * Method returns callable for execution all update operations in async and sync mode.
   *
   * @param l Value will be added to sequence.
   * @param updated If {@code true}, will return updated value, if {@code false}, will return
   *     previous value.
   * @return Callable for execution in async and sync mode.
   */
  @SuppressWarnings("TooBroadScope")
  private Callable<Long> internalUpdate(final long l, final boolean updated) {
    return new Callable<Long>() {
      @Override
      public Long call() throws Exception {
        GridCacheTx tx = CU.txStartInternal(ctx, seqView, PESSIMISTIC, REPEATABLE_READ);

        try {
          GridCacheAtomicSequenceValue seq = seqView.get(key);

          checkRemoved();

          assert seq != null;

          long curLocVal;

          long newUpBound;

          lock.lock();

          try {
            curLocVal = locVal;

            // If local range was already reserved in another thread.
            if (locVal + l <= upBound) {
              long retVal = locVal;

              locVal += l;

              return updated ? locVal : retVal;
            }

            long curGlobalVal = seq.get();

            long newLocVal;

            /* We should use offset because we already reserved left side of range.*/
            long off = batchSize > 1 ? batchSize - 1 : 1;

            // Calculate new values for local counter, global counter and upper bound.
            if (curLocVal + l >= curGlobalVal) {
              newLocVal = curLocVal + l;

              newUpBound = newLocVal + off;
            } else {
              newLocVal = curGlobalVal;

              newUpBound = newLocVal + off;
            }

            locVal = newLocVal;
            upBound = newUpBound;

            if (updated) curLocVal = newLocVal;
          } finally {
            lock.unlock();
          }

          // Global counter must be more than reserved upper bound.
          seq.set(newUpBound + 1);

          seqView.put(key, seq);

          tx.commit();

          return curLocVal;
        } catch (Error e) {
          log.error("Failed to get and add: " + this, e);

          throw e;
        } catch (Exception e) {
          log.error("Failed to get and add: " + this, e);

          throw e;
        } finally {
          tx.end();
        }
      }
    };
  }

  /** {@inheritDoc} */
  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeObject(ctx);
    out.writeUTF(name);
  }

  /** {@inheritDoc} */
  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    stash.get().set1((GridCacheContext) in.readObject());
    stash.get().set2(in.readUTF());
  }

  /**
   * Reconstructs object on demarshalling.
   *
   * @return Reconstructed object.
   * @throws ObjectStreamException Thrown in case of demarshalling error.
   */
  private Object readResolve() throws ObjectStreamException {
    GridTuple2<GridCacheContext, String> t = stash.get();

    try {
      return t.get1().dataStructures().sequence(t.get2(), 0L, false, false);
    } catch (GridException e) {
      throw U.withCause(new InvalidObjectException(e.getMessage()), e);
    }
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridCacheAtomicSequenceImpl.class, this);
  }
}
예제 #10
0
/**
 * The RTPSession object is the core of jlibrtp.
 *
 * <p>One should be instantiated for every communication channel, i.e. if you send voice and video,
 * you should create one for each.
 *
 * <p>The instance holds a participant database, as well as other information about the session.
 * When the application registers with the session, the necessary threads for receiving and
 * processing RTP packets are spawned.
 *
 * <p>RTP Packets are sent synchronously, all other operations are asynchronous.
 *
 * @author Arne Kepp
 */
public class RTPSession {
  private static final Logger logger = LoggerFactory.getLogger(RTPSession.class);

  /**
   * The debug level is final to avoid compilation of if-statements.</br> 0 provides no debugging
   * information, 20 provides everything </br> Debug output is written to System.out</br> Debug
   * level for RTP related things.
   */
  public static final int rtpDebugLevel = 11;
  /**
   * The debug level is final to avoid compilation of if-statements.</br> 0 provides no debugging
   * information, 20 provides everything </br> Debug output is written to System.out</br> Debug
   * level for RTCP related things.
   */
  public static final int rtcpDebugLevel = 0;

  /** RTP unicast socket */
  protected DatagramSocket rtpSock = null;
  /** RTP multicast socket */
  protected MulticastSocket rtpMCSock = null;
  /** RTP multicast group */
  protected InetAddress mcGroup = null;

  // Internal state
  /** Whether this session is a multicast session or not */
  protected boolean mcSession = false;
  /** Current payload type, can be changed by application */
  protected int payloadType = 0;
  /** SSRC of this session */
  protected long ssrc;
  /** The last timestamp when we sent something */
  protected long lastTimestamp = 0;
  /** Current sequence number */
  protected int seqNum = 0;
  /** Number of packets sent by this session */
  protected int sentPktCount = 0;
  /** Number of octets sent by this session */
  protected int sentOctetCount = 0;

  /** The random seed */
  protected Random random = null;

  /** Session bandwidth in BYTES per second */
  protected int bandwidth = 8000;

  /** By default we do not return packets from strangers in unicast mode */
  protected boolean naiveReception = false;

  /** Should the library attempt frame reconstruction? */
  protected boolean frameReconstruction = true;

  /** Maximum number of packets used for reordering */
  protected int pktBufBehavior = 3;

  /** Participant database */
  protected ParticipantDatabase partDb = new ParticipantDatabase(this);
  /** Handle to application interface for RTP */
  protected RTPAppIntf appIntf = null;
  /** Handle to application interface for RTCP (optional) */
  protected RTCPAppIntf rtcpAppIntf = null;
  /** Handle to application interface for AVPF, RFC 4585 (optional) */
  protected RTCPAVPFIntf rtcpAVPFIntf = null;
  /** Handle to application interface for debugging */
  protected DebugAppIntf debugAppIntf = null;

  /** The RTCP session associated with this RTP Session */
  protected RTCPSession rtcpSession = null;
  /** The thread for receiving RTP packets */
  protected RTPReceiverThread recvThrd = null;
  /** The thread for invoking callbacks for RTP packets */
  protected AppCallerThread appCallerThrd = null;

  /** Lock to protect the packet buffers */
  protected final Lock pktBufLock = new ReentrantLock();
  /** Condition variable, to tell the */
  protected final Condition pktBufDataReady = pktBufLock.newCondition();

  /** Enough is enough, set to true when you want to quit. */
  protected boolean endSession = false;
  /** Only one registered application, please */
  protected boolean registered = false;
  /** We're busy resolving a SSRC conflict, please try again later */
  protected boolean conflict = false;
  /** Number of conflicts observed, exessive number suggests loop in network */
  protected int conflictCount = 0;

  /** SDES CNAME */
  protected String cname = null;
  /** SDES The participant's real name */
  public String name = null;
  /** SDES The participant's email */
  public String email = null;
  /** SDES The participant's phone number */
  public String phone = null;
  /** SDES The participant's location */
  public String loc = null;
  /** SDES The tool the participants is using */
  public String tool = null;
  /** SDES A note */
  public String note = null;
  /** SDES A priv string, loosely defined */
  public String priv = null;

  // RFC 4585 stuff. This should live on RTCPSession, but we need to have this
  // infromation ready by the time the RTCP Session starts
  // 0 = RFC 3550 , -1 = ACK , 1 = Immediate feedback, 2 = Early RTCP,
  protected int rtcpMode = 0;
  protected int fbEarlyThreshold = -1; // group size, immediate -> early transition point
  protected int fbRegularThreshold = -1; // group size, early -> regular transition point
  protected int minInterval = 5000; // minimum interval
  protected int fbMaxDelay = 1000; // how long the information is useful
  // RTCP bandwidth
  protected int rtcpBandwidth = -1;

  /**
   * Returns an instance of a <b>unicast</b> RTP session. Following this you should adjust any
   * settings and then register your application.
   *
   * <p>The sockets should have external ip addresses, else your CNAME automatically generated CNAMe
   * will be bad.
   *
   * @param rtpSocket UDP socket to receive RTP communication on
   * @param rtcpSocket UDP socket to receive RTCP communication on, null if none.
   */
  public RTPSession(DatagramSocket rtpSocket, DatagramSocket rtcpSocket) {
    mcSession = false;
    rtpSock = rtpSocket;
    this.generateCNAME();
    this.generateSsrc();
    this.rtcpSession = new RTCPSession(this, rtcpSocket);

    // The sockets are not always imediately available?
    try {
      Thread.sleep(1);
    } catch (InterruptedException e) {
      System.out.println("RTPSession sleep failed");
    }
  }

  /**
   * Returns an instance of a <b>multicast</b> RTP session. Following this you should register your
   * application.
   *
   * <p>The sockets should have external ip addresses, else your CNAME automatically generated CNAMe
   * will be bad.
   *
   * @param rtpSock a multicast socket to receive RTP communication on
   * @param rtcpSock a multicast socket to receive RTP communication on
   * @param multicastGroup the multicast group that we want to communicate with.
   */
  public RTPSession(MulticastSocket rtpSock, MulticastSocket rtcpSock, InetAddress multicastGroup)
      throws Exception {
    mcSession = true;
    rtpMCSock = rtpSock;
    mcGroup = multicastGroup;
    rtpMCSock.joinGroup(mcGroup);
    rtcpSock.joinGroup(mcGroup);
    this.generateCNAME();
    this.generateSsrc();
    this.rtcpSession = new RTCPSession(this, rtcpSock, mcGroup);

    // The sockets are not always imediately available?
    try {
      Thread.sleep(1);
    } catch (InterruptedException e) {
      System.out.println("RTPSession sleep failed");
    }
  }

  /**
   * Registers an application (RTPAppIntf) with the RTP session. The session will call receiveData()
   * on the supplied instance whenever data has been received.
   *
   * <p>Following this you should set the payload type and add participants to the session.
   *
   * @param rtpApp an object that implements the RTPAppIntf-interface
   * @param rtcpApp an object that implements the RTCPAppIntf-interface (optional)
   * @return -1 if this RTPSession-instance already has an application registered.
   */
  public int RTPSessionRegister(RTPAppIntf rtpApp, RTCPAppIntf rtcpApp, DebugAppIntf debugApp) {
    if (registered) {
      logger.error("Can\'t register another application!");
      return -1;
    } else {
      registered = true;
      generateSeqNum();

      this.appIntf = rtpApp;
      this.rtcpAppIntf = rtcpApp;
      this.debugAppIntf = debugApp;

      recvThrd = new RTPReceiverThread(this);
      appCallerThrd = new AppCallerThread(this, rtpApp);
      recvThrd.start();
      appCallerThrd.start();
      rtcpSession.start();
      return 0;
    }
  }

  /**
   * Send data to all participants registered as receivers, using the current timeStamp, dynamic
   * sequence number and the current payload type specified for the session.
   *
   * @param buf A buffer of bytes, less than 1496 bytes
   * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise
   */
  public long[] sendData(byte[] buf) {
    byte[][] tmp = {buf};
    long[][] ret = this.sendData(tmp, null, null, -1, null);

    if (ret != null) return ret[0];

    return null;
  }

  /**
   * Send data to all participants registered as receivers, using the specified timeStamp, sequence
   * number and the current payload type specified for the session.
   *
   * @param buf A buffer of bytes, less than 1496 bytes
   * @param rtpTimestamp the RTP timestamp to be used in the packet
   * @param seqNum the sequence number to be used in the packet
   * @return null if there was a problem, {RTP Timestamp, Sequence number} otherwise
   */
  public long[] sendData(byte[] buf, long rtpTimestamp, long seqNum) {
    byte[][] tmp = {buf};
    long[][] ret = this.sendData(tmp, null, null, -1, null);

    if (ret != null) return ret[0];

    return null;
  }

  /**
   * Send data to all participants registered as receivers, using the current timeStamp and payload
   * type. The RTP timestamp will be the same for all the packets.
   *
   * @param buffers A buffer of bytes, should not bed padded and less than 1500 bytes on most
   *     networks.
   * @param csrcArray an array with the SSRCs of contributing sources
   * @param markers An array indicating what packets should be marked. Rarely anything but the first
   *     one
   * @param rtpTimestamp The RTP timestamp to be applied to all packets
   * @param seqNumbers An array with the sequence number associated with each byte[]
   * @return null if there was a problem sending the packets, 2-dim array with {RTP Timestamp,
   *     Sequence number}
   */
  public long[][] sendData(
      byte[][] buffers, long[] csrcArray, boolean[] markers, long rtpTimestamp, long[] seqNumbers) {
    logger.debug("-> RTPSession.sendData(byte[])");

    // Same RTP timestamp for all
    if (rtpTimestamp < 0) rtpTimestamp = System.currentTimeMillis();

    // Return values
    long[][] ret = new long[buffers.length][2];

    for (int i = 0; i < buffers.length; i++) {
      byte[] buf = buffers[i];

      boolean marker = false;
      if (markers != null) marker = markers[i];

      if (buf.length > 1500) {
        System.out.println(
            "RTPSession.sendData() called with buffer exceeding 1500 bytes (" + buf.length + ")");
      }

      // Get the return values
      ret[i][0] = rtpTimestamp;
      if (seqNumbers == null) {
        ret[i][1] = getNextSeqNum();
      } else {
        ret[i][1] = seqNumbers[i];
      }
      // Create a new RTP Packet
      RtpPkt pkt = new RtpPkt(rtpTimestamp, this.ssrc, (int) ret[i][1], this.payloadType, buf);

      if (csrcArray != null) pkt.setCsrcs(csrcArray);

      pkt.setMarked(marker);

      // Creates a raw packet
      byte[] pktBytes = pkt.encode();

      // System.out.println(Integer.toString(StaticProcs.bytesToUIntInt(pktBytes, 2)));

      // Pre-flight check, are resolving an SSRC conflict?
      if (this.conflict) {
        System.out.println("RTPSession.sendData() called while trying to resolve conflict.");
        return null;
      }

      if (this.mcSession) {
        DatagramPacket packet = null;

        try {
          packet =
              new DatagramPacket(pktBytes, pktBytes.length, this.mcGroup, this.rtpMCSock.getPort());
        } catch (Exception e) {
          System.out.println("RTPSession.sendData() packet creation failed.");
          e.printStackTrace();
          return null;
        }

        try {
          rtpMCSock.send(packet);
          // Debug
          if (this.debugAppIntf != null) {
            this.debugAppIntf.packetSent(
                1,
                (InetSocketAddress) packet.getSocketAddress(),
                new String(
                    "Sent multicast RTP packet of size "
                        + packet.getLength()
                        + " to "
                        + packet.getSocketAddress().toString()
                        + " via "
                        + rtpMCSock.getLocalSocketAddress().toString()));
          }
        } catch (Exception e) {
          System.out.println("RTPSession.sendData() multicast failed.");
          e.printStackTrace();
          return null;
        }

      } else {
        // Loop over recipients
        Iterator<Participant> iter = partDb.getUnicastReceivers();
        while (iter.hasNext()) {
          InetSocketAddress receiver = iter.next().rtpAddress;
          DatagramPacket packet = null;

          logger.debug("   Sending to {}", receiver);

          try {
            packet = new DatagramPacket(pktBytes, pktBytes.length, receiver);
          } catch (Exception e) {
            System.out.println("RTPSession.sendData() packet creation failed.");
            e.printStackTrace();
            return null;
          }

          // Actually send the packet
          try {
            rtpSock.send(packet);
            // Debug
            if (this.debugAppIntf != null) {
              this.debugAppIntf.packetSent(
                  0,
                  (InetSocketAddress) packet.getSocketAddress(),
                  new String(
                      "Sent unicast RTP packet of size "
                          + packet.getLength()
                          + " to "
                          + packet.getSocketAddress().toString()
                          + " via "
                          + rtpSock.getLocalSocketAddress().toString()));
            }
          } catch (Exception e) {
            System.out.println("RTPSession.sendData() unicast failed.");
            e.printStackTrace();
            return null;
          }
        }
      }

      // Update our stats
      this.sentPktCount++;
      this.sentOctetCount++;

      logger.info("<- RTPSession.sendData(byte[])", pkt.getSeqNumber());
    }

    return ret;
  }

  /**
   * Send RTCP App packet to receiver specified by ssrc
   *
   * <p>Return values: 0 okay -1 no RTCP session established -2 name is not byte[4]; -3 data is not
   * byte[x], where x = 4*y for syme y -4 type is not a 5 bit unsigned integer
   *
   * <p>Note that a return value of 0 does not guarantee delivery. The participant must also exist
   * in the participant database, otherwise the message will eventually be deleted.
   *
   * @param ssrc of the participant you want to reach
   * @param type the RTCP App packet subtype, default 0
   * @param name the ASCII (in byte[4]) representation
   * @param data the data itself
   * @return 0 if okay, negative value otherwise (see above)
   */
  public int sendRTCPAppPacket(long ssrc, int type, byte[] name, byte[] data) {
    if (this.rtcpSession == null) return -1;

    if (name.length != 4) return -2;

    if (data.length % 4 != 0) return -3;

    if (type > 63 || type < 0) return -4;

    RtcpPktAPP pkt = new RtcpPktAPP(ssrc, type, name, data);
    this.rtcpSession.addToAppQueue(ssrc, pkt);

    return 0;
  }
  /**
   * Add a participant object to the participant database.
   *
   * <p>If packets have already been received from this user, we will try to update the
   * automatically inserted participant with the information provided here.
   *
   * @param p A participant.
   */
  public int addParticipant(Participant p) {
    // For now we make all participants added this way persistent
    p.unexpected = false;
    return this.partDb.addParticipant(0, p);
  }

  /**
   * Remove a participant from the database. All buffered packets will be destroyed.
   *
   * @param p A participant.
   */
  public void removeParticipant(Participant p) {
    partDb.removeParticipant(p);
  }

  public Iterator<Participant> getUnicastReceivers() {
    return partDb.getUnicastReceivers();
  }

  public Enumeration<Participant> getParticipants() {
    return partDb.getParticipants();
  }

  /**
   * End the RTP Session. This will halt all threads and send bye-messages to other participants.
   *
   * <p>RTCP related threads may require several seconds to wake up and terminate.
   */
  public void endSession() {
    this.endSession = true;

    // No more RTP packets, please
    if (this.mcSession) {
      this.rtpMCSock.close();
    } else {
      this.rtpSock.close();
    }

    // Signal the thread that pushes data to application
    this.pktBufLock.lock();
    try {
      this.pktBufDataReady.signalAll();
    } finally {
      this.pktBufLock.unlock();
    }
    // Interrupt what may be sleeping
    this.rtcpSession.senderThrd.interrupt();

    // Give things a chance to cool down.
    try {
      Thread.sleep(50);
    } catch (Exception e) {
    }
    ;

    this.appCallerThrd.interrupt();

    // Give things a chance to cool down.
    try {
      Thread.sleep(50);
    } catch (Exception e) {
    }
    ;

    if (this.rtcpSession != null) {
      // No more RTP packets, please
      if (this.mcSession) {
        this.rtcpSession.rtcpMCSock.close();
      } else {
        this.rtcpSession.rtcpSock.close();
      }
    }
  }

  /**
   * Check whether this session is ending.
   *
   * @return true if session and associated threads are terminating.
   */
  boolean isEnding() {
    return this.endSession;
  }

  /**
   * Overrides CNAME, used for outgoing RTCP packets.
   *
   * @param cname a string, e.g. username@hostname. Must be unique for session.
   */
  public void CNAME(String cname) {
    this.cname = cname;
  }

  /** Get the current CNAME, used for outgoing SDES packets */
  public String CNAME() {
    return this.cname;
  }

  public long getSsrc() {
    return this.ssrc;
  }

  private void generateCNAME() {
    String hostname;

    if (this.mcSession) {
      hostname = this.rtpMCSock.getLocalAddress().getCanonicalHostName();
    } else {
      hostname = this.rtpSock.getLocalAddress().getCanonicalHostName();
    }

    // if(hostname.equals("0.0.0.0") && System.getenv("HOSTNAME") != null) {
    //	hostname = System.getenv("HOSTNAME");
    // }

    cname = System.getProperty("user.name") + "@" + hostname;
  }

  /**
   * Change the RTP socket of the session. Peers must be notified through SIP or other signalling
   * protocol. Only valid if this is a unicast session to begin with.
   *
   * @param newSock integer for new port number, check it is free first.
   */
  public int updateRTPSock(DatagramSocket newSock) {
    if (!mcSession) {
      rtpSock = newSock;
      return 0;
    } else {
      System.out.println("Can't switch from multicast to unicast.");
      return -1;
    }
  }

  /**
   * Change the RTCP socket of the session. Peers must be notified through SIP or other signalling
   * protocol. Only valid if this is a unicast session to begin with.
   *
   * @param newSock the new unicast socket for RTP communication.
   */
  public int updateRTCPSock(DatagramSocket newSock) {
    if (!mcSession) {
      this.rtcpSession.rtcpSock = newSock;
      return 0;
    } else {
      System.out.println("Can't switch from multicast to unicast.");
      return -1;
    }
  }

  /**
   * Change the RTP multicast socket of the session. Peers must be notified through SIP or other
   * signalling protocol. Only valid if this is a multicast session to begin with.
   *
   * @param newSock the new multicast socket for RTP communication.
   */
  public int updateRTPSock(MulticastSocket newSock) {
    if (mcSession) {
      this.rtpMCSock = newSock;
      return 0;
    } else {
      System.out.println("Can't switch from unicast to multicast.");
      return -1;
    }
  }

  /**
   * Change the RTCP multicast socket of the session. Peers must be notified through SIP or other
   * signalling protocol. Only valid if this is a multicast session to begin with.
   *
   * @param newSock the new multicast socket for RTCP communication.
   */
  public int updateRTCPSock(MulticastSocket newSock) {
    if (mcSession) {
      this.rtcpSession.rtcpMCSock = newSock;
      return 0;
    } else {
      System.out.println("Can't switch from unicast to multicast.");
      return -1;
    }
  }

  /**
   * Update the payload type used for the session. It is represented as a 7 bit integer, whose
   * meaning must be negotiated elsewhere (see IETF RFCs <a
   * href="http://www.ietf.org/rfc/rfc3550.txt">3550</a> and <a
   * href="http://www.ietf.org/rfc/rfc3550.txt">3551</a>)
   *
   * @param payloadT an integer representing the payload type of any subsequent packets that are
   *     sent.
   */
  public int payloadType(int payloadT) {
    if (payloadT > 128 || payloadT < 0) {
      return -1;
    } else {
      this.payloadType = payloadT;
      return this.payloadType;
    }
  }

  /**
   * Get the payload type that is currently used for outgoing RTP packets.
   *
   * @return payload type as integer
   */
  public int payloadType() {
    return this.payloadType;
  }

  /**
   * Should packets from unknown participants be returned to the application? This can be dangerous.
   *
   * @param doAccept packets from participants not added by the application.
   */
  public void naivePktReception(boolean doAccept) {
    naiveReception = doAccept;
  }

  /**
   * Are packets from unknown participants returned to the application?
   *
   * @return whether we accept packets from participants not added by the application.
   */
  public boolean naivePktReception() {
    return naiveReception;
  }

  /**
   * Set the number of RTP packets that should be buffered when a packet is missing or received out
   * of order. Setting this number high increases the chance of correctly reordering packets, but
   * increases latency when a packet is dropped by the network.
   *
   * <p>Packets that arrive in order are not affected, they are passed straight to the application.
   *
   * <p>The maximum delay is numberofPackets * packet rate , where the packet rate depends on the
   * codec and profile used by the sender.
   *
   * <p>Valid values: >0 - The maximum number of packets (based on RTP Timestamp) that may
   * accumulate 0 - All valid packets received in order will be given to the application -1 - All
   * valid packets will be given to the application
   *
   * @param behavior the be
   * @return the behavior set, unchanged in the case of a erroneous value
   */
  public int packetBufferBehavior(int behavior) {
    if (behavior > -2) {
      this.pktBufBehavior = behavior;
      // Signal the thread that pushes data to application
      this.pktBufLock.lock();
      try {
        this.pktBufDataReady.signalAll();
      } finally {
        this.pktBufLock.unlock();
      }
      return this.pktBufBehavior;
    } else {
      return this.pktBufBehavior;
    }
  }

  /**
   * The number of RTP packets that should be buffered when a packet is missing or received out of
   * order. A high number increases the chance of correctly reordering packets, but increases
   * latency when a packet is dropped by the network.
   *
   * <p>A negative value disables the buffering, out of order packets will simply be dropped.
   *
   * @return the maximum number of packets that can accumulate before the first is returned
   */
  public int packetBufferBehavior() {
    return this.pktBufBehavior;
  }

  /**
   * Set whether the stack should operate in RFC 4585 mode.
   *
   * <p>This will automatically call adjustPacketBufferBehavior(-1), i.e. disable all RTP packet
   * buffering in jlibrtp, and disable frame reconstruction
   *
   * @param rtcpAVPFIntf the in
   */
  public int registerAVPFIntf(
      RTCPAVPFIntf rtcpAVPFIntf, int maxDelay, int earlyThreshold, int regularThreshold) {
    if (this.rtcpSession != null) {
      this.packetBufferBehavior(-1);
      this.frameReconstruction = false;
      this.rtcpAVPFIntf = rtcpAVPFIntf;
      this.fbEarlyThreshold = earlyThreshold;
      this.fbRegularThreshold = regularThreshold;
      return 0;
    } else {
      return -1;
    }
  }

  /**
   * Unregisters the RTCP AVPF interface, thereby going from RFC 4585 mode to RFC 3550
   *
   * <p>You still have to adjust packetBufferBehavior() and frameReconstruction.
   */
  public void unregisterAVPFIntf() {
    this.fbEarlyThreshold = -1;
    this.fbRegularThreshold = -1;
    this.rtcpAVPFIntf = null;
  }

  /**
   * Enable / disable frame reconstruction in the packet buffers. This is only relevant if
   * getPacketBufferBehavior > 0;
   *
   * <p>Default is true.
   */
  public void frameReconstruction(boolean toggle) {
    this.frameReconstruction = toggle;
  }

  /**
   * Whether the packet buffer will attempt to reconstruct packet automatically.
   *
   * @return the status
   */
  public boolean frameReconstruction() {
    return this.frameReconstruction;
  }

  /**
   * The bandwidth currently allocated to the session, in bytes per second. The default is 8000.
   *
   * <p>This value is not enforced and currently only used to calculate the RTCP interval to ensure
   * the control messages do not exceed 5% of the total bandwidth described here.
   *
   * <p>Since the actual value may change a conservative estimate should be used to avoid RTCP
   * flooding.
   *
   * <p>see rtcpBandwidth(void)
   *
   * @return current bandwidth setting
   */
  public int sessionBandwidth() {
    return this.bandwidth;
  }

  /**
   * Set the bandwidth of the session.
   *
   * <p>See sessionBandwidth(void) for details.
   *
   * @param bandwidth the new value requested, in bytes per second
   * @return the actual value set
   */
  public int sessionBandwidth(int bandwidth) {
    if (bandwidth < 1) {
      this.bandwidth = 8000;
    } else {
      this.bandwidth = bandwidth;
    }
    return this.bandwidth;
  }

  /**
   * RFC 3550 dictates that 5% of the total bandwidth, as set by sessionBandwidth, should be
   * dedicated to RTCP traffic. This
   *
   * <p>This should normally not be done, but is permissible in conjunction with feedback (RFC 4585)
   * and possibly other profiles.
   *
   * <p>Also see sessionBandwidth(void)
   *
   * @return current RTCP bandwidth setting, -1 means not in use
   */
  public int rtcpBandwidth() {
    return this.rtcpBandwidth;
  }

  /**
   * Set the RTCP bandwidth, see rtcpBandwidth(void) for details.
   *
   * <p>This function must be
   *
   * @param bandwidth the new value requested, in bytes per second or -1 to disable
   * @return the actual value set
   */
  public int rtcpBandwidth(int bandwidth) {
    if (bandwidth < -1) {
      this.rtcpBandwidth = -1;
    } else {
      this.rtcpBandwidth = bandwidth;
    }
    return this.rtcpBandwidth;
  }

  /**
   * ******************************************* Feedback message stuff
   * **************************************
   */

  /**
   * Adds a Picture Loss Indication to the feedback queue
   *
   * @param ssrcMediaSource
   * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
   */
  public int fbPictureLossIndication(long ssrcMediaSource) {
    int ret = 0;

    if (this.rtcpAVPFIntf == null) return -1;

    RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
    pkt.makePictureLossIndication();
    ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
    if (ret == 0) this.rtcpSession.wakeSenderThread(ssrcMediaSource);
    return ret;
  }

  /**
   * Adds a Slice Loss Indication to the feedback queue
   *
   * @param ssrcMediaSource
   * @param sliFirst macroblock (MB) address of the first lost macroblock
   * @param sliNumber number of lost macroblocks
   * @param sliPictureId six least significant bits of the codec-specific identif
   * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
   */
  public int fbSlicLossIndication(
      long ssrcMediaSource, int[] sliFirst, int[] sliNumber, int[] sliPictureId) {
    int ret = 0;
    if (this.rtcpAVPFIntf == null) return -1;

    RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
    pkt.makeSliceLossIndication(sliFirst, sliNumber, sliPictureId);

    ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
    if (ret == 0) this.rtcpSession.wakeSenderThread(ssrcMediaSource);
    return ret;
  }

  /**
   * Adds a Reference Picture Selection Indication to the feedback queue
   *
   * @param ssrcMediaSource
   * @param bitPadding number of padded bits at end of bitString
   * @param payloadType RTP payload type for codec
   * @param bitString RPSI information as natively defined by the video codec
   * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
   */
  public int fbRefPictureSelIndic(
      long ssrcMediaSource, int bitPadding, int payloadType, byte[] bitString) {
    int ret = 0;

    if (this.rtcpAVPFIntf == null) return -1;

    RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
    pkt.makeRefPictureSelIndic(bitPadding, payloadType, bitString);
    ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
    if (ret == 0) this.rtcpSession.wakeSenderThread(ssrcMediaSource);
    return ret;
  }

  /**
   * Adds a Picture Loss Indication to the feedback queue
   *
   * @param ssrcMediaSource
   * @param bitString the original application message
   * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
   */
  public int fbAppLayerFeedback(long ssrcMediaSource, byte[] bitString) {
    int ret = 0;

    if (this.rtcpAVPFIntf == null) return -1;

    RtcpPktPSFB pkt = new RtcpPktPSFB(this.ssrc, ssrcMediaSource);
    pkt.makeAppLayerFeedback(bitString);
    ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
    if (ret == 0) this.rtcpSession.wakeSenderThread(ssrcMediaSource);
    return ret;
  }

  /**
   * Adds a RTP Feedback packet to the feedback queue.
   *
   * <p>These are mostly used for NACKs.
   *
   * @param ssrcMediaSource
   * @param FMT the Feedback Message Subtype
   * @param PID RTP sequence numbers of lost packets
   * @param BLP bitmask of following lost packets, shared index with PID
   * @return 0 if packet was queued, -1 if no feedback support, 1 if redundant
   */
  public int fbPictureLossIndication(long ssrcMediaSource, int FMT, int[] PID, int[] BLP) {
    int ret = 0;

    if (this.rtcpAVPFIntf == null) return -1;

    RtcpPktRTPFB pkt = new RtcpPktRTPFB(this.ssrc, ssrcMediaSource, FMT, PID, BLP);
    ret = this.rtcpSession.addToFbQueue(ssrcMediaSource, pkt);
    if (ret == 0) this.rtcpSession.wakeSenderThread(ssrcMediaSource);
    return ret;
  }

  /**
   * Fetches the next sequence number for RTP packets.
   *
   * @return the next sequence number
   */
  private int getNextSeqNum() {
    seqNum++;
    // 16 bit number
    if (seqNum > 65536) {
      seqNum = 0;
    }
    return seqNum;
  }

  /** Initializes a random variable */
  private void createRandom() {
    this.random =
        new Random(
            System.currentTimeMillis()
                + Thread.currentThread().getId()
                - Thread.currentThread().hashCode()
                + this.cname.hashCode());
  }

  /** Generates a random sequence number */
  private void generateSeqNum() {
    if (this.random == null) createRandom();

    seqNum = this.random.nextInt();
    if (seqNum < 0) seqNum = -seqNum;
    while (seqNum > 65535) {
      seqNum = seqNum / 10;
    }
  }

  /** Generates a random SSRC */
  private void generateSsrc() {
    if (this.random == null) createRandom();

    // Set an SSRC
    this.ssrc = this.random.nextInt();
    if (this.ssrc < 0) {
      this.ssrc = this.ssrc * -1;
    }
  }

  /**
   * Resolve an SSRC conflict.
   *
   * <p>Also increments the SSRC conflict counter, after 5 conflicts it is assumed there is a loop
   * somewhere and the session will terminate.
   */
  protected void resolveSsrcConflict() {
    System.out.println("!!!!!!! Beginning SSRC conflict resolution !!!!!!!!!");
    this.conflictCount++;

    if (this.conflictCount < 5) {
      // Don't send any more regular packets out until we have this sorted out.
      this.conflict = true;

      // Send byes
      rtcpSession.sendByes();

      // Calculate the next delay
      rtcpSession.calculateDelay();

      // Generate a new Ssrc for ourselves
      generateSsrc();

      // Get the SDES packets out faster
      rtcpSession.initial = true;

      this.conflict = false;
      System.out.println("SSRC conflict resolution complete");

    } else {
      System.out.println("Too many conflicts. There is probably a loop in the network.");
      this.endSession();
    }
  }
}
예제 #11
0
public class RDPServer implements Runnable {
  RDPServer() {}

  /** rdpserversocket wants to bind on a local port */
  static DatagramChannel bind(Integer port, int receiveBufferSize)
      throws java.net.BindException, java.io.IOException, java.net.SocketException {
    lock.lock();
    try {
      // see if there is an existing datagramchannel bound to this port
      DatagramChannel dc = channelMap.get(port);
      if (dc != null) {
        throw new java.net.BindException("RDPServer.bind: port is already used");
      }

      // make a new datagram channel
      dc = DatagramChannel.open();
      dc.configureBlocking(false);
      dc.socket().setReceiveBufferSize(receiveBufferSize);
      if (port == null) {
        if (Log.loggingNet) Log.net("RDPServer.bind: binding to a random system port");
        dc.socket().bind(null);
      } else {
        if (Log.loggingNet) Log.net("RDPServer.bind: binding to port " + port);
        dc.socket().bind(new InetSocketAddress(port));
      }
      int resultingPort = dc.socket().getLocalPort();
      if (Log.loggingNet) Log.net("RDPServer.bind: resulting port=" + resultingPort);

      // add the channel to the channel map
      channelMap.put(resultingPort, dc);
      if (Log.loggingNet) Log.net("RDPServer.bind: added dc to channel map");

      // add the channel to the newChannelsSet
      // we want to register this channel with the selector
      // but the selector thread needs to do that,
      // so place it in this set, and wake up the selector
      newChannelSet.add(dc);
      if (Log.loggingNet) Log.net("RDPServer.bind: added dc to newChannelSet");

      // in case the rdpserver was waiting while it had no sockets,
      // signal it
      channelMapNotEmpty.signal();
      Log.net("RDPServer.bind: signalled channel map not empty condition");

      // wakeup the selector -
      // it needs to register the new channel with itself
      selector.wakeup();

      if (Log.loggingNet) Log.net("RDPServer.bind: woke up selector");
      return dc;
    } finally {
      lock.unlock();
    }
  }

  /**
   * assume the socket is already bound, now we need to add it to the socket map
   *
   * <p>this map is used when we get a packet and look up the datagramchannel to see if its
   * associated with a listening socket for a new rdp connection
   */
  static void registerSocket(RDPServerSocket rdpSocket, DatagramChannel dc) {
    lock.lock();
    try {
      socketMap.put(dc, rdpSocket);
    } finally {
      lock.unlock();
    }
  }

  /** the conn data should already be set (remote addr, etc) */
  static void registerConnection(RDPConnection con, DatagramChannel dc) {
    lock.lock();
    try {
      if (Log.loggingNet) Log.net("RDPServer.registerConnection: registering con " + con);

      // first we get the set of connections attached to the given dc
      Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc);
      if (dcConMap == null) {
        dcConMap = new HashMap<ConnectionInfo, RDPConnection>();
      }

      // add this connection to the map
      int localPort = con.getLocalPort();
      int remotePort = con.getRemotePort();
      InetAddress remoteAddr = con.getRemoteAddr();
      ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort);
      dcConMap.put(conInfo, con);
      allConMap.put(dc, dcConMap);
    } finally {
      lock.unlock();
    }
  }

  /**
   * removes this connection from the connections map the datagram channel still sticks around in
   * case it needs to be reused
   */
  static void removeConnection(RDPConnection con) {
    lock.lock();
    try {
      if (Log.loggingNet) Log.net("RDPServer.removeConnection: removing con " + con);
      con.setState(RDPConnection.CLOSED);

      DatagramChannel dc = con.getDatagramChannel();

      // first we get the set of connections attached to the given dc
      Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc);
      if (dcConMap == null) {
        throw new MVRuntimeException("RDPServer.removeConnection: cannot find dc");
      }

      int localPort = con.getLocalPort();
      int remotePort = con.getRemotePort();
      InetAddress remoteAddr = con.getRemoteAddr();
      ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort);
      Object rv = dcConMap.remove(conInfo);
      if (rv == null) {
        throw new MVRuntimeException("RDPServer.removeConnection: could not find the connection");
      }

      // close the datagramchannel if needed
      // conditions: no other connections on this datagramchannel
      // no socket listening on this datagramchannel
      if (dcConMap.isEmpty()) {
        Log.net("RDPServer.removeConnection: no other connections for this datagramchannel (port)");
        // there are no more connections on this datagram channel
        // check if there is a serversocket listening
        if (getRDPSocket(dc) == null) {
          Log.net("RDPServer.removeConnection: no socket listening on this port - closing");
          // no socket either, close the datagramchannel
          dc.socket().close();
          channelMap.remove(localPort);
          Log.net("RDPServer.removeConnection: closed and removed datagramchannel/socket");
        } else {
          Log.net("RDPServer.removeConnection: there is a socket listening on this port");
        }
      } else {
        Log.net("RDPServer.removeConnection: there are other connections on this port");
      }
    } finally {
      lock.unlock();
    }
  }

  // ////////////////////////////////////////////////////////////////
  //
  // internal working
  //
  // ////////////////////////////////////////////////////////////////

  /** starts the server listens to incoming packets */
  public void run() {
    try {
      while (true) {
        if (Log.loggingNet) Log.net("In RDPServer.run: starting new iteration");
        try {
          Set<DatagramChannel> activeChannels = getActiveChannels();
          activeChannelCalls++;
          Iterator<DatagramChannel> iter = activeChannels.iterator();
          while (iter.hasNext()) {
            DatagramChannel dc = iter.next();
            if (Log.loggingNet) Log.net("In RDPServer.run: about to call processActiveChannel");
            processActiveChannel(dc);
            if (Log.loggingNet) Log.net("In RDPServer.run: returned from processActiveChannel");
          }
        } catch (ClosedChannelException ex) {
          // ignore
        } catch (Exception e) {
          Log.exception("RDPServer.run caught exception", e);
        }
      }
    } finally {
      Log.warn("RDPServer.run: thread exiting");
    }
  }

  /**
   * a DatagramChannel has data ready - process all the pending packets, whether its for a
   * rdpserversocket or rdpconnection.
   */
  void processActiveChannel(DatagramChannel dc) throws ClosedChannelException {
    RDPPacket packet;
    int count = 0;
    // read in the packet
    try {
      Set<RDPConnection> needsAckConnections = new HashSet<RDPConnection>();
      while ((packet = RDPServer.receivePacket(dc)) != null) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.processActiveChannel: Starting iteration with count of "
                  + count
                  + " packets");
        // see if there is a connection already for this packet
        InetAddress remoteAddr = packet.getInetAddress();
        int remotePort = packet.getPort();
        int localPort = dc.socket().getLocalPort();
        ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort);
        RDPConnection con = RDPServer.getConnection(dc, conInfo);
        if (con != null) {
          if (Log.loggingNet)
            Log.net("RDPServer.processActiveChannel: found an existing connection: " + con);
          count++;
          if (processExistingConnection(con, packet)) needsAckConnections.add(con);
          // Prevent this from blocking getActiveChannels by
          // putting an upper bound on the number of packets
          // processed
          if (count >= 20) break;
          continue;
        } else {
          Log.net("RDPServer.processActiveChannel: did not find an existing connection");
        }
        // there is no connection,
        // see if there is a socket listening for new connection
        RDPServerSocket rdpSocket = RDPServer.getRDPSocket(dc);
        if (rdpSocket != null) {
          count++;
          processNewConnection(rdpSocket, packet);
          return;
        }
        return;
      }
      // Finally, send out the acks
      for (RDPConnection con : needsAckConnections) {
        RDPPacket replyPacket = new RDPPacket(con);
        con.sendPacketImmediate(replyPacket, false);
      }
    } catch (ClosedChannelException ex) {
      Log.error("RDPServer.processActiveChannel: ClosedChannel " + dc.socket());
      throw ex;
    } finally {
      if (Log.loggingNet)
        Log.net("RDPServer.processActiveChannel: Returning after processing " + count + " packets");
    }
  }

  /**
   * there is a socket listening on the port for this packet. process if it is a new connection rdp
   * packet
   */
  public void processNewConnection(RDPServerSocket serverSocket, RDPPacket packet) {
    if (Log.loggingNet)
      Log.net(
          "processNewConnection: RDPPACKET (localport=" + serverSocket.getPort() + "): " + packet);

    //        int localPort = serverSocket.getPort();
    InetAddress remoteAddr = packet.getInetAddress();
    int remotePort = packet.getPort();
    if (!packet.isSyn()) {
      // the client is not attemping to start a new connection
      // send a reset and forget about it
      Log.debug("socket got non-syn packet, replying with reset: packet=" + packet);
      RDPPacket rstPacket = RDPPacket.makeRstPacket();
      rstPacket.setPort(remotePort);
      rstPacket.setInetAddress(remoteAddr);
      RDPServer.sendPacket(serverSocket.getDatagramChannel(), rstPacket);
      return;
    }

    // it is a syn packet, lets make a new connection for it
    RDPConnection con = new RDPConnection();
    DatagramChannel dc = serverSocket.getDatagramChannel();
    con.initConnection(dc, packet);

    // add new connection to allConnectionMap
    registerConnection(con, dc);

    // ack it with a syn
    RDPPacket synPacket = RDPPacket.makeSynPacket(con);
    con.sendPacketImmediate(synPacket, false);
  }

  /** returns a list of rdpserversockets */
  Set<DatagramChannel> getActiveChannels() throws InterruptedException, java.io.IOException {
    lock.lock();
    try {
      while (channelMap.isEmpty()) {
        channelMapNotEmpty.await();
      }
    } finally {
      lock.unlock();
    }

    Set<SelectionKey> readyKeys = null;
    do {
      lock.lock();
      try {
        if (!newChannelSet.isEmpty()) {
          if (Log.loggingNet) Log.net("RDPServer.getActiveChannels: newChannelSet is not null");
          Iterator<DatagramChannel> iter = newChannelSet.iterator();
          while (iter.hasNext()) {
            DatagramChannel newDC = iter.next();
            iter.remove();
            newDC.register(selector, SelectionKey.OP_READ);
          }
        }
      } finally {
        lock.unlock();
      }
      int numReady = selector.select(); // this is a blocking call - thread safe
      selectCalls++;
      if (numReady == 0) {
        if (Log.loggingNet) Log.net("RDPServer.getActiveChannels: selector returned 0");
        continue;
      }
      readyKeys = selector.selectedKeys();
      if (Log.loggingNet)
        Log.net(
            "RDPServer.getActiveChannels: called select - # of ready keys = "
                + readyKeys.size()
                + " == "
                + numReady);
    } while (readyKeys == null || readyKeys.isEmpty());

    lock.lock();
    try {
      // get a datagramchannel that is ready
      Set<DatagramChannel> activeChannels = new HashSet<DatagramChannel>();

      Iterator<SelectionKey> iter = readyKeys.iterator();
      while (iter.hasNext()) {
        SelectionKey key = iter.next();
        if (Log.loggingNet)
          Log.net(
              "RDPServer.getActiveChannels: matched selectionkey: "
                  + key
                  + ", isAcceptable="
                  + key.isAcceptable()
                  + ", isReadable="
                  + key.isReadable()
                  + ", isValid="
                  + key.isValid()
                  + ", isWritable="
                  + key.isWritable());
        iter.remove(); // remove from the selected key list

        if (!key.isReadable() || !key.isValid()) {
          Log.error(
              "RDPServer.getActiveChannels: Throwing exception: RDPServer: not readable or invalid");
          throw new MVRuntimeException("RDPServer: not readable or invalid");
        }

        DatagramChannel dc = (DatagramChannel) key.channel();
        activeChannels.add(dc);
      }
      if (Log.loggingNet)
        Log.net(
            "RDPServer.getActiveChannels: returning " + activeChannels.size() + " active channels");
      return activeChannels;
    } finally {
      lock.unlock();
    }
  }

  /**
   * returns the RDPConnection that is registered for the given datagram channel and is connected to
   * the host/port in ConnectionInfo returns null if there is no matching registered rdpconnection
   */
  static RDPConnection getConnection(DatagramChannel dc, ConnectionInfo conInfo) {
    lock.lock();
    try {
      Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc);

      if (dcConMap == null) {
        // there isnt even a datagram associated
        if (Log.loggingNet) Log.net("RDPServer.getConnection: could not find datagram");
        return null;
      }
      return dcConMap.get(conInfo);
    } finally {
      lock.unlock();
    }
  }

  static Set<RDPConnection> getAllConnections() {
    lock.lock();
    try {
      Set<RDPConnection> allCon = new HashSet<RDPConnection>();
      Iterator<Map<ConnectionInfo, RDPConnection>> iter = allConMap.values().iterator();
      while (iter.hasNext()) {
        Map<ConnectionInfo, RDPConnection> dcMap = iter.next();
        allCon.addAll(dcMap.values());
      }
      return allCon;
    } finally {
      lock.unlock();
    }
  }

  /**
   * returns the RDPServerSocket that is registered for the given datagramchannel returns null if
   * none exists
   */
  static RDPServerSocket getRDPSocket(DatagramChannel dc) {
    lock.lock();
    try {
      return socketMap.get(dc);
    } finally {
      lock.unlock();
    }
  }

  static CountMeter packetCounter = new CountMeter("RDPPacketReceiveCounter");
  static CountMeter dataCounter = new CountMeter("RDPPacketReceiveDATA");

  /**
   * we have a packet that belongs to the passed in connection. process the packet for the
   * connection. It returns true if the connection is open and the packet was a data packet
   */
  boolean processExistingConnection(RDPConnection con, RDPPacket packet) {

    if (Log.loggingNet)
      Log.net("RDPServer.processExistingConnection: con state=" + con + ", packet=" + packet);
    packetCounter.add();

    int state = con.getState();
    if (state == RDPConnection.LISTEN) {
      // something is wrong, we shouldn't be here
      // we get to this method after looking in the connections map
      // but all LISTEN connections should be listed direct
      // from serversockets
      Log.error("RDPServer.processExistingConnection: connection shouldnt be in LISTEN state");
      return false;
    }
    if (state == RDPConnection.SYN_SENT) {
      if (!packet.isAck()) {
        Log.warn("got a non-ack packet when we're in SYN_SENT");
        return false;
      }
      if (!packet.isSyn()) {
        Log.warn("got a non-syn packet when we're in SYN_SENT");
        return false;
      }
      if (Log.loggingNet) Log.net("good: got syn-ack packet in syn_sent");

      // make sure its acking our initial segment #
      if (packet.getAckNum() != con.getInitialSendSeqNum()) {
        if (Log.loggingNet) Log.net("syn's ack number does not match initial seq #");
        return false;
      }

      con.setRcvCur(packet.getSeqNum());
      con.setRcvIrs(packet.getSeqNum());
      con.setMaxSendUnacks(packet.getSendUnacks());
      con.setMaxReceiveSegmentSize(packet.getMaxRcvSegmentSize());
      con.setSendUnackd(packet.getAckNum() + 1);

      // ack first before setting state to open
      // otherwise some other thread will get woken up and send data
      // before we send the ack
      if (Log.loggingNet) Log.net("new connection state: " + con);
      RDPPacket replyPacket = new RDPPacket(con);
      con.sendPacketImmediate(replyPacket, false);
      con.setState(RDPConnection.OPEN);
      return false;
    }
    if (state == RDPConnection.SYN_RCVD) {
      if (packet.getSeqNum() <= con.getRcvIrs()) {
        Log.error("seqnum is not above rcv initial seq num");
        return false;
      }
      if (packet.getSeqNum() > (con.getRcvCur() + (con.getRcvMax() * 2))) {
        Log.error("seqnum is too big");
        return false;
      }
      if (packet.isAck()) {
        if (packet.getAckNum() == con.getInitialSendSeqNum()) {
          if (Log.loggingNet) Log.net("got ack for our syn - setting state to open");
          con.setState(RDPConnection.OPEN); // this will notify()

          // call the accept callback
          // first find the serversocket
          DatagramChannel dc = con.getDatagramChannel();
          if (dc == null) {
            throw new MVRuntimeException(
                "RDPServer.processExistingConnection: no datagramchannel for connection that just turned OPEN");
          }
          RDPServerSocket rdpSocket = RDPServer.getRDPSocket(dc);
          if (rdpSocket == null) {
            throw new MVRuntimeException(
                "RDPServer.processExistingConnection: no socket for connection that just turned OPEN");
          }
          ClientConnection.AcceptCallback acceptCB = rdpSocket.getAcceptCallback();
          if (acceptCB != null) {
            acceptCB.acceptConnection(con);
          } else {
            Log.warn("serversocket has no accept callback");
          }
          if (Log.loggingNet)
            Log.net(
                "RDPServer.processExistingConnection: got ACK, removing from unack list: "
                    + packet.getSeqNum());
          con.removeUnackPacket(packet.getSeqNum());
        }
      }
    }
    if (state == RDPConnection.CLOSE_WAIT) {
      // reply with a reset on all packets
      if (!packet.isRst()) {
        RDPPacket rstPacket = RDPPacket.makeRstPacket();
        con.sendPacketImmediate(rstPacket, false);
      }
    }
    if (state == RDPConnection.OPEN) {
      if (packet.isRst()) {
        // the other side wants to close the connection
        // set the state,
        // dont call con.close() since that will send a reset packet
        if (Log.loggingDebug)
          Log.debug("RDPServer.processExistingConnection: got reset packet for con " + con);
        if (con.getState() != RDPConnection.CLOSE_WAIT) {
          con.setState(RDPConnection.CLOSE_WAIT);
          con.setCloseWaitTimer();
          // Only invoke callback when moving into CLOSE_WAIT
          // state.  This prevents two calls to connectionReset.
          Log.net("RDPServer.processExistingConnection: calling reset callback");
          ClientConnection.MessageCallback pcb = con.getCallback();
          pcb.connectionReset(con);
        }

        return false;
      }
      if (packet.isSyn()) {
        // this will close the connection (put into CLOSE_WAIT)
        // send a reset packet and call the connectionReset callback
        Log.error(
            "RDPServer.processExistingConnection: closing connection because we got a syn packet, con="
                + con);
        con.close();
        return false;
      }

      // TODO: shouldnt it be ok for it to have same seq num?
      // if it is a 0 data packet?
      long rcvCur = con.getRcvCur();
      if (packet.getSeqNum() <= rcvCur) {
        if (Log.loggingNet)
          Log.net("RDPServer.processExistingConnection: seqnum too small - acking/not process");
        if (packet.getData() != null) {
          if (Log.loggingNet)
            Log.net(
                "RDPServer.processExistingConnection: sending ack even though seqnum out of range");
          RDPPacket replyPacket = new RDPPacket(con);
          con.sendPacketImmediate(replyPacket, false);
        }
        return false;
      }
      if (packet.getSeqNum() > (rcvCur + (con.getRcvMax() * 2))) {
        Log.error("RDPServer.processExistingConnection: seqnum too big - discarding");
        return false;
      }
      if (packet.isAck()) {
        if (Log.loggingNet)
          Log.net("RDPServer.processExistingConnection: processing ack " + packet.getAckNum());
        // lock for race condition (read then set)
        con.getLock().lock();
        try {
          if (packet.getAckNum() >= con.getSendNextSeqNum()) {
            // acking something we didnt even send yet
            Log.error(
                "RDPServer.processExistingConnection: discarding -- got ack #"
                    + packet.getAckNum()
                    + ", but our next send seqnum is "
                    + con.getSendNextSeqNum()
                    + " -- "
                    + con);
            return false;
          }
          if (con.getSendUnackd() <= packet.getAckNum()) {
            con.setSendUnackd(packet.getAckNum() + 1);
            if (Log.loggingNet)
              Log.net(
                  "RDPServer.processExistingConnection: updated send_unackd num to "
                      + con.getSendUnackd()
                      + " (one greater than packet ack) - "
                      + con);
            con.removeUnackPacketUpTo(packet.getAckNum());
          }
          if (packet.isEak()) {
            List eackList = packet.getEackList();
            Iterator iter = eackList.iterator();
            while (iter.hasNext()) {
              Long seqNum = (Long) iter.next();
              if (Log.loggingNet)
                Log.net("RDPServer.processExistingConnection: got EACK: " + seqNum);
              con.removeUnackPacket(seqNum.longValue());
            }
          }
        } finally {
          con.getLock().unlock();
          if (Log.loggingNet)
            Log.net("RDPServer.processExistingConnection: processed ack " + packet.getAckNum());
        }
      }
      // process the data
      byte[] data = packet.getData();
      if ((data != null) || packet.isNul()) {
        dataCounter.add();

        // lock - since racecondition: we read then set
        con.getLock().lock();
        try {
          rcvCur = con.getRcvCur(); // update rcvCur
          if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: rcvcur is " + rcvCur);

          ClientConnection.MessageCallback pcb = con.getCallback();
          if (pcb == null) {
            Log.warn("RDPServer.processExistingConnection: no packet callback registered");
          }

          // call callback only if we havent seen it already - eackd
          if (!con.hasEack(packet.getSeqNum())) {
            if (con.isSequenced()) {
              // this is a sequential connection,
              // make sure this is the 'next' packet
              // is this the next sequential packet
              if (packet.getSeqNum() == (rcvCur + 1)) {
                // this is the next packet
                if (Log.loggingNet)
                  Log.net(
                      "RDPServer.processExistingConnection: conn is sequenced and received next packet, rcvCur="
                          + rcvCur
                          + ", packet="
                          + packet);
                if ((pcb != null) && (data != null)) {
                  queueForCallbackProcessing(pcb, con, packet);
                }
              } else {
                // not the next packet, place it in queue
                if (Log.loggingNet)
                  Log.net(
                      "RDPServer.processExistingConnection: conn is sequenced, BUT PACKET is OUT OF ORDER: rcvcur="
                          + rcvCur
                          + ", packet="
                          + packet);
                con.addSequencePacket(packet);
              }
            } else {
              if ((pcb != null) && (data != null)) {
                // make sure we havent already processed packet
                queueForCallbackProcessing(pcb, con, packet);
              }
            }
          } else {
            if (Log.loggingNet) Log.net(con.toString() + " already seen this packet");
          }

          // is this the next sequential packet
          if (packet.getSeqNum() == (rcvCur + 1)) {
            con.setRcvCur(rcvCur + 1);
            if (Log.loggingNet)
              Log.net(
                  "RDPServer.processExistingConnection RCVD: incremented last sequenced rcvd: "
                      + (rcvCur + 1));

            // packet in order - dont add to eack
            // Take any additional sequential packets off eack
            long seqNum = rcvCur + 2;
            while (con.removeEack(seqNum)) {
              if (Log.loggingNet)
                Log.net("RDPServer.processExistingConnection: removing/collapsing eack: " + seqNum);
              con.setRcvCur(seqNum++);
            }

            if (con.isSequenced()) {
              rcvCur++; // since we just process the last one
              Log.net(
                  "RDPServer.processExistingConnection: connection is sequenced, processing collapsed packets.");
              // send any saved sequential packets also
              Iterator iter = con.getSequencePackets().iterator();
              while (iter.hasNext()) {
                RDPPacket p = (RDPPacket) iter.next();
                if (Log.loggingNet)
                  Log.net(
                      "rdpserver: stored packet seqnum="
                          + p.getSeqNum()
                          + ", if equal to (rcvcur + 1)="
                          + (rcvCur + 1));
                if (p.getSeqNum() == (rcvCur + 1)) {
                  Log.net(
                      "RDPServer.processExistingConnection: this is the next packet, processing");
                  // this is the next packet - update rcvcur
                  rcvCur++;

                  // process this packet
                  Log.net(
                      "RDPServer.processExistingConnection: processing stored sequential packet "
                          + p);
                  byte[] storedData = p.getData();
                  if (pcb != null && storedData != null) {
                    queueForCallbackProcessing(pcb, con, packet);
                  }
                  iter.remove();
                }
              }
            } else {
              if (Log.loggingNet)
                Log.net("RDPServer.processExistingConnection: connection is not sequenced");
            }
          } else {
            if (Log.loggingNet)
              Log.net(
                  "RDPServer.processExistingConnection: RCVD OUT OF ORDER: packet seq#: "
                      + packet.getSeqNum()
                      + ", but last sequential rcvd packet was: "
                      + con.getRcvCur()
                      + " -- not incrementing counter");
            if (packet.getSeqNum() > rcvCur) {
              // must be at least + 2 larger than rcvCur
              if (Log.loggingNet) Log.net("adding to eack list " + packet);
              con.addEack(packet);
            }
          }
        } finally {
          con.getLock().unlock();
        }
        return true;
      }
    }
    return false;
  }

  /** reads in an rdp packet from the datagram channel - blocking call */
  static RDPPacket receivePacket(DatagramChannel dc) throws ClosedChannelException {
    try {
      if (dc == null) {
        throw new MVRuntimeException("RDPServer.receivePacket: datagramChannel is null");
      }

      // get a packet from the reader
      staticMVBuff.rewind();
      InetSocketAddress addr = (InetSocketAddress) dc.receive(staticMVBuff.getNioBuf());
      if (addr == null) {
        return null;
      }

      RDPPacket packet = new RDPPacket();
      packet.setPort(addr.getPort());
      packet.setInetAddress(addr.getAddress());
      packet.parse(staticMVBuff);
      return packet;
    } catch (ClosedChannelException ex) {
      throw ex;
    } catch (Exception e) {
      throw new MVRuntimeException("error", e);
    }
  }

  // Only used by receivePacket, which is guaranteed to be single-threaded.
  private static MVByteBuffer staticMVBuff =
      new MVByteBuffer(RDPConnection.DefaultMaxReceiveSegmentSize);

  static String printSocket(DatagramSocket socket) {
    return "[Socket: localPort="
        + socket.getLocalPort()
        + ", remoteAddr="
        + socket.getInetAddress()
        + ", localAddr="
        + socket.getLocalAddress()
        + "]";
  }

  static CountMeter sendMeter = new CountMeter("RDPSendPacketMeter");
  static CountMeter sendDataMeter = new CountMeter("RDPSendDataPacketMeter");

  /** make sure the packet as the remote address and remote port set */
  static void sendPacket(DatagramChannel dc, RDPPacket packet) {

    sendMeter.add();

    // allocate a buffer
    int bufSize = 100 + (packet.numEacks() * 4);
    if (packet.getData() != null) {
      bufSize += packet.getData().length;
      sendDataMeter.add();
    }
    MVByteBuffer buf = new MVByteBuffer(bufSize);
    packet.toByteBuffer(buf); // function flips the buffer

    int remotePort = packet.getPort();
    InetAddress remoteAddr = packet.getInetAddress();

    if ((remotePort < 0) || (remoteAddr == null)) {
      throw new MVRuntimeException("RDPServer.sendPacket: remotePort or addr is null");
    }

    try {
      int bytes = dc.send(buf.getNioBuf(), new InetSocketAddress(remoteAddr, remotePort));
      if (bytes == 0) {
        Log.error("RDPServer.sendPacket: could not send packet, size=" + bufSize);
      }

      if (Log.loggingNet)
        Log.net(
            "RDPServer.sendPacket: remoteAddr="
                + remoteAddr
                + ", remotePort="
                + remotePort
                + ", numbytes sent="
                + bytes);
    } catch (java.io.IOException e) {
      Log.exception(
          "RDPServer.sendPacket: remoteAddr="
              + remoteAddr
              + ", remotePort="
              + remotePort
              + ", got exception",
          e);
      throw new MVRuntimeException("RDPServer.sendPacket", e);
    }
  }

  // ////////////////////////////////////////
  //
  // Private Fields
  //

  // use 'rdpServer' object as static lock - including for bindmap
  // and connectionMap
  static RDPServer rdpServer = new RDPServer();

  // localport -> datagramchannel for that port
  private static Map<Integer, DatagramChannel> channelMap = new HashMap<Integer, DatagramChannel>();

  // maps datagramchannel to serversocket
  // so when we get a new packet on a datagram channel via select() we can
  // associate it with a server socket and thus its callback
  private static Map<DatagramChannel, RDPServerSocket> socketMap =
      new HashMap<DatagramChannel, RDPServerSocket>();

  // map of datagram channel to a secondary map of connectioninfo->connection
  // when we get a packet, we check if it is associated with an existing
  // connection. but there can be many connections associated with a
  // single datagramchannel (localport), so we first look up by
  // datagram channel and then that returns us a second map.
  // we key into the connectioninfo (which makes a connection unique -
  // (localport, remoteport, remoteaddr) and then get the single connection
  private static Map<DatagramChannel, Map<ConnectionInfo, RDPConnection>> allConMap =
      new HashMap<DatagramChannel, Map<ConnectionInfo, RDPConnection>>();

  private static Lock unsentPacketsLock = LockFactory.makeLock("unsentPacketsLock");
  static Condition unsentPacketsNotEmpty = unsentPacketsLock.newCondition();

  /** set of new datagram channels that need to be registered with the selector */
  static Set<DatagramChannel> newChannelSet = new HashSet<DatagramChannel>();

  // thread that reads in new packets
  static Thread rdpServerThread = null;
  static Thread retryThread = null;
  static Thread packetCallbackThread = null;

  // // list of datagramchannels for sockets that are dead and should be
  // removed
  // // they became dead when the other side of the connection went away
  // // in the case of a single user connections
  // static List<DatagramChannel> deadDatagramChannelList =
  // new LinkedList<DatagramChannel>();

  static Selector selector = null;

  private static boolean rdpServerStarted = false;

  public static void startRDPServer() {
    if (rdpServerStarted) return;
    rdpServerStarted = true;
    rdpServerThread = new Thread(rdpServer, "RDPServer");
    retryThread = new Thread(new RetryThread(), "RDPRetry");
    packetCallbackThread = new Thread(new PacketCallbackThread(), "RDPCallback");
    if (Log.loggingNet) Log.net("static - starting rdpserver thread");
    try {
      selector = Selector.open();
    } catch (Exception e) {
      Log.exception("RDPServer caught exception opening selector", e);
      System.exit(1);
    }
    rdpServerThread.setPriority(rdpServerThread.getPriority() + 2);
    if (Log.loggingDebug)
      Log.debug(
          "RDPServer: starting rdpServerThread with priority " + rdpServerThread.getPriority());
    rdpServerThread.start();
    retryThread.start();
    packetCallbackThread.start();
  }

  // used in the TreeSet of connections with pending packets
  // it has time data associated with the connection, telling us
  // when we need to re-visit this connection to send its pending
  // data, in case it was not able to send all of its packets
  // due to throttling
  static class RDPConnectionData implements Comparable {
    public RDPConnection con;
    public long readyTime;

    public int compareTo(Object arg0) {
      RDPConnectionData other = (RDPConnectionData) arg0;

      if (this.readyTime < other.readyTime) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.RDPConnectionData.compareTo: readyTime compare -1: thiscon="
                  + this.con
                  + ", othercon="
                  + other.con
                  + ", thisready="
                  + this.readyTime
                  + ", otherReady="
                  + other.readyTime);
        return -1;
      } else if (this.readyTime > other.readyTime) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.RDPConnectionData.compareTo: readyTime compare 1: thiscon="
                  + this.con
                  + ", othercon="
                  + other.con
                  + ", thisready="
                  + this.readyTime
                  + ", otherReady="
                  + other.readyTime);
        return 1;
      }

      if (this.con == other.con) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.RDPConnectionData.compareTo: conRef compare 0: thiscon="
                  + this.con
                  + ", othercon="
                  + other.con);
        return 0;
      } else if (this.con.hashCode() < other.con.hashCode()) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.RDPConnectionData.compareTo: hashCode compare -1: thiscon="
                  + this.con
                  + ", othercon="
                  + other.con);
        return -1;
      } else if (this.con.hashCode() > other.con.hashCode()) {
        if (Log.loggingNet)
          Log.net(
              "RDPServer.RDPConnectionData.compareTo: hashCode compare 1: thiscon="
                  + this.con
                  + ", othercon="
                  + other.con);
        return 1;
      } else {
        throw new RuntimeException("error");
      }
    }

    public boolean equals(Object obj) {
      int rv = this.compareTo(obj);
      if (Log.loggingNet)
        Log.net(
            "RDPServer.RDPConnectionData.equals: thisObj="
                + this.toString()
                + ", other="
                + obj.toString()
                + ", result="
                + rv);
      return (rv == 0);
    }
  }

  // //////////////////////////////////////////////////
  //
  // RETRY THREAD - also handles CLOSE_WAIT connections - to actually close
  // them
  //
  static class RetryThread implements Runnable {
    public RetryThread() {}

    public void run() {
      // every second, go through all the packets that havent been
      // ack'd
      List<RDPConnection> conList = new LinkedList<RDPConnection>();
      long lastCounterTime = System.currentTimeMillis();
      while (true) {
        try {
          long startTime = System.currentTimeMillis();
          long interval = startTime - lastCounterTime;
          if (interval > 1000) {

            if (Log.loggingNet) {
              Log.net(
                  "RDPServer counters: activeChannelCalls "
                      + activeChannelCalls
                      + ", selectCalls "
                      + selectCalls
                      + ", transmits "
                      + transmits
                      + ", retransmits "
                      + retransmits
                      + " in "
                      + interval
                      + "ms");
            }
            activeChannelCalls = 0;
            selectCalls = 0;
            transmits = 0;
            retransmits = 0;
            lastCounterTime = startTime;
          }
          if (Log.loggingNet) Log.net("RDPServer.RETRY: startTime=" + startTime);

          // go through all the rdpconnections and re-send any
          // unacked packets
          conList.clear();

          lock.lock();
          try {
            // make a copy since the values() collection is
            // backed by the map
            Set<RDPConnection> conCol = RDPServer.getAllConnections();
            if (conCol == null) {
              throw new MVRuntimeException("values() returned null");
            }
            conList.addAll(conCol); // make non map backed copy
          } finally {
            lock.unlock();
          }

          Iterator<RDPConnection> iter = conList.iterator();
          while (iter.hasNext()) {
            RDPConnection con = iter.next();
            long currentTime = System.currentTimeMillis();

            // is the connection in CLOSE_WAIT
            if (con.getState() == RDPConnection.CLOSE_WAIT) {
              long closeTime = con.getCloseWaitTimer();
              long elapsedTime = currentTime - closeTime;
              Log.net(
                  "RDPRetryThread: con is in CLOSE_WAIT: elapsed close timer(ms)="
                      + elapsedTime
                      + ", waiting for 30seconds to elapse. con="
                      + con);
              if (elapsedTime > 30000) {
                // close the connection
                Log.net("RDPRetryThread: removing CLOSE_WAIT connection. con=" + con);
                removeConnection(con);
              } else {
                Log.net(
                    "RDPRetryThread: time left on CLOSE_WAIT timer: "
                        + (30000 - (currentTime - closeTime)));
              }
              // con.close();
              continue;
            }
            if (Log.loggingNet)
              Log.net(
                  "RDPServer.RETRY: resending expired packets "
                      + con
                      + " - current list size = "
                      + con.unackListSize());

            // see if we should send a null packet, but only if con is already open
            if ((con.getState() == RDPConnection.OPEN)
                && ((currentTime - con.getLastNullPacketTime()) > 30000)) {
              con.getLock().lock();
              try {
                RDPPacket nulPacket = RDPPacket.makeNulPacket();
                con.sendPacketImmediate(nulPacket, false);
                con.setLastNullPacketTime();
                if (Log.loggingNet) Log.net("RDPServer.retry: sent nul packet: " + nulPacket);
              } finally {
                con.getLock().unlock();
              }
            } else {
              if (Log.loggingNet)
                Log.net(
                    "RDPServer.retry: sending nul packet in "
                        + (30000 - (currentTime - con.getLastNullPacketTime())));
            }
            con.resend(
                currentTime - resendTimerMS, // resend cutoff time
                currentTime - resendTimeoutMS); // giveup time
          }

          long endTime = System.currentTimeMillis();
          if (Log.loggingNet)
            Log.net(
                "RDPServer.RETRY: endTime=" + endTime + ", elapse(ms)=" + (endTime - startTime));
          Thread.sleep(250);
        } catch (Exception e) {
          Log.exception("RDPServer.RetryThread.run caught exception", e);
        }
      }
    }
  }

  static Lock lock = LockFactory.makeLock("StaticRDPServerLock");

  /**
   * this condition gets signalled when there is a new server socket. this is useful when you are
   * waiting to process a new connection
   */
  static Condition channelMapNotEmpty = lock.newCondition();

  // private MVLock lock = new MVLock("RDPServerLock");

  /**
   * maximum time (in milliseconds) a packet can be in the resend queue before the connection closes
   * itself - defaults to 30 seconds
   */
  public static int resendTimeoutMS = 30000;

  /** how often we resend packets */
  public static int resendTimerMS = 500;

  public static void setCounterLogging(boolean enable) {
    packetCounter.setLogging(enable);
    dataCounter.setLogging(enable);
    sendMeter.setLogging(enable);
    sendDataMeter.setLogging(enable);
    RDPConnection.resendMeter.setLogging(enable);
  }

  /** machinery to count select and active channel calls */
  public static int activeChannelCalls = 0;

  public static int selectCalls = 0;
  public static int transmits = 0;
  public static int retransmits = 0;

  // this is the worker thread which calls the callback
  // we want it in a seperate thread so it doesnt block
  // the rdpserver connections.  Currently unused.
  static class CallbackThread implements Runnable {
    CallbackThread(RDPPacketCallback cb, RDPConnection con, RDPPacket packet, MVByteBuffer buf) {
      this.cb = cb;
      this.con = con;
      this.packet = packet;
      this.buf = buf;
    }

    public void run() {
      cb.processPacket(con, buf);
    }

    RDPConnection con = null;

    RDPPacketCallback cb = null;

    RDPPacket packet = null;

    MVByteBuffer buf = null;
  }

  /**
   * Machinery to process a queue of packets that have been received but not yet subjected to packet
   * processing.
   */
  static class PacketCallbackStruct {
    PacketCallbackStruct(
        ClientConnection.MessageCallback cb, ClientConnection con, RDPPacket packet) {
      this.cb = cb;
      this.con = con;
      this.packet = packet;
    }

    ClientConnection con = null;

    ClientConnection.MessageCallback cb = null;

    RDPPacket packet = null;
  }

  static void queueForCallbackProcessing(
      ClientConnection.MessageCallback pcb, ClientConnection con, RDPPacket packet) {
    queuedPacketCallbacksLock.lock();
    try {
      queuedPacketCallbacks.addLast(new PacketCallbackStruct(pcb, con, packet));
      queuedPacketCallbacksNotEmpty.signal();
    } finally {
      queuedPacketCallbacksLock.unlock();
    }
  }

  static LinkedList<PacketCallbackStruct> queuedPacketCallbacks =
      new LinkedList<PacketCallbackStruct>();
  static Lock queuedPacketCallbacksLock = LockFactory.makeLock("queuedPacketCallbacksLock");
  static Condition queuedPacketCallbacksNotEmpty = queuedPacketCallbacksLock.newCondition();

  // this is the worker thread that processes the queue of packets
  // received but not yet subjected to callback processing.
  static class PacketCallbackThread implements Runnable {

    PacketCallbackThread() {}

    public void run() {
      while (true) {
        LinkedList<PacketCallbackStruct> list = null;
        try {
          queuedPacketCallbacksLock.lock();
          try {
            queuedPacketCallbacksNotEmpty.await();
          } catch (Exception e) {
            Log.error(
                "RDPServer.PacketCallbackThread: queuedPacketCallbacksNotEmpty.await() caught exception "
                    + e.getMessage());
          }
          list = queuedPacketCallbacks;
          queuedPacketCallbacks = new LinkedList<PacketCallbackStruct>();
        } finally {
          queuedPacketCallbacksLock.unlock();
        }
        if (Log.loggingNet)
          Log.net("RDPServer.PacketCallbackThread: Got " + list.size() + " queued packets");
        for (PacketCallbackStruct pcs : list) {
          try {
            callbackProcessPacket(pcs.cb, pcs.con, pcs.packet);
          } catch (Exception e) {
            Log.exception("RDPServer.PacketCallbackThread: ", e);
          }
        }
      }
    }
  }

  static void callbackProcessPacket(
      ClientConnection.MessageCallback pcb, ClientConnection clientCon, RDPPacket packet) {
    if (packet.isNul()) {
      return;
    }
    byte[] data = packet.getData();
    MVByteBuffer buf = new MVByteBuffer(data);
    RDPConnection con = (RDPConnection) clientCon;
    // If this is a multiple-message message . . .
    if (buf.getLong() == -1 && buf.getInt() == RDPConnection.aggregatedMsgId) {
      con.aggregatedReceives++;
      PacketAggregator.allAggregatedReceives++;
      // Get the count of sub buffers
      int size = buf.getInt();
      con.receivedMessagesAggregated += size;
      PacketAggregator.allReceivedMessagesAggregated += size;
      if (Log.loggingNet)
        Log.net(
            "RDPServer.callbackProcessPacket: processing aggregated message with "
                + size
                + " submessages");
      MVByteBuffer subBuf = null;
      for (int i = 0; i < size; i++) {
        try {
          subBuf = buf.getByteBuffer();
        } catch (Exception e) {
          Log.error("In CallbackThread, error getting aggregated subbuffer: " + e.getMessage());
        }
        if (subBuf != null) pcb.processPacket(con, subBuf);
      }
    } else {
      con.unaggregatedReceives++;
      PacketAggregator.allUnaggregatedReceives++;
      buf.rewind();
      pcb.processPacket(con, buf);
    }
  }
}
예제 #12
0
 Barrier(int num) {
   lock = new ReentrantLock();
   NumWait = num;
   workers = lock.newCondition();
 }
class HostConnectionPool {

  private static final Logger logger = LoggerFactory.getLogger(HostConnectionPool.class);

  private static final int MAX_SIMULTANEOUS_CREATION = 1;

  public final Host host;
  public volatile HostDistance hostDistance;
  private final Session.Manager manager;

  private final List<Connection> connections;
  private final AtomicInteger open;
  private final AtomicBoolean isShutdown = new AtomicBoolean();
  private final Set<Connection> trash = new CopyOnWriteArraySet<Connection>();

  private volatile int waiter = 0;
  private final Lock waitLock = new ReentrantLock(true);
  private final Condition hasAvailableConnection = waitLock.newCondition();

  private final Runnable newConnectionTask;

  private final AtomicInteger scheduledForCreation = new AtomicInteger();

  public HostConnectionPool(Host host, HostDistance hostDistance, Session.Manager manager)
      throws ConnectionException {
    assert hostDistance != HostDistance.IGNORED;
    this.host = host;
    this.hostDistance = hostDistance;
    this.manager = manager;

    this.newConnectionTask =
        new Runnable() {
          @Override
          public void run() {
            addConnectionIfUnderMaximum();
            scheduledForCreation.decrementAndGet();
          }
        };

    // Create initial core connections
    List<Connection> l =
        new ArrayList<Connection>(options().getCoreConnectionsPerHost(hostDistance));
    try {
      for (int i = 0; i < options().getCoreConnectionsPerHost(hostDistance); i++)
        l.add(manager.connectionFactory().open(host));
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      // If asked to interrupt, we can skip opening core connections, the pool will still work.
      // But we ignore otherwise cause I'm not sure we can do much better currently.
    }
    this.connections = new CopyOnWriteArrayList<Connection>(l);
    this.open = new AtomicInteger(connections.size());

    logger.trace("Created connection pool to host {}", host);
  }

  private PoolingOptions options() {
    return manager.configuration().getPoolingOptions();
  }

  public Connection borrowConnection(long timeout, TimeUnit unit)
      throws ConnectionException, TimeoutException {
    if (isShutdown.get())
      // Note: throwing a ConnectionException is probably fine in practice as it will trigger the
      // creation of a new host.
      // That being said, maybe having a specific exception could be cleaner.
      throw new ConnectionException(host.getAddress(), "Pool is shutdown");

    if (connections.isEmpty()) {
      for (int i = 0; i < options().getCoreConnectionsPerHost(hostDistance); i++) {
        // We don't respect MAX_SIMULTANEOUS_CREATION here because it's  only to
        // protect against creating connection in excess of core too quickly
        scheduledForCreation.incrementAndGet();
        manager.executor().submit(newConnectionTask);
      }
      Connection c = waitForConnection(timeout, unit);
      c.setKeyspace(manager.poolsState.keyspace);
      return c;
    }

    int minInFlight = Integer.MAX_VALUE;
    Connection leastBusy = null;
    for (Connection connection : connections) {
      int inFlight = connection.inFlight.get();
      if (inFlight < minInFlight) {
        minInFlight = inFlight;
        leastBusy = connection;
      }
    }

    if (minInFlight >= options().getMaxSimultaneousRequestsPerConnectionThreshold(hostDistance)
        && connections.size() < options().getMaxConnectionsPerHost(hostDistance))
      maybeSpawnNewConnection();

    while (true) {
      int inFlight = leastBusy.inFlight.get();

      if (inFlight >= Connection.MAX_STREAM_PER_CONNECTION) {
        leastBusy = waitForConnection(timeout, unit);
        break;
      }

      if (leastBusy.inFlight.compareAndSet(inFlight, inFlight + 1)) break;
    }
    leastBusy.setKeyspace(manager.poolsState.keyspace);
    return leastBusy;
  }

  private void awaitAvailableConnection(long timeout, TimeUnit unit) throws InterruptedException {
    waitLock.lock();
    waiter++;
    try {
      hasAvailableConnection.await(timeout, unit);
    } finally {
      waiter--;
      waitLock.unlock();
    }
  }

  private void signalAvailableConnection() {
    // Quick check if it's worth signaling to avoid locking
    if (waiter == 0) return;

    waitLock.lock();
    try {
      hasAvailableConnection.signal();
    } finally {
      waitLock.unlock();
    }
  }

  private void signalAllAvailableConnection() {
    // Quick check if it's worth signaling to avoid locking
    if (waiter == 0) return;

    waitLock.lock();
    try {
      hasAvailableConnection.signalAll();
    } finally {
      waitLock.unlock();
    }
  }

  private Connection waitForConnection(long timeout, TimeUnit unit)
      throws ConnectionException, TimeoutException {
    long start = System.nanoTime();
    long remaining = timeout;
    do {
      try {
        awaitAvailableConnection(remaining, unit);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        // If we're interrupted fine, check if there is a connection available but stop waiting
        // otherwise
        timeout = 0; // this will make us stop the loop if we don't get a connection right away
      }

      if (isShutdown()) throw new ConnectionException(host.getAddress(), "Pool is shutdown");

      int minInFlight = Integer.MAX_VALUE;
      Connection leastBusy = null;
      for (Connection connection : connections) {
        int inFlight = connection.inFlight.get();
        if (inFlight < minInFlight) {
          minInFlight = inFlight;
          leastBusy = connection;
        }
      }

      while (true) {
        int inFlight = leastBusy.inFlight.get();

        if (inFlight >= Connection.MAX_STREAM_PER_CONNECTION) break;

        if (leastBusy.inFlight.compareAndSet(inFlight, inFlight + 1)) return leastBusy;
      }

      remaining = timeout - Cluster.timeSince(start, unit);
    } while (remaining > 0);

    throw new TimeoutException();
  }

  public void returnConnection(Connection connection) {
    int inFlight = connection.inFlight.decrementAndGet();

    if (connection.isDefunct()) {
      if (host.getMonitor().signalConnectionFailure(connection.lastException())) shutdown();
      else replace(connection);
    } else {

      if (trash.contains(connection) && inFlight == 0) {
        if (trash.remove(connection)) close(connection);
        return;
      }

      if (connections.size() > options().getCoreConnectionsPerHost(hostDistance)
          && inFlight <= options().getMinSimultaneousRequestsPerConnectionThreshold(hostDistance)) {
        trashConnection(connection);
      } else {
        signalAvailableConnection();
      }
    }
  }

  private boolean trashConnection(Connection connection) {
    // First, make sure we don't go below core connections
    for (; ; ) {
      int opened = open.get();
      if (opened <= options().getCoreConnectionsPerHost(hostDistance)) return false;

      if (open.compareAndSet(opened, opened - 1)) break;
    }
    trash.add(connection);
    connections.remove(connection);

    if (connection.inFlight.get() == 0 && trash.remove(connection)) close(connection);
    return true;
  }

  private boolean addConnectionIfUnderMaximum() {

    // First, make sure we don't cross the allowed limit of open connections
    for (; ; ) {
      int opened = open.get();
      if (opened >= options().getMaxConnectionsPerHost(hostDistance)) return false;

      if (open.compareAndSet(opened, opened + 1)) break;
    }

    if (isShutdown()) {
      open.decrementAndGet();
      return false;
    }

    // Now really open the connection
    try {
      connections.add(manager.connectionFactory().open(host));
      signalAvailableConnection();
      return true;
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      // Skip the open but ignore otherwise
      open.decrementAndGet();
      return false;
    } catch (ConnectionException e) {
      open.decrementAndGet();
      logger.debug("Connection error to {} while creating additional connection", host);
      if (host.getMonitor().signalConnectionFailure(e)) shutdown();
      return false;
    } catch (AuthenticationException e) {
      // This shouldn't really happen in theory
      open.decrementAndGet();
      logger.error(
          "Authentication error while creating additional connection (error is: {})",
          e.getMessage());
      shutdown();
      return false;
    }
  }

  private void maybeSpawnNewConnection() {
    while (true) {
      int inCreation = scheduledForCreation.get();
      if (inCreation >= MAX_SIMULTANEOUS_CREATION) return;
      if (scheduledForCreation.compareAndSet(inCreation, inCreation + 1)) break;
    }

    logger.debug("Creating new connection on busy pool to {}", host);
    manager.executor().submit(newConnectionTask);
  }

  private void replace(final Connection connection) {
    connections.remove(connection);

    manager
        .executor()
        .submit(
            new Runnable() {
              @Override
              public void run() {
                connection.close();
                addConnectionIfUnderMaximum();
              }
            });
  }

  private void close(final Connection connection) {
    manager
        .executor()
        .submit(
            new Runnable() {
              @Override
              public void run() {
                connection.close();
              }
            });
  }

  public boolean isShutdown() {
    return isShutdown.get();
  }

  public void shutdown() {
    try {
      shutdown(0, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }

  public boolean shutdown(long timeout, TimeUnit unit) throws InterruptedException {
    if (!isShutdown.compareAndSet(false, true)) return true;

    logger.debug("Shutting down pool");

    // Wake up all threads that waits
    signalAllAvailableConnection();
    return discardAvailableConnections(timeout, unit);
  }

  public int opened() {
    return open.get();
  }

  private boolean discardAvailableConnections(long timeout, TimeUnit unit)
      throws InterruptedException {
    long start = System.nanoTime();
    boolean success = true;
    for (Connection connection : connections) {
      success &= connection.close(timeout - Cluster.timeSince(start, unit), unit);
      open.decrementAndGet();
    }
    return success;
  }

  // This creates connections if we have less than core connections (if we
  // have more than core, connection will just get trash when we can).
  public void ensureCoreConnections() {
    if (isShutdown()) return;

    // Note: this process is a bit racy, but it doesn't matter since we're still guaranteed to not
    // create
    // more connection than maximum (and if we create more than core connection due to a race but
    // this isn't
    // justified by the load, the connection in excess will be quickly trashed anyway)
    int opened = open.get();
    for (int i = opened; i < options().getCoreConnectionsPerHost(hostDistance); i++) {
      // We don't respect MAX_SIMULTANEOUS_CREATION here because it's only to
      // protect against creating connection in excess of core too quickly
      scheduledForCreation.incrementAndGet();
      manager.executor().submit(newConnectionTask);
    }
  }

  static class PoolState {

    volatile String keyspace;

    public void setKeyspace(String keyspace) {
      this.keyspace = keyspace;
    }
  }
}
예제 #14
0
파일: Product.java 프로젝트: JCortz/SD
 public Product(int s, Lock l) {
   this.quantity = s;
   c = l.newCondition();
 }