1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.util; 20 21 import java.util.concurrent.atomic.AtomicReference; 22 23 24 /* ------------------------------------------------------------ */ 25 /** Iterating Callback. 26 * <p>This specialized callback is used when breaking up an 27 * asynchronous task into smaller asynchronous tasks. A typical pattern 28 * is that a successful callback is used to schedule the next sub task, but 29 * if that task completes quickly and uses the calling thread to callback 30 * the success notification, this can result in a growing stack depth. 31 * </p> 32 * <p>To avoid this issue, this callback uses an AtomicReference to note 33 * if the success callback has been called during the processing of a 34 * sub task, and if so then the processing iterates rather than recurses. 35 * </p> 36 * <p>This callback is passed to the asynchronous handling of each sub 37 * task and a call the {@link #succeeded()} on this call back represents 38 * completion of the subtask. Only once all the subtasks are completed is 39 * the {#completed()} method called.</p> 40 * 41 */ 42 public abstract class IteratingCallback implements Callback 43 { 44 private enum State { WAITING, ITERATING, SUCCEEDED, FAILED }; 45 private final AtomicReference<State> _state = new AtomicReference<>(State.WAITING); 46 47 public IteratingCallback() 48 { 49 } 50 51 /* ------------------------------------------------------------ */ 52 /** 53 * Process a subtask. 54 * <p>Called by {@link #iterate()} to process a sub task of the overall task 55 * <p> 56 * @return True if the total task is complete. If false is returned 57 * then this Callback must be scheduled to receive either a call to 58 * {@link #succeeded()} or {@link #failed(Throwable)}. 59 * @throws Exception 60 */ 61 abstract protected boolean process() throws Exception; 62 63 abstract protected void completed(); 64 65 /* ------------------------------------------------------------ */ 66 /** This method is called initially to start processing and 67 * is then called by subsequent sub task success to continue 68 * processing. 69 */ 70 public void iterate() 71 { 72 try 73 { 74 // Keep iterating as long as succeeded() is called during process() 75 // If we are in WAITING state, either this is the first iteration or 76 // succeeded()/failed() were called already. 77 while(_state.compareAndSet(State.WAITING,State.ITERATING)) 78 { 79 // Make some progress by calling process() 80 if (process()) 81 { 82 // A true return indicates we are finished and no further callbacks 83 // are scheduled. So we must still be ITERATING. 84 if (_state.compareAndSet(State.ITERATING,State.SUCCEEDED)) 85 completed(); 86 else 87 throw new IllegalStateException("Already "+_state.get()); 88 return; 89 } 90 // else a callback has been scheduled. If it has not happened yet, 91 // we will still be ITERATING 92 else if (_state.compareAndSet(State.ITERATING,State.WAITING)) 93 // no callback yet, so break the loop and wait for it 94 break; 95 96 // The callback must have happened and we are either WAITING already or FAILED 97 // the loop test will work out which 98 } 99 } 100 catch(Exception e) 101 { 102 failed(e); 103 } 104 } 105 106 /* ------------------------------------------------------------ */ 107 @Override 108 public void succeeded() 109 { 110 // Try a short cut for the fast method. If we are still iterating 111 if (_state.compareAndSet(State.ITERATING,State.WAITING)) 112 // then next loop will continue processing, so nothing to do here 113 return; 114 115 // OK do it properly 116 loop: while(true) 117 { 118 switch(_state.get()) 119 { 120 case ITERATING: 121 if (_state.compareAndSet(State.ITERATING,State.WAITING)) 122 break loop; 123 continue; 124 125 case WAITING: 126 // we are really waiting, so use this callback thread to iterate some more 127 iterate(); 128 break loop; 129 130 default: 131 throw new IllegalStateException("Already "+_state.get()); 132 } 133 } 134 } 135 136 /* ------------------------------------------------------------ */ 137 /** 138 * Derivations of this method should always call super.failed(x) 139 * to check the state before handling the failure. 140 * @see org.eclipse.jetty.util.Callback#failed(java.lang.Throwable) 141 */ 142 @Override 143 public void failed(Throwable x) 144 { 145 loop: while(true) 146 { 147 switch(_state.get()) 148 { 149 case ITERATING: 150 if (_state.compareAndSet(State.ITERATING,State.FAILED)) 151 break loop; 152 continue; 153 154 case WAITING: 155 if (_state.compareAndSet(State.WAITING,State.FAILED)) 156 break loop; 157 continue; 158 159 default: 160 throw new IllegalStateException("Already "+_state.get(),x); 161 } 162 } 163 } 164 }