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); } }
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(); } } }
public Runway() { L = new ReentrantLock(); C = L.newCondition(); wind_direction = 0; runway_number = 0; turn = 0; runway_direction = ""; }
// 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() + ']'); } } } }
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); } }
/** * 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(); } } }
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); } } }
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; } } }
public Product(int s, Lock l) { this.quantity = s; c = l.newCondition(); }