View Javadoc
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 }