001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019package org.apache.hadoop.hbase.util;
020
021import org.apache.hadoop.hbase.Stoppable;
022import org.apache.yetus.audience.InterfaceAudience;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Sleeper for current thread.
028 * Sleeps for passed period.  Also checks passed boolean and if interrupted,
029 * will return if the flag is set (rather than go back to sleep until its
030 * sleep time is up).
031 */
032@InterfaceAudience.Private
033public class Sleeper {
034  private static final Logger LOG = LoggerFactory.getLogger(Sleeper.class);
035  private final int period;
036  private final Stoppable stopper;
037  private static final long MINIMAL_DELTA_FOR_LOGGING = 10000;
038
039  private final Object sleepLock = new Object();
040  private boolean triggerWake = false;
041
042  /**
043   * @param sleep sleep time in milliseconds
044   * @param stopper When {@link Stoppable#isStopped()} is true, this thread will
045   * cleanup and exit cleanly.
046   */
047  public Sleeper(final int sleep, final Stoppable stopper) {
048    this.period = sleep;
049    this.stopper = stopper;
050  }
051
052  /**
053   * Sleep for period.
054   */
055  public void sleep() {
056    sleep(System.currentTimeMillis());
057  }
058
059  /**
060   * If currently asleep, stops sleeping; if not asleep, will skip the next
061   * sleep cycle.
062   */
063  public void skipSleepCycle() {
064    synchronized (sleepLock) {
065      triggerWake = true;
066      sleepLock.notifyAll();
067    }
068  }
069
070  /**
071   * Sleep for period adjusted by passed <code>startTime</code>
072   * @param startTime Time some task started previous to now.  Time to sleep
073   * will be docked current time minus passed <code>startTime</code>.
074   */
075  public void sleep(final long startTime) {
076    if (this.stopper.isStopped()) {
077      return;
078    }
079    long now = System.currentTimeMillis();
080    long waitTime = this.period - (now - startTime);
081    if (waitTime > this.period) {
082      LOG.warn("Calculated wait time > " + this.period +
083        "; setting to this.period: " + System.currentTimeMillis() + ", " +
084        startTime);
085      waitTime = this.period;
086    }
087    while (waitTime > 0) {
088      long woke = -1;
089      try {
090        synchronized (sleepLock) {
091          if (triggerWake) break;
092          sleepLock.wait(waitTime);
093        }
094        woke = System.currentTimeMillis();
095        long slept = woke - now;
096        if (slept - this.period > MINIMAL_DELTA_FOR_LOGGING) {
097          LOG.warn("We slept " + slept + "ms instead of " + this.period +
098              "ms, this is likely due to a long " +
099              "garbage collecting pause and it's usually bad, see " +
100              "http://hbase.apache.org/book.html#trouble.rs.runtime.zkexpired");
101        }
102      } catch(InterruptedException iex) {
103        // We we interrupted because we're meant to stop?  If not, just
104        // continue ignoring the interruption
105        if (this.stopper.isStopped()) {
106          return;
107        }
108      }
109      // Recalculate waitTime.
110      woke = (woke == -1)? System.currentTimeMillis(): woke;
111      waitTime = this.period - (woke - startTime);
112    }
113    synchronized(sleepLock) {
114      triggerWake = false;
115    }
116  }
117
118  /**
119   * @return the sleep period in milliseconds
120   */
121  public final int getPeriod() {
122    return period;
123  }
124}