View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.errorhandling;
19  
20  import java.util.Timer;
21  import java.util.TimerTask;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.hbase.classification.InterfaceAudience;
26  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
27  
28  /**
29   * Time a given process/operation and report a failure if the elapsed time exceeds the max allowed
30   * time.
31   * <p>
32   * The timer won't start tracking time until calling {@link #start()}. If {@link #complete()} or
33   * {@link #trigger()} is called before {@link #start()}, calls to {@link #start()} will fail.
34   */
35  @InterfaceAudience.Private
36  public class TimeoutExceptionInjector {
37  
38    private static final Log LOG = LogFactory.getLog(TimeoutExceptionInjector.class);
39  
40    private final long maxTime;
41    private volatile boolean complete;
42    private final Timer timer;
43    private final TimerTask timerTask;
44    private long start = -1;
45  
46    /**
47     * Create a generic timer for a task/process.
48     * @param listener listener to notify if the process times out
49     * @param maxTime max allowed running time for the process. Timer starts on calls to
50     *          {@link #start()}
51     */
52    public TimeoutExceptionInjector(final ForeignExceptionListener listener, final long maxTime) {
53      this.maxTime = maxTime;
54      timer = new Timer();
55      timerTask = new TimerTask() {
56        @Override
57        public void run() {
58          // ensure we don't run this task multiple times
59          synchronized (this) {
60            // quick exit if we already marked the task complete
61            if (TimeoutExceptionInjector.this.complete) return;
62            // mark the task is run, to avoid repeats
63            TimeoutExceptionInjector.this.complete = true;
64          }
65          long end = EnvironmentEdgeManager.currentTime();
66          TimeoutException tee =  new TimeoutException(
67              "Timeout caused Foreign Exception", start, end, maxTime);
68          String source = "timer-" + timer;
69          listener.receive(new ForeignException(source, tee));
70        }
71      };
72    }
73  
74    public long getMaxTime() {
75      return maxTime;
76    }
77  
78    /**
79     * For all time forward, do not throw an error because the process has completed.
80     */
81    public void complete() {
82      synchronized (this.timerTask) {
83        if (this.complete) {
84          LOG.warn("Timer already marked completed, ignoring!");
85          return;
86        }
87        if (LOG.isDebugEnabled()) {
88          LOG.debug("Marking timer as complete - no error notifications will be received for " +
89            "this timer.");
90        }
91        this.complete = true;
92      }
93      this.timer.cancel();
94    }
95  
96    /**
97     * Start a timer to fail a process if it takes longer than the expected time to complete.
98     * <p>
99     * Non-blocking.
100    * @throws IllegalStateException if the timer has already been marked done via {@link #complete()}
101    *           or {@link #trigger()}
102    */
103   public synchronized void start() throws IllegalStateException {
104     if (this.start >= 0) {
105       LOG.warn("Timer already started, can't be started again. Ignoring second request.");
106       return;
107     }
108     LOG.debug("Scheduling process timer to run in: " + maxTime + " ms");
109     timer.schedule(timerTask, maxTime);
110     this.start = EnvironmentEdgeManager.currentTime();
111   }
112 
113   /**
114    * Trigger the timer immediately.
115    * <p>
116    * Exposed for testing.
117    */
118   public void trigger() {
119     synchronized (timerTask) {
120       if (this.complete) {
121         LOG.warn("Timer already completed, not triggering.");
122         return;
123       }
124       LOG.debug("Triggering timer immediately!");
125       this.timer.cancel();
126       this.timerTask.run();
127     }
128   }
129 }