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

org.cometd.server.ServerSessionImpl Maven / Gradle / Ivy

There is a newer version: 8.0.6
Show newest version
/*
 * Copyright (c) 2008-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cometd.server;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.cometd.bayeux.Channel;
import org.cometd.bayeux.ChannelId;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.Promise;
import org.cometd.bayeux.Session;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.bayeux.server.ServerTransport;
import org.cometd.common.AsyncFoldLeft;
import org.cometd.common.HashMapMessage;
import org.cometd.server.AbstractServerTransport.Scheduler;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.eclipse.jetty.util.thread.Scheduler.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ServerSessionImpl implements ServerSession, Dumpable {
    private static final AtomicLong _idCount = new AtomicLong();
    private static final Logger _logger = LoggerFactory.getLogger(ServerSession.class);

    private final ReentrantLock lock = new ReentrantLock();
    private final BayeuxServerImpl _bayeux;
    private final String _id;
    private final List _listeners = new CopyOnWriteArrayList<>();
    private final List _extensions = new CopyOnWriteArrayList<>();
    private final Queue _queue = new ArrayDeque<>();
    private final LocalSessionImpl _localSession;
    private final AttributesMap _attributes = new AttributesMap();
    private final Set subscriptions = Collections.newSetFromMap(new ConcurrentHashMap<>());
    private final LazyTask _lazyTask = new LazyTask();
    private AbstractServerTransport.Scheduler _scheduler = new Scheduler.None(0);
    private ServerTransport _transport;
    private ServerTransport _advisedTransport;
    private Object _endPoint;
    private State _state = State.NEW;
    private int _maxQueue = -1;
    private long _transientTimeout = -1;
    private long _transientInterval = -1;
    private long _timeout = -1;
    private long _interval = -1;
    private long _maxInterval = -1;
    private long _maxProcessing = -1;
    private long _maxLazy = -1;
    private boolean _metaConnectDelivery;
    private int _batch;
    private String _userAgent;
    private long _messageTime;
    private long _expireTime;
    private boolean _nonLazyMessages;
    private boolean _broadcastToPublisher;
    private boolean _allowMessageDeliveryDuringHandshake;
    private String _browserId;

    public ServerSessionImpl(BayeuxServerImpl bayeux) {
        this(bayeux, null, null);
    }

    public ServerSessionImpl(BayeuxServerImpl bayeux, LocalSessionImpl localSession, String idHint) {
        _bayeux = bayeux;
        _localSession = localSession;

        StringBuilder id = new StringBuilder(30);
        int len = 20;
        if (idHint != null) {
            len += idHint.length() + 1;
            id.append(idHint);
            id.append('_');
        }
        int index = id.length();

        while (id.length() < len) {
            id.append(Long.toString(_bayeux.randomLong(), 36));
        }

        id.insert(index, Long.toString(_idCount.incrementAndGet(), 36));

        _id = id.toString();

        _broadcastToPublisher = _bayeux.isBroadcastToPublisher();
    }

    public BayeuxServerImpl getBayeuxServer() {
        return _bayeux;
    }

    /**
     * @return the remote user agent
     */
    @Override
    public String getUserAgent() {
        return _userAgent;
    }

    /**
     * @param userAgent the remote user agent
     */
    public void setUserAgent(String userAgent) {
        _userAgent = userAgent;
    }

    /**
     * @return the remote client identifier
     */
    public String getBrowserId() {
        return _browserId;
    }

    /**
     * 

Sets a remote client identifier, typically a browser.

* * @param browserId the remote client identifier */ public void setBrowserId(String browserId) { _browserId = browserId; } protected void sweep(long now) { if (isLocalSession()) { return; } boolean remove = false; Scheduler scheduler = null; lock.lock(); try { if (_expireTime == 0) { if (_maxProcessing > 0 && (now - _messageTime) > _maxProcessing) { _logger.info("Sweeping during processing {}", this); remove = true; } } else { if (now - _expireTime > 0) { if (_logger.isDebugEnabled()) { _logger.debug("Sweeping {}", this); } remove = true; } } if (remove) { scheduler = _scheduler; } } finally { lock.unlock(); } if (remove) { scheduler.destroy(); _bayeux.removeServerSession(this, true); } } @Override public Set getSubscriptions() { return Set.copyOf(subscriptions); } @Override public void addExtension(Extension extension) { _extensions.add(extension); } @Override public void removeExtension(Extension extension) { _extensions.remove(extension); } @Override public List getExtensions() { return List.copyOf(_extensions); } @Override public void batch(Runnable batch) { startBatch(); try { batch.run(); } finally { endBatch(); } } @Override public void deliver(Session sender, ServerMessage.Mutable message, Promise promise) { ServerSession session = null; if (sender instanceof ServerSession) { session = (ServerSession)sender; } else if (sender instanceof LocalSession) { session = ((LocalSession)sender).getServerSession(); } ServerSession serverSession = session; _bayeux.extendOutgoing(serverSession, this, message, Promise.from(b -> { if (b) { deliver1(serverSession, message, promise); } else { promise.succeed(false); } }, promise::fail)); } @Override public void deliver(Session sender, String channelId, Object data, Promise promise) { ServerMessage.Mutable message = _bayeux.newMessage(); message.setChannel(channelId); message.setData(data); deliver(sender, message, promise); } protected void deliver1(ServerSession sender, ServerMessage.Mutable mutable, Promise promise) { if (sender == this && !isBroadcastToPublisher() && ChannelId.isBroadcast(mutable.getChannel())) { promise.succeed(false); } else { extendOutgoing(sender, mutable, Promise.from(message -> { if (message == null) { promise.succeed(false); } else { _bayeux.freeze(message); AsyncFoldLeft.run(_listeners, true, (result, listener, loop) -> { if (listener instanceof MessageListener) { notifyOnMessage((MessageListener)listener, sender, message, _bayeux.resolveLoop(loop)); } else { loop.proceed(result); } }, Promise.from(b -> { if (b) { deliver2(sender, message, promise); } else { promise.succeed(false); } }, promise::fail)); } }, promise::fail)); } } private void deliver2(ServerSession sender, ServerMessage.Mutable message, Promise promise) { Boolean wakeup = enqueueMessage(sender, message); if (wakeup == null) { promise.succeed(false); } else { if (wakeup) { if (message.isLazy()) { flushLazy(message); } else { flush(); } } promise.succeed(true); } } private Boolean enqueueMessage(ServerSession sender, ServerMessage.Mutable message) { lock.lock(); try { if (isTerminated() && !ChannelId.isMeta(message.getChannel())) { if (_logger.isDebugEnabled()) { _logger.debug("Dropping message {} for terminated {}", message, this); } return null; } for (ServerSessionListener listener : _listeners) { if (listener instanceof QueueMaxedListener) { int maxQueueSize = _maxQueue; if (maxQueueSize > 0 && _queue.size() >= maxQueueSize) { if (!notifyQueueMaxed((QueueMaxedListener)listener, this, _queue, sender, message)) { return null; } } } } addMessage(message); for (ServerSessionListener listener : _listeners) { if (listener instanceof QueueListener) { notifyQueued((QueueListener)listener, sender, message); } } return _batch == 0; } finally { lock.unlock(); } } protected void extendOutgoing(ServerSession sender, ServerMessage.Mutable message, Promise promise) { AsyncFoldLeft.reverseRun(_extensions, message, (result, extension, loop) -> { try { extension.outgoing(sender, this, result, Promise.from(m -> { if (m != null) { loop.proceed(m); } else { loop.leave(null); } }, failure -> { _logger.info("Exception reported by extension " + extension, failure); loop.proceed(result); })); } catch (Throwable x) { _logger.info("Exception thrown by extension " + extension, x); loop.proceed(result); } }, promise); } private boolean notifyQueueMaxed(QueueMaxedListener listener, ServerSession session, Queue queue, ServerSession sender, ServerMessage message) { try { return listener.queueMaxed(session, queue, sender, message); } catch (Throwable x) { _logger.info("Exception while invoking listener " + listener, x); return true; } } private void notifyOnMessage(MessageListener listener, ServerSession sender, ServerMessage message, Promise promise) { try { listener.onMessage(this, sender, message, Promise.from(promise::succeed, failure -> { _logger.info("Exception reported by listener " + listener, failure); promise.succeed(true); })); } catch (Throwable x) { _logger.info("Exception thrown by listener " + listener, x); promise.succeed(true); } } private void notifyQueued(QueueListener listener, ServerSession session, ServerMessage message) { try { listener.queued(session, message); } catch (Throwable x) { _logger.info("Exception while invoking listener " + listener, x); } } protected boolean handshake(ServerMessage.Mutable message) { AbstractServerTransport transport = message == null ? null : (AbstractServerTransport)message.getServerTransport(); if (transport != null) { _maxQueue = transport.getOption(AbstractServerTransport.MAX_QUEUE_OPTION, -1); _maxProcessing = transport.getOption(AbstractServerTransport.MAX_PROCESSING_OPTION, -1); if (_maxProcessing > 0) { _maxProcessing = TimeUnit.MILLISECONDS.toNanos(_maxProcessing); } _maxLazy = transport.getMaxLazyTimeout(); } lock.lock(); try { if (_state == State.NEW) { _state = State.HANDSHAKEN; return true; } return false; } finally { lock.unlock(); } } protected boolean connected() { lock.lock(); try { if (_state == State.HANDSHAKEN || _state == State.CONNECTED) { _state = State.CONNECTED; return true; } return false; } finally { lock.unlock(); } } @Override public void disconnect() { boolean connected = _bayeux.removeServerSession(this, false); if (connected) { ServerMessage.Mutable message = _bayeux.newMessage(); message.setSuccessful(true); message.setChannel(Channel.META_DISCONNECT); deliver(this, message, new Promise<>() { @Override public void succeed(Boolean result) { flush(); } }); } } @Override public void startBatch() { lock.lock(); try { ++_batch; } finally { lock.unlock(); } } @Override public boolean endBatch() { boolean result = false; lock.lock(); try { if (--_batch == 0 && _nonLazyMessages) { result = true; } } finally { lock.unlock(); } if (result) { flush(); } return result; } @Override public LocalSession getLocalSession() { return _localSession; } @Override public boolean isLocalSession() { return _localSession != null; } @Override public void addListener(ServerSessionListener listener) { _listeners.add(listener); } @Override public String getId() { return _id; } public Lock getLock() { return lock; } public Queue getQueue() { return _queue; } public boolean hasNonLazyMessages() { lock.lock(); try { return _nonLazyMessages; } finally { lock.unlock(); } } protected void addMessage(ServerMessage message) { lock.lock(); try { _queue.add(message); _nonLazyMessages |= !message.isLazy(); } finally { lock.unlock(); } } public List takeQueue(List replies) { List copy = List.of(); lock.lock(); try { // Always call listeners, even if the queue is // empty since they may add messages to the queue. for (ServerSessionListener listener : _listeners) { if (listener instanceof DeQueueListener) { notifyDeQueue((DeQueueListener)listener, this, _queue, replies); } } int size = _queue.size(); if (size > 0) { copy = new ArrayList<>(size); copy.addAll(_queue); _queue.clear(); } _nonLazyMessages = false; } finally { lock.unlock(); } return copy; } private void notifyDeQueue(DeQueueListener listener, ServerSession serverSession, Queue queue, List replies) { try { listener.deQueue(serverSession, queue, replies); } catch (Throwable x) { _logger.info("Exception while invoking listener " + listener, x); } } public void notifySuspended(ServerMessage message, long timeout) { for (ServerSessionListener listener : _listeners) { if (listener instanceof ServerSession.HeartBeatListener) { ((HeartBeatListener)listener).onSuspended(this, message, timeout); } } } public void notifyResumed(ServerMessage message, boolean timeout) { for (ServerSessionListener listener : _listeners) { if (listener instanceof ServerSession.HeartBeatListener) { ((HeartBeatListener)listener).onResumed(this, message, timeout); } } } @Override public void removeListener(ServerSessionListener listener) { _listeners.remove(listener); } public List getListeners() { return List.copyOf(_listeners); } public void setScheduler(AbstractServerTransport.Scheduler newScheduler) { if (newScheduler == null) { Scheduler oldScheduler; lock.lock(); try { oldScheduler = _scheduler; if (_logger.isDebugEnabled()) { _logger.debug("{} disabling scheduler {}", this, oldScheduler); } } finally { lock.unlock(); } oldScheduler.cancel(); } else { Scheduler oldScheduler; boolean schedule = false; lock.lock(); try { oldScheduler = _scheduler; // Only set the scheduler if it has a greater or equal cycle. if (newScheduler.getMetaConnectCycle() >= oldScheduler.getMetaConnectCycle()) { _scheduler = newScheduler; if (_logger.isDebugEnabled()) { _logger.debug("{} replacing scheduler old={} new={}", this, oldScheduler, newScheduler); } if (shouldSchedule()) { schedule = true; } else { // An existing scheduler may have woken // up and scheduled session expiration, // but here we are suspending, so we must // be sure session expiration is cancelled. cancelExpiration(true); } } else { oldScheduler = newScheduler; if (_logger.isDebugEnabled()) { _logger.debug("{} ignoring stale scheduler {}", this, newScheduler); } } } finally { lock.unlock(); } oldScheduler.cancel(); if (schedule) { newScheduler.schedule(); } } } public boolean shouldSchedule() { lock.lock(); try { return hasNonLazyMessages() && _batch == 0; } finally { lock.unlock(); } } public void flush() { Scheduler scheduler; lock.lock(); try { _lazyTask.cancel(); scheduler = _scheduler; } finally { lock.unlock(); } if (_localSession == null) { // It's a remote session, schedule delivery and return. scheduler.schedule(); } else { // Local delivery. if (hasNonLazyMessages()) { for (ServerMessage msg : takeQueue(List.of())) { _localSession.receive(new HashMapMessage(msg), Promise.noop()); } } } } private void flushLazy(ServerMessage message) { lock.lock(); try { ServerChannel channel = _bayeux.getChannel(message.getChannel()); long lazyTimeout = -1; if (channel != null) { lazyTimeout = channel.getLazyTimeout(); } if (lazyTimeout <= 0) { lazyTimeout = _maxLazy; } if (lazyTimeout <= 0) { flush(); } else { _lazyTask.schedule(lazyTimeout); } } finally { lock.unlock(); } } public void destroyScheduler() { Scheduler oldScheduler; lock.lock(); try { oldScheduler = _scheduler; _scheduler = new Scheduler.None(Long.MAX_VALUE); } finally { lock.unlock(); } oldScheduler.destroy(); } public void cancelExpiration(boolean metaConnect) { long now = System.nanoTime(); lock.lock(); try { _messageTime = now; if (metaConnect) { // A /meta/connect was received and possibly // suspended by the server, don't sweep it. _expireTime = 0; } else if (_expireTime != 0) { // A /meta/connect was returned to // the client, and another message was // received, so extend the expiration. long maxInterval = calculateMaxInterval(getServerTransport().getMaxInterval()); _expireTime = Math.max(_expireTime, now + TimeUnit.MILLISECONDS.toNanos(maxInterval)); } } finally { lock.unlock(); } if (_logger.isDebugEnabled()) { _logger.debug("{} expiration for {}", metaConnect ? "Cancelled" : "Delayed", this); } } public void scheduleExpiration(long defaultInterval, long defaultMaxInterval, long metaConnectCycle) { long interval = calculateInterval(defaultInterval); long maxInterval = calculateMaxInterval(defaultMaxInterval); long now = System.nanoTime(); boolean scheduled = false; lock.lock(); try { // When metaConnectCycle == 0, the /meta/connect was not suspended. // Otherwise it was suspended, and some other event such as the // /meta/connect timeout expiration may schedule session expiration // only if there isn't a more recent /meta/connect that was suspended, // which would have cancelled the session expiration. if (metaConnectCycle == 0 || metaConnectCycle == getMetaConnectCycle()) { scheduled = true; _expireTime = now + TimeUnit.MILLISECONDS.toNanos(interval + maxInterval); } } finally { lock.unlock(); } if (_logger.isDebugEnabled()) { _logger.debug("{} expiration for {}", scheduled ? "Scheduled" : "Skipped", this); } } long getMetaConnectCycle() { lock.lock(); try { return _scheduler.getMetaConnectCycle(); } finally { lock.unlock(); } } @Override public long getMaxInterval() { return _maxInterval; } @Override public void setMaxInterval(long maxInterval) { _maxInterval = maxInterval; } long getIntervalTimestamp() { return _expireTime; } @Override public Object getAttribute(String name) { return _attributes.getAttribute(name); } @Override public Set getAttributeNames() { return _attributes.getAttributeNameSet(); } @Override public Object removeAttribute(String name) { Object old = getAttribute(name); _attributes.removeAttribute(name); return old; } @Override public void setAttribute(String name, Object value) { _attributes.setAttribute(name, value); } @Override public boolean isHandshook() { lock.lock(); try { return _state == State.HANDSHAKEN || _state == State.CONNECTED; } finally { lock.unlock(); } } @Override public boolean isConnected() { lock.lock(); try { return _state == State.CONNECTED; } finally { lock.unlock(); } } public boolean isDisconnected() { lock.lock(); try { return _state == State.DISCONNECTED; } finally { lock.unlock(); } } public boolean isTerminated() { lock.lock(); try { return _state == State.DISCONNECTED || _state == State.EXPIRED; } finally { lock.unlock(); } } protected void extendIncoming(ServerMessage.Mutable message, Promise promise) { AsyncFoldLeft.run(_extensions, true, (result, extension, loop) -> { if (result) { try { extension.incoming(this, message, Promise.from(loop::proceed, failure -> { _logger.info("Exception reported by extension " + extension, failure); loop.proceed(true); })); } catch (Throwable x) { _logger.info("Exception thrown by extension " + extension, x); loop.proceed(true); } } else { loop.leave(false); } }, promise); } public void reAdvise() { _advisedTransport = null; } public Map takeAdvice(ServerTransport transport) { if (transport == null || transport == _advisedTransport) { // The advice has not changed, so return null. return null; } return createAdvice(transport); } private Map createAdvice(ServerTransport transport) { _advisedTransport = transport; // The timeout is calculated based on the values of the session/transport // because we want to send to the client the *next* timeout. long timeout = getTimeout() < 0 ? transport.getTimeout() : getTimeout(); // The interval is calculated using also the transient value // because we want to send to the client the *current* interval. long interval = calculateInterval(transport.getInterval()); Map advice = new HashMap<>(3); advice.put(Message.RECONNECT_FIELD, Message.RECONNECT_RETRY_VALUE); advice.put(Message.INTERVAL_FIELD, interval); advice.put(Message.TIMEOUT_FIELD, timeout); if (transport instanceof AbstractServerTransport) { if (((AbstractServerTransport)transport).isHandshakeReconnect()) { long maxInterval = calculateMaxInterval(transport.getMaxInterval()); advice.put(Message.MAX_INTERVAL_FIELD, maxInterval); } } return advice; } @Override public ServerTransport getServerTransport() { return _transport; } public void setServerTransport(ServerTransport transport) { _transport = transport; } public boolean updateServerEndPoint(Object newEndPoint) { Object oldEndPoint = _endPoint; if (oldEndPoint == newEndPoint) { return false; } _endPoint = newEndPoint; return true; } @Override public long getTimeout() { return _timeout; } @Override public long getInterval() { return _interval; } @Override public void setTimeout(long timeoutMS) { _timeout = timeoutMS; _advisedTransport = null; } @Override public void setInterval(long intervalMS) { _interval = intervalMS; _advisedTransport = null; } @Override public boolean isBroadcastToPublisher() { return _broadcastToPublisher; } @Override public void setBroadcastToPublisher(boolean value) { _broadcastToPublisher = value; } void added(ServerMessage message) { for (ServerSessionListener listener : _listeners) { if (listener instanceof AddedListener) { notifyAdded((AddedListener)listener, this, message); } } } /** * @param message the message that caused the removal, or null * @param timeout whether the session has been removed due a timeout * @return True if the session was connected. */ protected boolean removed(ServerMessage message, boolean timeout) { boolean result; lock.lock(); try { result = isHandshook(); _state = timeout ? State.EXPIRED : State.DISCONNECTED; } finally { lock.unlock(); } if (result) { for (ServerChannelImpl channel : subscriptions) { channel.unsubscribe(this); } for (ServerSessionListener listener : _listeners) { if (listener instanceof RemovedListener) { notifyRemoved((RemovedListener)listener, this, message, timeout); } } } return result; } private void notifyAdded(AddedListener listener, ServerSession serverSession, ServerMessage message) { try { listener.added(serverSession, message); } catch (Exception x) { _logger.info("Exception while invoking listener " + listener, x); } } private void notifyRemoved(RemovedListener listener, ServerSession session, ServerMessage message, boolean timeout) { try { listener.removed(session, message, timeout); } catch (Throwable x) { _logger.info("Exception while invoking listener " + listener, x); } } @Override public void setMetaConnectDeliveryOnly(boolean meta) { _metaConnectDelivery = meta; } @Override public boolean isMetaConnectDeliveryOnly() { return _metaConnectDelivery; } public boolean isAllowMessageDeliveryDuringHandshake() { return _allowMessageDeliveryDuringHandshake; } public void setAllowMessageDeliveryDuringHandshake(boolean allow) { _allowMessageDeliveryDuringHandshake = allow; } protected boolean subscribe(ServerChannelImpl channel) { lock.lock(); try { if (isTerminated()) { return false; } else { subscriptions.add(channel); return true; } } finally { lock.unlock(); } } protected void unsubscribedFrom(ServerChannelImpl channel) { subscriptions.remove(channel); } public long calculateTimeout(long defaultTimeout) { if (_transientTimeout >= 0) { return _transientTimeout; } if (_timeout >= 0) { return _timeout; } return defaultTimeout; } public long calculateInterval(long defaultInterval) { if (_transientInterval >= 0) { return _transientInterval; } if (_interval >= 0) { return _interval; } return defaultInterval; } private long calculateMaxInterval(long defaultMaxInterval) { if (_maxInterval > 0) { return _maxInterval; } return defaultMaxInterval; } /** * Updates the transient timeout with the given value. * The transient timeout is the one sent by the client, that should * temporarily override the session/transport timeout, for example * when the client sends {timeout:0} * * @param timeout the value to update the timeout to * @see #updateTransientInterval(long) */ public void updateTransientTimeout(long timeout) { _transientTimeout = timeout; } /** * Updates the transient interval with the given value. * The transient interval is the one sent by the client, that should * temporarily override the session/transport interval, for example * when the client sends {timeout:0,interval:60000} * * @param interval the value to update the interval to * @see #updateTransientTimeout(long) */ public void updateTransientInterval(long interval) { _transientInterval = interval; } @Override public void dump(Appendable out, String indent) throws IOException { List listeners = _listeners; if (_bayeux.isDetailedDump()) { Dumpable.dumpObjects(out, indent, this, new DumpableCollection("listeners", listeners)); } else { Dumpable.dumpObjects(out, indent, this, "listeners size=" + listeners.size()); } } @Override public String toString() { long cycle; long last; long expire; State state; int size; long now = System.nanoTime(); lock.lock(); try { cycle = getMetaConnectCycle(); last = now - _messageTime; expire = _expireTime == 0 ? 0 : _expireTime - now; state = _state; size = _queue.size(); } finally { lock.unlock(); } return String.format("%s@%x[%s,%s,q=%d,cycle=%d,last=%d,expire=%d]", getClass().getSimpleName(), hashCode(), _id, state, size, cycle, TimeUnit.NANOSECONDS.toMillis(last), TimeUnit.NANOSECONDS.toMillis(expire)); } private class LazyTask implements Runnable { private long _nextExecutionNanos; private volatile Task _task; @Override public void run() { flush(); _nextExecutionNanos = 0; _task = null; } public boolean cancel() { Task task = _task; return task != null && task.cancel(); } public boolean schedule(long lazyTimeout) { long nextExecutionNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(lazyTimeout); // Reschedule if the next execution is earlier than the previous one. if (_task == null || (_nextExecutionNanos - nextExecutionNanos) > 0) { cancel(); _nextExecutionNanos = nextExecutionNanos; _task = _bayeux.schedule(this, lazyTimeout); return true; } return false; } } private enum State { NEW, HANDSHAKEN, CONNECTED, DISCONNECTED, EXPIRED } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy