1 //
2 // ========================================================================
3 // Copyright (c) 1995-2016 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.io.Closeable;
22 import java.io.IOException;
23 import java.io.InterruptedIOException;
24 import java.util.concurrent.CancellationException;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27 import java.util.concurrent.locks.Condition;
28 import java.util.concurrent.locks.ReentrantLock;
29
30 import org.eclipse.jetty.util.log.Log;
31 import org.eclipse.jetty.util.log.Logger;
32 import org.eclipse.jetty.util.thread.Invocable.InvocationType;
33
34 /**
35 * Provides a reusable {@link Callback} that can block the thread
36 * while waiting to be completed.
37 * <p>
38 * A typical usage pattern is:
39 * <pre>
40 * void someBlockingCall(Object... args) throws IOException
41 * {
42 * try(Blocker blocker = sharedBlockingCallback.acquire())
43 * {
44 * someAsyncCall(args, blocker);
45 * blocker.block();
46 * }
47 * }
48 * </pre>
49 */
50 public class SharedBlockingCallback
51 {
52 static final Logger LOG = Log.getLogger(SharedBlockingCallback.class);
53
54 private static Throwable IDLE = new ConstantThrowable("IDLE");
55 private static Throwable SUCCEEDED = new ConstantThrowable("SUCCEEDED");
56
57 private static Throwable FAILED = new ConstantThrowable("FAILED");
58
59 private final ReentrantLock _lock = new ReentrantLock();
60 private final Condition _idle = _lock.newCondition();
61 private final Condition _complete = _lock.newCondition();
62 private Blocker _blocker = new Blocker();
63
64 protected long getIdleTimeout()
65 {
66 return -1;
67 }
68
69 public Blocker acquire() throws IOException
70 {
71 long idle = getIdleTimeout();
72 _lock.lock();
73 try
74 {
75 while (_blocker._state != IDLE)
76 {
77 if (idle>0 && (idle < Long.MAX_VALUE/2))
78 {
79 // Wait a little bit longer than the blocker might block
80 if (!_idle.await(idle*2,TimeUnit.MILLISECONDS))
81 throw new IOException(new TimeoutException());
82 }
83 else
84 _idle.await();
85 }
86 _blocker._state = null;
87 return _blocker;
88 }
89 catch (InterruptedException x)
90 {
91 throw new InterruptedIOException();
92 }
93 finally
94 {
95 _lock.unlock();
96 }
97 }
98
99 protected void notComplete(Blocker blocker)
100 {
101 LOG.warn("Blocker not complete {}",blocker);
102 if (LOG.isDebugEnabled())
103 LOG.debug(new Throwable());
104 }
105
106 /**
107 * A Closeable Callback.
108 * Uses the auto close mechanism to check block has been called OK.
109 * <p>Implements {@link Callback} because calls to this
110 * callback do not blocak, rather they wakeup the thread that is blocked
111 * in {@link #block()}
112 */
113 public class Blocker implements Callback, Closeable
114 {
115 private Throwable _state = IDLE;
116
117 protected Blocker()
118 {
119 }
120
121 @Override
122 public InvocationType getInvocationType()
123 {
124 return InvocationType.NON_BLOCKING;
125 }
126
127 @Override
128 public void succeeded()
129 {
130 _lock.lock();
131 try
132 {
133 if (_state == null)
134 {
135 _state = SUCCEEDED;
136 _complete.signalAll();
137 }
138 else
139 throw new IllegalStateException(_state);
140 }
141 finally
142 {
143 _lock.unlock();
144 }
145 }
146
147 @Override
148 public void failed(Throwable cause)
149 {
150 _lock.lock();
151 try
152 {
153 if (_state == null)
154 {
155 if (cause==null)
156 _state=FAILED;
157 else if (cause instanceof BlockerTimeoutException)
158 // Not this blockers timeout
159 _state=new IOException(cause);
160 else
161 _state=cause;
162 _complete.signalAll();
163 }
164 else if (_state instanceof BlockerTimeoutException)
165 {
166 // Failure arrived late, block() already
167 // modified the state, nothing more to do.
168 }
169 else
170 {
171 throw new IllegalStateException(_state);
172 }
173 }
174 finally
175 {
176 _lock.unlock();
177 }
178 }
179
180 /**
181 * Block until the Callback has succeeded or failed and after the return leave in the state to allow reuse. This is useful for code that wants to
182 * repeatable use a FutureCallback to convert an asynchronous API to a blocking API.
183 *
184 * @throws IOException
185 * if exception was caught during blocking, or callback was cancelled
186 */
187 public void block() throws IOException
188 {
189 long idle = getIdleTimeout();
190 _lock.lock();
191 try
192 {
193 while (_state == null)
194 {
195 if (idle > 0)
196 {
197 // Waiting here may compete with the idle timeout mechanism,
198 // so here we wait a little bit longer to favor the normal
199 // idle timeout mechanism that will call failed(Throwable).
200 long excess = Math.min(idle / 2, 1000);
201 if (!_complete.await(idle + excess, TimeUnit.MILLISECONDS))
202 {
203 // Method failed(Throwable) has not been called yet,
204 // so we will synthesize a special TimeoutException.
205 _state = new BlockerTimeoutException();
206 }
207 }
208 else
209 {
210 _complete.await();
211 }
212 }
213
214 if (_state == SUCCEEDED)
215 return;
216 if (_state == IDLE)
217 throw new IllegalStateException("IDLE");
218 if (_state instanceof IOException)
219 throw (IOException)_state;
220 if (_state instanceof CancellationException)
221 throw (CancellationException)_state;
222 if (_state instanceof RuntimeException)
223 throw (RuntimeException)_state;
224 if (_state instanceof Error)
225 throw (Error)_state;
226 throw new IOException(_state);
227 }
228 catch (final InterruptedException e)
229 {
230 throw new InterruptedIOException();
231 }
232 finally
233 {
234 _lock.unlock();
235 }
236 }
237
238 /**
239 * Check the Callback has succeeded or failed and after the return leave in the state to allow reuse.
240 */
241 @Override
242 public void close()
243 {
244 _lock.lock();
245 try
246 {
247 if (_state == IDLE)
248 throw new IllegalStateException("IDLE");
249 if (_state == null)
250 notComplete(this);
251 }
252 finally
253 {
254 try
255 {
256 // If the blocker timed itself out, remember the state
257 if (_state instanceof BlockerTimeoutException)
258 // and create a new Blocker
259 _blocker=new Blocker();
260 else
261 // else reuse Blocker
262 _state = IDLE;
263 _idle.signalAll();
264 _complete.signalAll();
265 }
266 finally
267 {
268 _lock.unlock();
269 }
270 }
271 }
272
273 @Override
274 public String toString()
275 {
276 _lock.lock();
277 try
278 {
279 return String.format("%s@%x{%s}",Blocker.class.getSimpleName(),hashCode(),_state);
280 }
281 finally
282 {
283 _lock.unlock();
284 }
285 }
286 }
287
288 private static class BlockerTimeoutException extends TimeoutException
289 {
290 }
291 }