package com.tesora.dve.concurrent; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import com.tesora.dve.exceptions.PECodingException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; /** * */ public class ChainedNotifier<R> implements CompletionTarget<R>, CompletionHandle<R>, CompletionNotifier<R>, SynchronousCompletion<R>, Future<R> { AtomicReference<State<R>> state; public ChainedNotifier() { state = new AtomicReference<State<R>>( new State<R>(State.WaitState.PARENT_SUCCESS_LOCAL_WAIT,null,null,null,null)); } public ChainedNotifier(CompletionNotifier<R> dependsOn) { state = new AtomicReference<State<R>>( new State<R>()); ParentAdapter listenForParentFinish = new ParentAdapter(); dependsOn.addListener(listenForParentFinish); } protected ChainedNotifier(State<R> state) { this.state = new AtomicReference<State<R>>( state ); } @Override public void addListener(CompletionTarget<R> listener) { State<R> current; while (! (current = state.get()).isFullfilled() ){ State<R> next = current.nextViaAddListener(listener); if (state.compareAndSet(current,next)) return; //installed new listener. } //didn't install listener, must already be finished, callback on register thread. if (current.isSuccess()) listener.success(current.result); else listener.failure(current.error); } @Override public boolean trySuccess(R returnValue) { State<R> current; while (! (current = state.get()).isFullfilled() ){ State<R> next = current.nextViaLocalSuccess(returnValue); if (state.compareAndSet(current,next)) { if (next.isFullfilled()) next.notifyListeners();//local success triggered success, tell listeners. return true; //Caller is probably interested if someone already signaled local success, return true regardless of parent state. } } return false; } protected void handleParentSuccess(R returnValue) { State<R> current; while (! (current = state.get()).isFullfilled() ){ State<R> next = current.nextViaParentSuccess(returnValue); if (state.compareAndSet(current,next)) { if (next.isFullfilled()) next.notifyListeners();//parent success triggered success, tell listeners. break; } } } protected void handleParentFailure(Exception e) { this.failure(e); } @Override public boolean isFulfilled() { return state.get().isFullfilled(); } @Override public void success(R returnValue) { trySuccess(returnValue); } @Override public void failure(Exception e) { State<R> current; while (! (current = state.get()).isFullfilled() ){ State<R> next = current.nextViaFailure(e); if (state.compareAndSet(current,next)) { next.notifyListeners();//failure triggered finish, tell listeners. break; } } } public static <V> ChainedNotifier<V> newInstance(){ return new ChainedNotifier<>(); } public static <V> ChainedNotifier<V> newInstance(CompletionNotifier<V> dependsOn){ if (dependsOn == null) return newInstance(); else return new ChainedNotifier<>(dependsOn); } public static <V> ChainedNotifier<V> noop(V result){ return new ChainedNotifier<V>(new State<>(State.WaitState.SUCCESS,result,null,null,null)); } @Override public R sync() throws Exception { SynchronousListener<R> syncListener = new SynchronousListener<R>(); this.addListener(syncListener); return syncListener.sync(); } public R sync(boolean canInterrupt) throws Exception { SynchronousListener<R> syncListener = new SynchronousListener<R>(); this.addListener(syncListener); return syncListener.sync(canInterrupt); } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return isFulfilled(); } @Override public R get() throws InterruptedException, ExecutionException { try { return sync(); } catch (InterruptedException | ExecutionException e) { throw e; } catch (Exception e){ throw new ExecutionException(e); } } @Override public R get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return get(); } private static class State<V> { enum WaitState { WAITING_ON_BOTH, PARENT_SUCCESS_LOCAL_WAIT, LOCAL_SUCCESS_PARENT_WAIT, SUCCESS, FAILURE } final private WaitState waitStatus; final private V result; final private Exception error; final private CompletionTarget<V> listener; final private State<V> next; public State(){ this(WaitState.WAITING_ON_BOTH,null,null,null,null); } public State(WaitState waitStatus, V result, Exception error,CompletionTarget<V> listener, State<V> next) { this.waitStatus = waitStatus; this.result = result; this.error = error; this.listener = listener; this.next = next; } public boolean isSuccess(){ return waitStatus == WaitState.SUCCESS; } public boolean isFailure(){ return waitStatus == WaitState.FAILURE; } public boolean isFullfilled(){ return isSuccess() || isFailure(); } public State<V> nextViaParentSuccess(V someResult){ switch (waitStatus) { case SUCCESS: case FAILURE: case PARENT_SUCCESS_LOCAL_WAIT: return this; case WAITING_ON_BOTH: return new State<V>(WaitState.PARENT_SUCCESS_LOCAL_WAIT,null,null,null,this); case LOCAL_SUCCESS_PARENT_WAIT: return new State<V>(WaitState.SUCCESS,this.result,null,null,this); default: return throwUnexpectedState(); } } public State<V> nextViaLocalSuccess(V someResult){ switch (waitStatus) { case SUCCESS: case FAILURE: case LOCAL_SUCCESS_PARENT_WAIT: return this; case WAITING_ON_BOTH: return new State<V>(WaitState.LOCAL_SUCCESS_PARENT_WAIT,someResult,null,null,this); case PARENT_SUCCESS_LOCAL_WAIT: return new State<V>(WaitState.SUCCESS,someResult,null,null,this); default: return throwUnexpectedState(); } } public State<V> nextViaFailure(Exception e){ switch (waitStatus) { case SUCCESS: case FAILURE: return this; case WAITING_ON_BOTH: case LOCAL_SUCCESS_PARENT_WAIT: case PARENT_SUCCESS_LOCAL_WAIT: return new State<V>(WaitState.FAILURE,null,e,null,this); default: return throwUnexpectedState(); } } public State<V> nextViaAddListener(CompletionTarget<V> listener){ switch (waitStatus) { case SUCCESS: case FAILURE: return this; case WAITING_ON_BOTH: case LOCAL_SUCCESS_PARENT_WAIT: case PARENT_SUCCESS_LOCAL_WAIT: return new State<V>(this.waitStatus,this.result,this.error,listener,this); default: return throwUnexpectedState(); } } protected void notifyListeners(){ notifyFinished(this); } protected static <T> void notifyFinished(State<T> triggerState){ if (triggerState.isSuccess()) notifySuccess(triggerState); else notifyFailure(triggerState); } protected static <T> void notifySuccess(State<T> triggerState){ T result = triggerState.result; while (triggerState != null){ if (triggerState.listener != null) triggerState.listener.success(result); triggerState = triggerState.next; } } protected static <T> void notifyFailure(State<T> triggerState){ Exception e = triggerState.error; while (triggerState != null){ if (triggerState.listener != null) triggerState.listener.failure(e); triggerState = triggerState.next; } } private State<V> throwUnexpectedState() { throw new PECodingException("Unexpected state in enum,"+waitStatus); } } private class ParentAdapter implements CompletionTarget<R> { @Override public void success(R returnValue) { handleParentSuccess(returnValue); } @Override public void failure(Exception e) { handleParentFailure(e); } } }