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.classification.InterfaceAudience;
26  import org.apache.hadoop.classification.InterfaceStability;
27  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
28  
29  /**
30   * Time a given process/operation and report a failure if the elapsed time exceeds the max allowed
31   * time.
32   * <p>
33   * The timer won't start tracking time until calling {@link #start()}. If {@link #complete()} or
34   * {@link #trigger()} is called before {@link #start()}, calls to {@link #start()} will fail.
35   */
36  @InterfaceAudience.Private
37  public class TimeoutExceptionInjector {
38  
39    private static final Log LOG = LogFactory.getLog(TimeoutExceptionInjector.class);
40  
41    private final long maxTime;
42    private volatile boolean complete;
43    private final Timer timer;
44    private final TimerTask timerTask;
45    private long start = -1;
46  
47    /**
48     * Create a generic timer for a task/process.
49     * @param listener listener to notify if the process times out
50     * @param maxTime max allowed running time for the process. Timer starts on calls to
51     *          {@link #start()}
52     */
53    public TimeoutExceptionInjector(final ForeignExceptionListener listener, final long maxTime) {
54      this.maxTime = maxTime;
55      timer = new Timer();
56      timerTask = new TimerTask() {
57        @Override
58        public void run() {
59          // ensure we don't run this task multiple times
60          synchronized (this) {
61            // quick exit if we already marked the task complete
62            if (TimeoutExceptionInjector.this.complete) return;
63            // mark the task is run, to avoid repeats
64            TimeoutExceptionInjector.this.complete = true;
65          }
66          long end = EnvironmentEdgeManager.currentTime();
67          TimeoutException tee =  new TimeoutException(
68              "Timeout caused Foreign Exception", start, end, maxTime);
69          String source = "timer-" + timer;
70          listener.receive(new ForeignException(source, tee));
71        }
72      };
73    }
74  
75    public long getMaxTime() {
76      return maxTime;
77    }
78  
79    /**
80     * For all time forward, do not throw an error because the process has completed.
81     */
82    public void complete() {
83      // warn if the timer is already marked complete. This isn't going to be thread-safe, but should
84      // be good enough and its not worth locking just for a warning.
85      if (this.complete) {
86        LOG.warn("Timer already marked completed, ignoring!");
87        return;
88      }
89      LOG.debug("Marking timer as complete - no error notifications will be received for this timer.");
90      synchronized (this.timerTask) {
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 }