package org.deftserver.io.timeout; import java.nio.channels.SelectableChannel; import java.util.Iterator; import java.util.Map; import org.deftserver.util.MXBeanUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Maps; import com.google.common.collect.TreeMultiset; public class JMXDebuggableTimeoutManager implements TimeoutManager, TimeoutManagerMXBean { private final Logger logger = LoggerFactory.getLogger(JMXDebuggableTimeoutManager.class); private final TreeMultiset<DecoratedTimeout> timeouts = TreeMultiset.create(); private final Map<SelectableChannel, DecoratedTimeout> index = Maps.newHashMap(); { // instance initialization block MXBeanUtil.registerMXBean(this, "TimeoutManager"); } @Override public void addKeepAliveTimeout(SelectableChannel channel, Timeout timeout) { logger.debug("added keep-alive timeout: {}", timeout); DecoratedTimeout oldTimeout = index.get(channel); if (oldTimeout != null) { timeouts.remove(oldTimeout); } DecoratedTimeout decorated = new DecoratedTimeout(channel, timeout); timeouts.add(decorated); index.put(channel, decorated); } @Override public void addTimeout(Timeout timeout) { logger.debug("added generic timeout: {}", timeout); timeouts.add(new DecoratedTimeout(timeout)); } @Override public boolean hasKeepAliveTimeout(SelectableChannel channel) { return index.containsKey(channel); } @Override public long execute() { // makes a defensive copy to avoid (1) CME (new timeouts are added this iteration) and (2) IO starvation. TreeMultiset<DecoratedTimeout> defensive = TreeMultiset.create(timeouts); Iterator<DecoratedTimeout> iter = defensive.iterator(); final long now = System.currentTimeMillis(); while (iter.hasNext()) { DecoratedTimeout candidate = iter.next(); if (candidate.timeout.getTimeout() > now) { break; } candidate.timeout.getCallback().onCallback(); index.remove(candidate.channel); iter.remove(); timeouts.remove(candidate); logger.debug("Timeout triggered: {}", candidate.timeout); } return timeouts.isEmpty() ? Long.MAX_VALUE : Math.max(1, timeouts.iterator().next().timeout.getTimeout() - now); } // implements TimoutMXBean @Override public int getNumberOfKeepAliveTimeouts() { return index.size(); } @Override public int getNumberOfTimeouts() { return timeouts.size(); } private class DecoratedTimeout implements Comparable<DecoratedTimeout> { public final SelectableChannel channel; public final Timeout timeout; public DecoratedTimeout(SelectableChannel channel, Timeout timeout) { this.channel = channel; this.timeout = timeout; } public DecoratedTimeout(Timeout timeout) { this(null, timeout); } @Override public int compareTo(DecoratedTimeout that) { long diff = timeout.getTimeout() - that.timeout.getTimeout(); if (diff < 0) { return -1; } else if (diff > 0) { return 1; } if (channel != null && that.channel != null) { return channel.hashCode() - that.channel.hashCode(); } else if (channel == null && that.channel != null){ return -1; } else if (channel != null && that.channel == null){ return -1; } else { return 0; } } } }