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

com.oracle.coherence.common.internal.net.RunnableSelectionService Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.common.internal.net;


import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.base.NonBlocking;
import com.oracle.coherence.common.collections.ConcurrentLinkedQueue;
import com.oracle.coherence.common.net.SelectionService;
import com.oracle.coherence.common.util.Timers;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.IllegalSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Queue;
import java.util.Set;


/**
 * RunnableSelectionService is a single-threaded SelectionService implementation.
 * 

* RunnableSelectionService is Runnable but to be functional needs to be * serviced by a thread. The {@link #run} method is not thread-safe, and * should not be invoked by more then one thread. *

* The service will create its Selector as part of the first registration, * which will ultimately dictate the family of SelectableChannels which * can be used with the service. * * @author mf 2010.10.29 */ public class RunnableSelectionService implements SelectionService, Runnable { // ----- SelectionService interface ------------------------------------- /** * {@inheritDoc} */ public synchronized void register(SelectableChannel chan, Handler handler) throws IOException { if (chan.isBlocking()) { throw new IllegalBlockingModeException(); } ensureSelector(chan); // add registration f_mapRegistrations.put(chan, handler); // ensure selector thread sees the new registration if (!m_fPendingRegistrations) { m_fPendingRegistrations = true; wakeup(); } } /** * {@inheritDoc} */ public void invoke(final SelectableChannel chan, final Runnable runnable, long cMillis) throws IOException { ensureSelector(chan); if (cMillis == 0) { boolean fEmpty = f_tasks.isEmpty(); f_tasks.add(runnable); if (fEmpty) { wakeup(); } } else { Timers.scheduleNonBlockingTask(new Runnable() { @Override public void run() { try { invoke(chan, runnable, 0); } catch (IOException e) {} } }, cMillis); } } /** * Invoke a wakeup on the selector. */ protected void wakeup() { Selector selector = m_selector; if (selector != null) { selector.wakeup(); } } /** * Ensure that the selector is available * * @param chan the channel which needs a selector * * @return the selector * * @throws IOException if an IO error occurs */ private Selector ensureSelector(SelectableChannel chan) throws IOException { Selector selector = m_selector; if (selector == null) { synchronized (this) { selector = m_selector; if (selector == null) { m_selector = selector = chan.provider().openSelector(); notifyAll(); // see run() } } } if (!selector.isOpen()) { throw new ClosedSelectorException(); } else if (!chan.provider().equals(selector.provider())) { throw new IllegalSelectorException(); } return selector; } /** * Return the number of channels currently managed by the service. * * @return the channel count. */ protected synchronized int getChannelCount() { Selector selector = m_selector; return selector == null ? f_mapRegistrations.size() : Math.max(f_mapRegistrations.size(), selector.keys().size()); } /** * Return the number of active channels currently managed by the service. * * @return the channel count. */ protected int getActiveChannelCount() { Selector selector = m_selector; return selector == null ? 0 : selector.keys().size(); } @Override public void associate(SelectableChannel chanParent, SelectableChannel chanChild) throws IOException { register(chanChild, null); } /** * {@inheritDoc} */ public synchronized void shutdown() { Selector selector = m_selector; if (selector != null) { try { selector.close(); } catch (IOException e) {} } } // ----- RunnableSelectionService interface ----------------------------- /** * Set the duration the {@link #run} method should block with no * registered keys before returning. *

* Upon timing out the {@link #run} method will return, but the service * will not be shutdown. *

* A timeout specified while {@link #run} is active may not immediately * take effect. * * @param cMillis the idle timeout, or zero for infinite * * @return this object */ public RunnableSelectionService setIdleTimeout(long cMillis) { m_cMillisTimeout = cMillis; return this; } /** * Return the duration the {@link #run} method should block with no * registered keys before returning. * * @return the idle timeout, or zero for infinite */ public long getIdleTimeout() { return m_cMillisTimeout; } /** * Indicate if the service is idle (has no registered channels)). * * @return true iff the service is idle */ public synchronized boolean isIdle() { Selector selector = m_selector; return selector == null || !selector.isOpen() || (selector.keys().isEmpty() && f_mapRegistrations.isEmpty() && f_tasks.isEmpty()); } // ----- Runnable interface --------------------------------------------- /** * {@inheritDoc} */ public void run() { Selector selector = null; try { // wait for the lazily set selector; see ensureSelector() synchronized (this) { while ((selector = m_selector) == null) { long cWait = getIdleTimeout(); Blocking.wait(this, cWait); if (cWait != 0 && m_selector == null) { return; } } } // perform service processing process(); // try to close the selector to avoid fd leaks - COH-5911 synchronized(this) { // ensure that there is no pending work for the selector. If there is // pending work, ResumableSelectionService should call the run // method again. if (isIdle()) { // unlike shutdown, we close the selector but are // still open for business m_selector = null; selector.close(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (InterruptedIOException e) { Thread.currentThread().interrupt(); } catch (ClosedSelectorException e) { // we've been shutdown } catch (IOException e) { if (selector.isOpen()) { throw new RuntimeException(e); } } } // ----- Object interface ---------------------------------------------- /** * {@inheritDoc} */ public synchronized String toString() { // TODO: include load (percentage of time awake)? return "SelectionService(channels=" + getChannelCount() + ", selector=" + m_selector + ", id=" + System.identityHashCode(this) + ")"; } // ----- helper methods ------------------------------------------------ /** * Perform service processing. *

* It is unsafe to call this method outside of the service thread. * * @throws IOException on an I/O error which is not related to a specific * channel */ protected void process() throws IOException { Selector selector = m_selector; Set setReg = selector.keys(); Set setReady = selector.selectedKeys(); boolean fImmediate = false; try (NonBlocking nonBlocking = new NonBlocking()) // mark thread as non-blocking during processing loop { for (int i = 0; ; ++i) { if (!fImmediate) // don't execute runnables or registrations while there are pending canceled keys { processRunnables(); if (m_fPendingRegistrations) { fImmediate |= processRegistrations(); } } // perform select try { if (fImmediate || m_fPendingRegistrations || !f_tasks.isEmpty()) { selector.selectNow(); fImmediate = false; continue; // ensure any pending registrations get picked up so we don't risk using an old handler for new IO } else { // we split the idle timeout in half and do up to two timed selects. This is because // select(timeout) == 0 doesn't necessarily mean that timeout time has passed, it could // mean that no time has passed and that it just cleaned up some canceled keys. By breaking // it in two we help ensure that on the second == 0 that some time has really passed long cMillisTimeout = (getIdleTimeout() + 1) / 2; if (Blocking.select(selector, cMillisTimeout) == 0 && cMillisTimeout != 0 && setReg.isEmpty() && isIdle() && Blocking.select(selector, cMillisTimeout) == 0 && isIdle()) { return; } } } catch (CancelledKeyException e) { // This can happen if another thread concurrently closes // a registered channel, during the start of the select // operation. We need to force another selection. fImmediate = true; } // process selected keys boolean fEager = true; for (int iSpin = 0; fEager && iSpin < 4; ++iSpin) // TODO: identify the proper spin limiter; make configurable? { fEager = false; for (Iterator iter = setReady.iterator(); iter.hasNext(); ) { SelectionKey key = iter.next(); try { int nInterest = key.interestOps(); int nReady = key.readyOps(); // nReady is a sub-set of nInterest int nInterestNew = ((Handler) key.attachment()).onReady( iSpin > 0 ? Handler.OP_EAGER | nInterest : nReady); if ((nInterestNew & Handler.OP_EAGER) == 0) { // not eager; remove from ready set iter.remove(); } else // eager { // leave eager channels in the ready set fEager = true; nInterestNew &= ~Handler.OP_EAGER; // hide eager from actual selector layer } if (nInterestNew != nInterest) { // profiling has shown that setting interestOps is not "free", so avoid doing // it unless necessary; note that it is cheaper to read it then to write it key.interestOps(nInterestNew); } } catch (Throwable t) { // on error, unregister the handler and force a // immediate selection to clear the key from selector // allowing for future registrations key.cancel(); fImmediate = true; try { iter.remove(); } catch (IllegalStateException e) { // we may have already removed above; eat it } } } } if (fEager && (i & 0xF) == 0) // TODO: identify the proper eagerness limiter; make configurable? { // periodically clear any eager channels in case they are being over eager setReady.clear(); } } } } /** * Process any pending registrations. *

* This is an internal operation which occurs as part of the * {@link #process} operation. It is not safe to call it outside of the * service thread. * * @return true if immediate selection is required * * @throws IOException on an I/O error which is not related to a specific * channel */ protected synchronized boolean processRegistrations() throws IOException { boolean fImmediate = false; // Note: I've evaluated using ConcurrentHashMap or // ConcurrentLinkedQueue in place of a HashMap protected by service // synchronization. The cost of an isEmpty() check on either of the // concurrent options is more expensive then the single volatile // read used in the synchronized solution. Since the majority of the // time the registration map should be empty, the current approach // seems to be a good choice. Map mapReg = f_mapRegistrations; Selector selector = m_selector; for (Map.Entry entry : mapReg.entrySet()) { SelectableChannel chan = entry.getKey(); Handler handler = entry.getValue(); if (handler == null) { SelectionKey key = chan.keyFor(selector); if (key != null) { fImmediate = true; key.cancel(); } } else { try { chan.register(selector, chan.validOps(), handler); } catch (IOException e) { fImmediate = true; } catch (CancelledKeyException e) { fImmediate = true; } } } mapReg.clear(); m_fPendingRegistrations = false; return fImmediate; } /** * Execute Runnable in the SelectionService thread */ protected void processRunnables() { // it is allowable for a task to add another task to the queue in which case we // could loop forever (MultiplexedSocket accept can cause this), add a marker so we can // avoid doing any more work then necessary. // Note, if we have EOS followed by more work it simply means we'll do a selectNow and // then come back here, so the impact is quite minimal, it just serves to allow for // some task swapping f_tasks.add(EOS); for (Runnable task = f_tasks.poll(); task != EOS; task = f_tasks.poll()) { try { task.run(); } catch (Throwable thr) {} } } // ----- data members -------------------------------------------------- /** * Marker for end of task stream */ private static final Runnable EOS = () -> {}; /** * The selector associated with this service. */ private volatile Selector m_selector; /** * Map of pending (re)registrations. *

* All access to the map is protected via synchronization on the service. */ private final Map f_mapRegistrations = new HashMap<>(); /** * Queue of tasks to run. */ private final Queue f_tasks = new ConcurrentLinkedQueue<>(); /** * Flag indicating if the pending registration map contains any values. */ private volatile boolean m_fPendingRegistrations; /** * The selector idle timeout. */ private long m_cMillisTimeout; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy