dev@grizzly.java.net

Re: Improve Controller & SelectorHandler state control

From: Jeanfrancois Arcand <Jeanfrancois.Arcand_at_Sun.COM>
Date: Mon, 14 Jan 2008 11:20:53 -0500

Salut,

looks good...just have a small request for comments (see inline) :-)

Oleksiy Stashok wrote:
> Hi,
>
> last time we had several problems with handling Controller,
> SelectorHandler states.
> For example we have Grizzly issue [1]; Sailfin bug, where call
> selectorHandler.shutdown(), when Controller is running, never ends.
>
> Currently in Controller we have 2 fields responsible for state:
> stateLock and state. With state field we have a lot of problems, as it's
> difficult to share the same state among Controller and its ReadControllers.
> So here I'm proposing to use just one class StateHolder, which is the
> holder of state (and could be easily shared), and has API for sync. work
> with state, and register state change listeners.
>
> Using StateHolder, I've also implemented pause/resume methods for
> Controller and SelectorHandler and added support for dynamic
> adding/removing of SelectorHandler to a Controller.
>
> I know here are lots of changes, but if you will find anything to
> comment - you're welcome :)
>
> Thanks.
>
> WBR,
> Alexey.
>
> [1] https://grizzly.dev.java.net/issues/show_bug.cgi?id=37

