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   * If currently asleep, stops sleeping; if not asleep, will skip the next
054   * sleep cycle.
055   */
056  public void skipSleepCycle() {
057    synchronized (sleepLock) {
058      triggerWake = true;
059      sleepLock.notifyAll();
060    }
061  }
062
063  /**
064   * Sleep for period.
065   */
066  public void sleep() {
067    sleep(this.period);
068  }
069
070  public void sleep(long sleepTime) {
071    if (this.stopper.isStopped()) {
072      return;
073    }
074    long now = System.currentTimeMillis();
075    long currentSleepTime = sleepTime;
076    while (currentSleepTime > 0) {
077      long woke = -1;
078      try {
079        synchronized (sleepLock) {
080          if (triggerWake) break;
081          sleepLock.wait(currentSleepTime);
082        }
083        woke = System.currentTimeMillis();
084        long slept = woke - now;
085        if (slept - this.period > MINIMAL_DELTA_FOR_LOGGING) {
086          LOG.warn("We slept " + slept + "ms instead of " + this.period +
087              "ms, this is likely due to a long " +
088              "garbage collecting pause and it's usually bad, see " +
089              "http://hbase.apache.org/book.html#trouble.rs.runtime.zkexpired");
090        }
091      } catch(InterruptedException iex) {
092        // We we interrupted because we're meant to stop?  If not, just
093        // continue ignoring the interruption
094        if (this.stopper.isStopped()) {
095          return;
096        }
097      }
098      // Recalculate waitTime.
099      woke = (woke == -1)? System.currentTimeMillis(): woke;
100      currentSleepTime = this.period - (woke - now);
101    }
102    synchronized(sleepLock) {
103      triggerWake = false;
104    }
105  }
106
107  /**
108   * @return the sleep period in milliseconds
109   */
110  public final int getPeriod() {
111    return period;
112  }
113}