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.Public
37  @InterfaceStability.Evolving
38  public class TimeoutExceptionInjector {
39  
40    private static final Log LOG = LogFactory.getLog(TimeoutExceptionInjector.class);
41  
42    private final long maxTime;
43    private volatile boolean complete;
44    private final Timer timer;
45    private final TimerTask timerTask;
46    private long start = -1;
47  
48    /**
49     * Create a generic timer for a task/process.
50     * @param listener listener to notify if the process times out
51     * @param maxTime max allowed running time for the process. Timer starts on calls to
52     *          {@link #start()}
53     */
54    public TimeoutExceptionInjector(final ForeignExceptionListener listener, final long maxTime) {
55      this.maxTime = maxTime;
56      timer = new Timer();
57      timerTask = new TimerTask() {
58        @Override
59        public void run() {
60          // ensure we don't run this task multiple times
61          synchronized (this) {
62            // quick exit if we already marked the task complete
63            if (TimeoutExceptionInjector.this.complete) return;
64            // mark the task is run, to avoid repeats
65            TimeoutExceptionInjector.this.complete = true;
66          }
67          long end = EnvironmentEdgeManager.currentTimeMillis();
68          TimeoutException tee =  new TimeoutException(
69              "Timeout caused Foreign Exception", start, end, maxTime);
70          String source = "timer-" + timer;
71          listener.receive(new ForeignException(source, tee));
72        }
73      };
74    }
75  
76    public long getMaxTime() {
77      return maxTime;
78    }
79  
80    /**
81     * For all time forward, do not throw an error because the process has completed.
82     */
83    public void complete() {
84      // warn if the timer is already marked complete. This isn't going to be thread-safe, but should
85      // be good enough and its not worth locking just for a warning.
86      if (this.complete) {
87        LOG.warn("Timer already marked completed, ignoring!");
88        return;
89      }
90      LOG.debug("Marking timer as complete - no error notifications will be received for this timer.");
91      synchronized (this.timerTask) {
92        this.complete = true;
93      }
94      this.timer.cancel();
95    }
96  
97    /**
98     * Start a timer to fail a process if it takes longer than the expected time to complete.
99     * <p>
100    * Non-blocking.
101    * @throws IllegalStateException if the timer has already been marked done via {@link #complete()}
102    *           or {@link #trigger()}
103    */
104   public synchronized void start() throws IllegalStateException {
105     if (this.start >= 0) {
106       LOG.warn("Timer already started, can't be started again. Ignoring second request.");
107       return;
108     }
109     LOG.debug("Scheduling process timer to run in: " + maxTime + " ms");
110     timer.schedule(timerTask, maxTime);
111     this.start = EnvironmentEdgeManager.currentTimeMillis();
112   }
113 
114   /**
115    * Trigger the timer immediately.
116    * <p>
117    * Exposed for testing.
118    */
119   public void trigger() {
120     synchronized (timerTask) {
121       if (this.complete) {
122         LOG.warn("Timer already completed, not triggering.");
123         return;
124       }
125       LOG.debug("Triggering timer immediately!");
126       this.timer.cancel();
127       this.timerTask.run();
128     }
129   }
130 }