> # This patch file was generated by NetBeans IDE
> # Following Index: paths are relative to: C:\Projects\Grizzly\trunk\modules\grizzly\src\main\java\com\sun\grizzly
> # This patch can be applied using context Tools: Patch action on respective folder.
> # It uses platform neutral UTF-8 encoding and \n newlines.
> # Above lines and this line are ignored by the patching process.
> Index: Controller.java
> --- Controller.java Base (BASE)
> +++ Controller.java Locally Modified (Based On LOCAL)
> @@ -26,6 +26,9 @@
> import com.sun.grizzly.util.AttributeHolder;
> import com.sun.grizzly.util.Cloner;
> import com.sun.grizzly.util.Copyable;
> +import com.sun.grizzly.util.State;
> +import com.sun.grizzly.util.StateHolder;
> +import com.sun.grizzly.util.SupportStateHolder;
> import java.io.IOException;
> import java.nio.channels.ClosedChannelException;
> import java.nio.channels.ClosedSelectorException;
> @@ -40,7 +43,6 @@
> import java.util.concurrent.Callable;
> import java.util.concurrent.ConcurrentLinkedQueue;
> import java.util.concurrent.atomic.AtomicInteger;
> -import java.util.concurrent.locks.ReentrantLock;
> import java.util.logging.Level;
> import java.util.logging.Logger;
>
> @@ -125,7 +127,7 @@
> * @author Jeanfrancois Arcand
> */
> public class Controller implements Runnable, Lifecycle, Copyable,
> - ConnectorHandlerPool, AttributeHolder {
> + ConnectorHandlerPool, AttributeHolder, SupportStateHolder<State> {
>
> public enum Protocol { UDP, TCP , TLS, CUSTOM }
>
> @@ -173,27 +175,12 @@
>
>
> /**
> - * Controller state enum
> - * STOPPED - Controller is in a stopped, not running state
> - * STARTED - Controller is in a started, running state
> - *
> + * Current <code>Controller</code> state
> */
> - protected enum State { STOPPED, STARTED }
> + protected StateHolder<State> stateHolder;
>
>
> /**
> - * Current Controller state
> - */
> - protected volatile State state;
> -
> -
> - /**
> - * State lock to have consistent state value
> - */
> - protected ReentrantLock stateLock;
> -
> -
> - /**
> * The number of read threads
> */
> protected int readThreadsCount = 0;
> @@ -253,7 +240,7 @@
> */
> public Controller() {
> contexts = new ConcurrentLinkedQueue<Context>();
> - stateLock = new ReentrantLock();
> + stateHolder = new StateHolder<State>(true);
> }
>
>
> @@ -301,7 +288,9 @@
> readyKeys = selectorHandler.select(serverCtx);
> selectorState = readyKeys.size();
>
> - if (state == State.STARTED && selectorState != 0) {
> + if (stateHolder.getState(false) == State.STARTED &&
> + selectorHandler.getStateHolder().getState(false) == State.STARTED &&
> + selectorState != 0) {
> iterator = readyKeys.iterator();
> while (iterator.hasNext()) {
> key = iterator.next();
> @@ -398,20 +387,17 @@
> // shutting down. Hence, we need to handle this Exception
> // appropriately. Perhaps check the state before logging
> // what's happening ?
> - stateLock.lock();
> - try {
> - if (state != State.STOPPED) {
> + if (stateHolder.getState() == State.STARTED &&
> + selectorHandler.getStateHolder().getState() == State.STARTED) {
> logger.log(Level.SEVERE, "Selector was unexpectedly closed.");
> notifyException(e);
> } else {
> logger.log(Level.FINE, "doSelect Selector closed");
> }
> - } finally {
> - stateLock.unlock();
> - }
> } catch (ClosedChannelException e) {
> // Don't use stateLock. This case is not strict
> - if (state != State.STOPPED) {
> + if (stateHolder.getState() == State.STARTED &&
> + selectorHandler.getStateHolder().getState() == State.STARTED) {
> logger.log(Level.WARNING, "Channel was unexpectedly closed");
> if (key != null){
> selectorHandler.getSelectionKeyHandler().cancel(key);
> @@ -457,7 +443,7 @@
> * @param protocol specified protocol SelectorHandler key should be registered on
> */
> public void registerKey(SelectionKey key, int ops, Protocol protocol){
> - if (state == State.STOPPED) {
> + if (stateHolder.getState() == State.STOPPED) {
> return;
> }
>
> @@ -471,7 +457,7 @@
> * @deprecated
> */
> public void cancelKey(SelectionKey key){
> - if (state == State.STOPPED) {
> + if (stateHolder.getState() == State.STOPPED) {
> return;
> }
>
> @@ -587,7 +573,17 @@
> selectorHandlers = new ConcurrentLinkedQueue<SelectorHandler>();
> }
> selectorHandlers.add(selectorHandler);
> + if (stateHolder.getState(false) != null &&
> + !State.STOPPED.equals(stateHolder.getState())) {
> + addSelectorHandlerOnReadControllers(selectorHandler);
> + if (readySelectorHandlerCounter != null &&
> + stoppedSelectorHandlerCounter != null) {
> + readySelectorHandlerCounter.incrementAndGet();
> + stoppedSelectorHandlerCounter.incrementAndGet();
> }
> + startSelectorHandlerRunner(selectorHandler, true);
> + }
> + }
>
>
> /**
> @@ -639,6 +635,25 @@
>
>
> /**
> + * Shuts down <code>SelectorHandler</code> and removes it from this
> + * <code>Controller</code> list
> + * @param <code>SelectorHandler</code> to remove
> + */
> + public void removeSelectorHandler(SelectorHandler selectorHandler) {
> + if (selectorHandlers.remove(selectorHandler)) {
> + if (stateHolder.getState(false) != null &&
> + !State.STOPPED.equals(stateHolder.getState())) {
> + readySelectorHandlerCounter.decrementAndGet();
> + stoppedSelectorHandlerCounter.decrementAndGet();
> + }
> +
> + removeSelectorHandlerOnReadControllers(selectorHandler);
> + selectorHandler.shutdown();
> + }
> + }
> +
> +
> + /**
> * Return the <code>Pipeline</code> (Thread Pool) used by this Controller.
> */
> public Pipeline getPipeline() {
> @@ -715,8 +730,7 @@
> copyController.readThreadControllers = readThreadControllers;
> copyController.readThreadsCount = readThreadsCount;
> copyController.selectionKeyHandler = selectionKeyHandler;
> - copyController.stateLock = stateLock;
> - copyController.state = state;
> + copyController.stateHolder = stateHolder;
> }
>
> // -------------------------------------------------------- Lifecycle ----//
> @@ -813,12 +827,7 @@
> pipeline.initPipeline();
> pipeline.startPipeline();
>
> - stateLock.lock();
> - try {
> - state = State.STARTED;
> - } finally {
> - stateLock.unlock();
> - }
> + stateHolder.setState(State.STARTED);
> notifyStarted();
>
> if (readThreadsCount > 0) {
> @@ -832,15 +841,8 @@
> stoppedSelectorHandlerCounter = new AtomicInteger(selectorHandlerCount);
>
> for (SelectorHandler selectorHandler : selectorHandlers) {
> - Runnable selectorRunner = new SelectorHandlerRunner(this, selectorHandler);
> - if(selectorHandlerCount > 1) {
> - // if there are more than 1 selector handler - run it in separate thread
> - new Thread(selectorRunner).start();
> - } else {
> - // else run it in current thread
> - selectorRunner.run();
> + startSelectorHandlerRunner(selectorHandler, selectorHandlerCount > 1);
> }
> - }
>
> waitUntilSeletorHandlersStop();
> selectorHandlers.clear();
> @@ -853,10 +855,10 @@
> * Stop the Controller by canceling all the registered keys.
> */
> public void stop() throws IOException {
> - stateLock.lock();
> + stateHolder.getStateLocker().writeLock().lock();
> try {
> - if (state != State.STOPPED) {
> - state = State.STOPPED;
> + if (stateHolder.getState(false) != State.STOPPED) {
> + stateHolder.setState(State.STOPPED, false);
> // TODO: Consider moving the for Controller loop below to
> // the end of the start() method and using a
> // wait() / notify() construct to shutdown this
> @@ -883,29 +885,38 @@
> logger.log(Level.FINE, "Controller is already in stopped state");
> }
> } finally {
> - stateLock.unlock();
> + stateHolder.getStateLocker().writeLock().unlock();
> }
>
> }
>
>
> /**
> - * Not implemented.
> + * Pause this <code>Controller</code> and associated <code>SelectorHandler</code>s
> */
> public void pause() throws IOException {
> - ; // Not yet implemented
> + stateHolder.setState(State.PAUSED);
> }
>
>
> /**
> - * Not implemented.
> + * Resume this <code>Controller</code> and associated <code>SelectorHandler</code>s
> */
> public void resume() throws IOException {
> - ; // Not yet implemented
> + stateHolder.setState(State.STARTED);
> }
>
>
> /**
> + * Gets this <code>Controller</code>'s <code>StateHolder</code>
> + * @return <code>StateHolder</code>
> + */
> + public StateHolder<State> getStateHolder() {
> + return stateHolder;
> + }
> +
> +
> + /**
> * Initialize the number of ReadThreadController.
> */
> private void initReadThreads() throws IOException {
> @@ -918,40 +929,80 @@
> for(int i=0; i<readThreadsCount; i++) {
> ReadController controller = new ReadController();
> copyTo(controller);
> - for (SelectorHandler selectorHandler: selectorHandlers){
> + controller.setReadThreadsCount(0);
> + readThreadControllers[i] = controller;
> + }
> +
> + for (SelectorHandler selectorHandler : selectorHandlers) {
> + addSelectorHandlerOnReadControllers(selectorHandler);
> + }
> +
> + for (Controller readController : readThreadControllers) {
> + // TODO Get a Thread from a Pool instead.
> + new Thread(readController).start();
> + }
> + }
> +
> +
> + /**
> + * Register <code>SelectorHandler</code> on all read controllers
> + * @param selectorHandler
> + */
> + private void addSelectorHandlerOnReadControllers(SelectorHandler selectorHandler) {
> + if (readThreadControllers == null || readThreadsCount == 0) return;
> +
> // Attributes need to be shared among SelectorHandler and its read-copies
> if (selectorHandler.getAttributes() == null) {
> selectorHandler.setAttributes(new HashMap<String, Object>(2));
> }
>
> + for (Controller readController : readThreadControllers) {
> SelectorHandler copySelectorHandler = Cloner.clone(selectorHandler);
> copySelectorHandler.setSelector(null);
> - controller.addSelectorHandler(copySelectorHandler);
> + readController.addSelectorHandler(copySelectorHandler);
> }
> - controller.setReadThreadsCount(0);
> - readThreadControllers[i] = controller;
> - // TODO Get a Thread from a Pool instead.
> - new Thread(controller).start();
> }
> +
> +
> + /**
> + * Starts <code>SelectorHandlerRunner</code>
> + * @param selectorHandler
> + * @param isRunAsync if true - <code>SelectorHandlerRunner</code> will be run
> + * in separate <code>Thread</code>, if false - in current <code>Thread</code>
> + */
> + private void startSelectorHandlerRunner(SelectorHandler selectorHandler,
> + boolean isRunAsync) {
> + Runnable selectorRunner = new SelectorHandlerRunner(this, selectorHandler);
> + if(isRunAsync) {
> + // if there are more than 1 selector handler - run it in separate thread
> + new Thread(selectorRunner).start();

Add the TODO: need a thread pool like you did above.

> + } else {
> + // else run it in current thread
> + selectorRunner.run();
> }
> + }
>
>
> /**
> + * Register <code>SelectorHandler</code> on all read controllers
> + * @param selectorHandler
> + */
> + private void removeSelectorHandlerOnReadControllers(SelectorHandler selectorHandler) {
> + if (readThreadControllers == null) return;
> +
> + for (ReadController readController : readThreadControllers) {
> + readController.removeSelectorHandlerClone(selectorHandler);
> + }
> + }
> +
> +
> + /**
> * Is this Controller started?
> * @return <code>boolean</code> true / false
> */
> public boolean isStarted() {
> - boolean result = false;
> - if (stateLock != null){
> - stateLock.lock();
> - try {
> - result = (state == State.STARTED);
> - } finally {
> - stateLock.unlock();
> + return stateHolder.getState() == State.STARTED;
> }
> - }
> - return result;
> - }
>
>
> // ----------- ConnectorHandlerPool interface implementation ----------- //
> Index: ReadController.java
> --- ReadController.java Base (BASE)
> +++ ReadController.java Locally Modified (Based On LOCAL)
> @@ -27,6 +27,7 @@
> import java.nio.channels.SelectableChannel;
> import java.nio.channels.SelectionKey;
> import java.nio.channels.Selector;
> +import java.util.Iterator;
> import java.util.concurrent.atomic.AtomicInteger;
> import java.util.logging.Level;
>
> @@ -42,6 +43,23 @@
> public class ReadController extends Controller {
>
> /**
> + * Removes <code>SelectorHandler</code>'s clone, registered
> + * on this <code>Controller</code>
> + *
> + * @param selectorHandler
> + */
> + public void removeSelectorHandlerClone(SelectorHandler selectorHandler) {
> + Iterator<SelectorHandler> it = selectorHandlers.iterator();
> + while(it.hasNext()) {
> + SelectorHandler cloneSelectorHandler = it.next();
> + if (cloneSelectorHandler.getStateHolder() == selectorHandler.getStateHolder()) {
> + removeSelectorHandler(cloneSelectorHandler);
> + return;
> + }
> + }
> + }
> +
> + /**
> * Add a <code>Channel</code>
> * to be processed by <code>ReadController</code>'s
> * <code>SelectorHandler</code>
> @@ -78,15 +96,6 @@
> */
> @Override
> public void start() throws IOException {
> - stateLock.lock();
> - try {
> - // could be null if ReadController will be used outside main Controller
> - if (state == null) {
> - state = State.STARTED;
> - }
> - } finally {
> - stateLock.unlock();
> - }
> notifyStarted();
>
> int selectorHandlerCount = selectorHandlers.size();
> Index: SelectorHandler.java
> --- SelectorHandler.java Base (BASE)
> +++ SelectorHandler.java Locally Modified (Based On LOCAL)
> @@ -26,6 +26,8 @@
> import com.sun.grizzly.async.AsyncQueueWriter;
> import com.sun.grizzly.util.AttributeHolder;
> import com.sun.grizzly.util.Copyable;
> +import com.sun.grizzly.util.State;
> +import com.sun.grizzly.util.SupportStateHolder;
> import java.io.IOException;
> import java.nio.channels.SelectableChannel;
> import java.nio.channels.SelectionKey;
> @@ -40,7 +42,8 @@
> *
> * @author Jeanfrancois Arcand
> */
> -public interface SelectorHandler extends Handler, Copyable, AttributeHolder {
> +public interface SelectorHandler extends Handler, Copyable,
> + AttributeHolder, SupportStateHolder<State> {
>
> /**
> * A token decribing the protocol supported by an implementation of this
> @@ -79,6 +82,18 @@
>
>
> /**
> + * Pause this <code>SelectorHandler</code>
> + */
> + public void pause();
> +
> +
> + /**
> + * Resume this <code>SelectorHandler</code>
> + */
> + public void resume();
> +
> +
> + /**
> * Shutdown this instance.
> */
> public void shutdown();
> Index: SelectorHandlerRunner.java
> --- SelectorHandlerRunner.java Base (BASE)
> +++ SelectorHandlerRunner.java Locally Modified (Based On LOCAL)
> @@ -23,8 +23,11 @@
>
> package com.sun.grizzly;
>
> -import java.nio.channels.SelectionKey;
> -import com.sun.grizzly.Controller.State;
> +import com.sun.grizzly.util.State;
> +import com.sun.grizzly.util.StateHolder;
> +import com.sun.grizzly.util.StateHolder.ConditionListener;
> +import java.util.concurrent.CountDownLatch;
> +import java.util.concurrent.TimeUnit;
>
> /**
> * Class is responsible for processing certain (single)
> @@ -48,25 +51,65 @@
>
> public void run() {
> boolean firstTimeSelect = true;
> + StateHolder<State> controllerStateHolder = controller.stateHolder;
> + StateHolder<State> selectorHandlerStateHolder = selectorHandler.getStateHolder();
> +
> try {
> - while (controller.state == State.STARTED) {
> + selectorHandler.getStateHolder().setState(State.STARTED);
> +
> + while (controllerStateHolder.getState(false) != State.STOPPED &&
> + selectorHandlerStateHolder.getState(false) != State.STOPPED) {
> +
> + State controllerState = controllerStateHolder.getState(false);
> + State selectorHandlerState = selectorHandlerStateHolder.getState(false);
> +
> + if (controllerState != State.PAUSED &&
> + selectorHandlerState != State.PAUSED) {
> controller.doSelect(selectorHandler);
>
> if (firstTimeSelect) {
> firstTimeSelect = false;
> controller.notifyReady();
> }
> - }
> + } else {
> + CountDownLatch latch = new CountDownLatch(1);
> + ConditionListener<State> controllerConditionListener =
> + registerForNotification(controllerState,
> + controllerStateHolder, latch);
> + ConditionListener<State> selectorHandlerConditionListener =
> + registerForNotification(selectorHandlerState,
> + selectorHandlerStateHolder, latch);
>
> - SelectionKeyHandler selectionKeyHandler = selectorHandler.getSelectionKeyHandler();
> -
> - for (SelectionKey selectionKey : selectorHandler.keys()) {
> - selectionKeyHandler.close(selectionKey);
> + try {
> + latch.await(5000, TimeUnit.MILLISECONDS);
> + } catch(InterruptedException e) {
> + } finally {
> + controllerStateHolder.removeConditionListener(controllerConditionListener);
> + selectorHandlerStateHolder.removeConditionListener(selectorHandlerConditionListener);
> }
> -
> - selectorHandler.shutdown();
> + }
> + }
> } finally {
> + selectorHandler.shutdown();
> controller.notifyStopped();
> }
> }
> +
> + /**
> + * Register <code>CountDownLatch</code> to be notified, when either Controller
> + * or SelectorHandler will change their state to correspondent values
> + * @param currentState initial/current <code>StateHolder</code> state
> + * @param stateHolder
> + * @param latch
> + * @return <code>ConditionListener</code> if listener is registered, null if
> + * condition is true right now
> + */
> + private ConditionListener<State> registerForNotification(State currentState,
> + StateHolder<State> stateHolder, CountDownLatch latch) {
> + if (currentState == State.PAUSED) {
> + return stateHolder.notifyWhenStateIsNotEqual(State.PAUSED, latch);
> + } else {
> + return stateHolder.notifyWhenStateIsEqual(State.STOPPED, latch);
> }
> + }
> +}
> \ No newline at end of file
> Index: TCPSelectorHandler.java
> --- TCPSelectorHandler.java Base (BASE)
> +++ TCPSelectorHandler.java Locally Modified (Based On LOCAL)
> @@ -33,6 +33,8 @@
> import com.sun.grizzly.util.Copyable;
> import com.sun.grizzly.util.SelectionKeyOP;
> import com.sun.grizzly.util.SelectionKeyOP.ConnectSelectionKeyOP;
> +import com.sun.grizzly.util.State;
> +import com.sun.grizzly.util.StateHolder;
> import java.io.IOException;
> import java.net.BindException;
> import java.net.InetAddress;
> @@ -42,6 +44,7 @@
> import java.net.SocketAddress;
> import java.net.SocketException;
> import java.nio.channels.ClosedChannelException;
> +import java.nio.channels.ClosedSelectorException;
> import java.nio.channels.SelectableChannel;
> import java.nio.channels.SelectionKey;
> import java.nio.channels.Selector;
> @@ -204,7 +207,10 @@
> */
> protected Map<String, Object> attributes;
>
> + protected StateHolder<State> stateHolder = new StateHolder<State>(true);
> +
> public TCPSelectorHandler(){
> + this(false);
> }
>
>
> @@ -232,6 +238,7 @@
> copyHandler.logger = logger;
> copyHandler.reuseAddress = reuseAddress;
> copyHandler.connectorInstanceHandler = connectorInstanceHandler;
> + copyHandler.stateHolder = stateHolder;
> }
>
>
> @@ -463,16 +470,42 @@
> selector.wakeup();
> }
>
> + /**
> + * {_at_inheritDoc}
> + */
> + public void pause() {
> + stateHolder.setState(State.PAUSED);
> + }
>
> /**
> + * {_at_inheritDoc}
> + */
> + public void resume() {
> + stateHolder.setState(State.STARTED);
> + }
> +
> + /**
> + * {_at_inheritDoc}
> + */
> + public StateHolder<State> getStateHolder() {
> + return stateHolder;
> + }
> +
> + /**
> * Shuntdown this instance by closing its Selector and associated channels.
> */
> public void shutdown() {
> - if (selector != null){
> + stateHolder.setState(State.STOPPED);
> +
> + if (selector != null) {
> + try {
> for (SelectionKey selectionKey : selector.keys()) {
> selectionKeyHandler.close(selectionKey);
> }
> + } catch (ClosedSelectorException e) {
> + // If Selector is already closed - OK
> }
> + }
>
> try{
> if (serverSocket != null)
> Index: util/State.java
> --- util/State.java Locally New
> +++ util/State.java Locally New
> @@ -0,0 +1,36 @@
> +/*
> + * The contents of this file are subject to the terms
> + * of the Common Development and Distribution License
> + * (the License). You may not use this file except in
> + * compliance with the License.
> + *
> + * You can obtain a copy of the license at
> + * https://glassfish.dev.java.net/public/CDDLv1.0.html or
> + * glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * See the License for the specific language governing
> + * permissions and limitations under the License.
> + *
> + * When distributing Covered Code, include this CDDL
> + * Header Notice in each file and include the License file
> + * at glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * If applicable, add the following below the CDDL Header,
> + * with the fields enclosed by brackets [] replaced by
> + * you own identifying information:
> + * "Portions Copyrighted [year] [name of copyright owner]"
> + *
> + * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
> + */
> +
> +package com.sun.grizzly.util;
> +
> +/**
> + * State enum
> + * STOPPED - Process is stopped, not running state
> + * STARTED - Process is started, running state
> + * PAUSED - Process is paused, not processing tasks
> + *
> + * @author Alexey Stashok
> + */
> +public enum State {
> + STOPPED, STARTED, PAUSED
> +}
> Index: util/StateHolder.java
> --- util/StateHolder.java Locally New
> +++ util/StateHolder.java Locally New
> @@ -0,0 +1,354 @@
> +/*
> + * The contents of this file are subject to the terms
> + * of the Common Development and Distribution License
> + * (the License). You may not use this file except in
> + * compliance with the License.
> + *
> + * You can obtain a copy of the license at
> + * https://glassfish.dev.java.net/public/CDDLv1.0.html or
> + * glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * See the License for the specific language governing
> + * permissions and limitations under the License.
> + *
> + * When distributing Covered Code, include this CDDL
> + * Header Notice in each file and include the License file
> + * at glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * If applicable, add the following below the CDDL Header,
> + * with the fields enclosed by brackets [] replaced by
> + * you own identifying information:
> + * "Portions Copyrighted [year] [name of copyright owner]"
> + *
> + * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
> + */
> +
> +package com.sun.grizzly.util;
> +
> +import com.sun.grizzly.Controller;
> +import java.util.Iterator;
> +import java.util.Map;
> +import java.util.concurrent.Callable;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.concurrent.CountDownLatch;
> +import java.util.concurrent.atomic.AtomicReference;
> +import java.util.concurrent.locks.ReentrantReadWriteLock;
> +import java.util.logging.Level;
> +
> +/**
> + * Class, which holds the state.
> + * Provides API for state change notification, state read/write access locking.
> + *
> + * @author Alexey Stashok
> + */
> +public class StateHolder<E> {
> + private AtomicReference<E> stateRef;
> +
> + private ReentrantReadWriteLock readWriteLock;
> + private volatile boolean isLockEnabled;
> +
> + private Map<ConditionListener<E>, Object> conditionListeners;
> +
> + /**
> + * Constructs <code>StateHolder</code>.
> + * StateHolder will work in not-locking mode.
> + */
> + public StateHolder() {
> + this(false);
> + }
> +
> + /**
> + * Constructs <code>StateHolder</code>.
> + * @param isLockEnabled locking mode
> + */
> + public StateHolder(boolean isLockEnabled) {
> + stateRef = new AtomicReference<E>();
> + readWriteLock = new ReentrantReadWriteLock();
> + conditionListeners = new ConcurrentHashMap<ConditionListener<E>, Object>();
> + this.isLockEnabled = isLockEnabled;
> + }
> +
> + /**
> + * Gets current state
> + * Current StateHolder locking mode will be used
> + * @return state
> + */
> + public E getState() {
> + return getState(isLockEnabled);
> + }
> +
> + /**
> + * Gets current state
> + * @param locked if true, get will be invoked in locking mode, false - non-locked
> + * @return state
> + */
> + public E getState(boolean locked) {
> + if (locked) {
> + readWriteLock.readLock().lock();
> + }
> +
> + E retState = stateRef.get();
> +
> + if (locked) {
> + readWriteLock.readLock().unlock();
> + }
> + return retState;
> + }
> +
> + /**
> + * Sets current state
> + * Current StateHolder locking mode will be used
> + * @param state
> + */
> + public void setState(E state) {
> + setState(state, isLockEnabled);
> + }
> +
> + /**
> + * Sets current state
> + * @param state
> + * @param locked if true, set will be invoked in locking mode, false - non-locking
> + */
> + public void setState(E state, boolean locked) {
> + if (locked) {
> + readWriteLock.writeLock().lock();
> + }
> +
> + stateRef.set(state);
> +
> + // downgrading lock to read
> + if (locked) {
> + readWriteLock.readLock().lock();
> + readWriteLock.writeLock().unlock();
> + }
> +
> + checkConditionListeners(state);
> +
> + if (locked) {
> + readWriteLock.readLock().unlock();
> + }
> + }
> +
> + /**
> + * Gets Read/Write locker, which is used by this <code>StateHolder</code>
> + * @return locker
> + */
> + public ReentrantReadWriteLock getStateLocker() {
> + return readWriteLock;
> + }
> +
> + /**
> + * Gets current locking mode
> + * @return true, if mode is set to locking, false otherwise
> + */
> + public boolean isLockEnabled() {
> + return isLockEnabled;
> + }
> +
> + /**
> + * Setss current locking mode
> + * @param isLockEnabled true, if mode will be set to locking, false otherwise
> + */
> + public void setLockEnabled(boolean isLockEnabled) {
> + this.isLockEnabled = isLockEnabled;
> + }
> +
> + /**
> + * Register listener, which will be notified, when state will be equal to passed
> + * one. Once listener will be notified - it will be removed from this
> + * <code>StateHolder</code>'s listener set.
> + * @param state State, listener is interested in
> + * @param listener Object, which will be notified. This <code>StateHolder</code>
> + * implementation works with Runnable, Callable, CountDownLatch, Object
> + * listeners
> + * @return <code>ConditionListener</code>, if current state is not equal to required
> + * and listener was registered, null if current state is equal to required.
> + * In both cases listener will be notified
> + */
> + public ConditionListener<E> notifyWhenStateIsEqual(E state, Object listener) {
> + boolean isLockEnabledLocal = isLockEnabled;
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().lock();
> + }
> +
> + ConditionListener<E> conditionListener = null;
> +
> + if (stateRef.get().equals(state)) {
> + EventListener.notifyListener(listener);
> + } else {
> + conditionListener = new EqualConditionListener<E>();
> + EventListener eventListener = new EventListener();
> + eventListener.set(listener);
> + conditionListener.set(state, eventListener);
> +
> + conditionListeners.put(conditionListener, this);
> + }
> +
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().unlock();
> + }
> +
> + return conditionListener;
> + }
> +
> + /**
> + * Register listener, which will be notified, when state will become not equal
> + * to passed one. Once listener will be notified - it will be removed from
> + * this <code>StateHolder</code>'s listener set.
> + * @param state State, listener is interested in
> + * @param listener Object, which will be notified. This <code>StateHolder</code>
> + * implementation works with Runnable, Callable, CountDownLatch, Object
> + * listeners
> + * @return <code>ConditionListener</code>, if current state is equal to required
> + * and listener was registered, null if current state is not equal to required.
> + * In both cases listener will be notified
> + */
> + public ConditionListener<E> notifyWhenStateIsNotEqual(E state, Object listener) {
> + boolean isLockEnabledLocal = isLockEnabled;
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().lock();
> + }
> +
> + ConditionListener<E> conditionListener = null;
> +
> + if (!stateRef.get().equals(state)) {
> + EventListener.notifyListener(listener);
> + } else {
> + conditionListener = new NotEqualConditionListener<E>();
> + EventListener eventListener = new EventListener();
> + eventListener.set(listener);
> + conditionListener.set(state, eventListener);
> +
> + conditionListeners.put(conditionListener, this);
> + }
> +
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().unlock();
> + }
> +
> + return conditionListener;
> + }
> +
> + /**
> + * Register custom condition listener, which will be notified, when listener's
> + * condition will become true. Once listener will be notified - it will be
> + * removed from this <code>StateHolder</code>'s listener set.
> + * @param conditionListener contains both condition and listener, which will be
> + * called, when condition become true
> + */
> + public void notifyWhenConditionMatchState(ConditionListener<E> conditionListener) {
> + boolean isLockEnabledLocal = isLockEnabled;
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().lock();
> + }
> +
> + E currentState = getState();
> +
> + if (conditionListener.check(currentState)) {
> + conditionListener.notifyListener();
> + } else {
> + conditionListeners.put(conditionListener, this);
> + }
> +
> + if (isLockEnabledLocal) {
> + getStateLocker().writeLock().unlock();
> + }
> + }
> +
> + public void removeConditionListener(ConditionListener<E> conditionListener) {
> + if (conditionListener == null) return;
> +
> + conditionListeners.remove(conditionListener);
> + }
> +
> + protected void checkConditionListeners(E state) {
> + Iterator<ConditionListener<E>> it = conditionListeners.keySet().iterator();
> + while(it.hasNext()) {
> + ConditionListener<E> listener = it.next();
> + try {
> + if (listener.check(state)) {
> + it.remove();
> + listener.notifyListener();
> + }
> + } catch(Exception e) {
> + Controller.logger().log(Level.WARNING, "Error calling ConditionListener", e);
> + }
> + }
> + }
> +
> + /**
> + * Common ConditionListener class, which could be used with StateHolder, to
> + * register custom conditions.
> + *
> + * On each state change - condition will be checked, if it's true - Condition's
> + * listener will be notified.
> + */
> + public static abstract class ConditionListener<E> {
> + public E state;
> + public EventListener listener;
> +
> + protected void set(E state, EventListener listener) {
> + this.state = state;
> + this.listener = listener;
> + }
> +
> + public void notifyListener() {
> + listener.notifyEvent();
> + }
> +
> + public abstract boolean check(E state);
> + }
> +
> + /**
> + * Equal ConditionListener implementation
> + * @param E state class
> + */
> + public static class EqualConditionListener<E> extends ConditionListener<E> {
> + public boolean check(E state) {
> + return state.equals(this.state);
> + }
> + }
> +
> + /**
> + * Not equal ConditionListener implementation
> + * @param E state class
> + */
> + public static class NotEqualConditionListener<E> extends ConditionListener<E> {
> + public boolean check(E state) {
> + return !state.equals(this.state);
> + }
> + }
> +
> + /**
> + * EventListener class, which is a part of
> + * <codE>EventConditionListener</code>, and implements notificatation logic,
> + * when condition becomes true.
> + */
> + public static class EventListener {
> + public Object notificationObject;
> +
> + public void set(Object notificationObject) {
> + this.notificationObject = notificationObject;
> + }
> +
> + public void notifyEvent() {
> + notifyListener(notificationObject);
> + }
> +
> + protected static void notifyListener(Object listener) {
> + if (listener instanceof CountDownLatch) {
> + ((CountDownLatch) listener).countDown();
> + } else if (listener instanceof Callable) {
> + try {
> + ((Callable) listener).call();
> + } catch(Exception e) {
> + throw new RuntimeException(e);
> + }
> + } else if (listener instanceof Runnable) {
> + ((Runnable) listener).run();
> + } else {
> + synchronized(listener) {
> + listener.notify();
> + }
> + }
> + }
> + }
> +}
> Index: util/SupportStateHolder.java
> --- util/SupportStateHolder.java Locally New
> +++ util/SupportStateHolder.java Locally New
> @@ -0,0 +1,37 @@
> +/*
> + * The contents of this file are subject to the terms
> + * of the Common Development and Distribution License
> + * (the License). You may not use this file except in
> + * compliance with the License.
> + *
> + * You can obtain a copy of the license at
> + * https://glassfish.dev.java.net/public/CDDLv1.0.html or
> + * glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * See the License for the specific language governing
> + * permissions and limitations under the License.
> + *
> + * When distributing Covered Code, include this CDDL
> + * Header Notice in each file and include the License file
> + * at glassfish/bootstrap/legal/CDDLv1.0.txt.
> + * If applicable, add the following below the CDDL Header,
> + * with the fields enclosed by brackets [] replaced by
> + * you own identifying information:
> + * "Portions Copyrighted [year] [name of copyright owner]"
> + *
> + * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
> + */
> +
> +package com.sun.grizzly.util;
> +
> +/**
> + * Interface implementors support <code>StateHolder</code> for state control
> + *
> + * @author Alexey Stashok
> + */
> +public interface SupportStateHolder<E> {
> + /**
> + * Gets <code>StateHolder</code> for this object
> + * @return <code>StateHolder</code>
> + */
> + public StateHolder<E> getStateHolder();
> +}
>

Looks pretty good! That code will be quite useful for GlassFish v3 :-)

Thanks!

-- Jeanfrancois



>
>
> ------------------------------------------------------------------------
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_grizzly.dev.java.net
> For additional commands, e-mail: dev-help_at_grizzly.dev.java.net