/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.solr.hadoop; import java.lang.invoke.MethodHandles; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.hadoop.mapreduce.TaskInputOutputContext; import org.apache.hadoop.util.Progressable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class runs a background thread that once every 60 seconds checks to see if * a progress report is needed. If a report is needed it is issued. * * A simple counter {@link #threadsNeedingHeartBeat} handles the number of * threads requesting a heart beat. * * The expected usage pattern is * * <pre> * try { * heartBeater.needHeartBeat(); * do something that may take a while * } finally { * heartBeater.cancelHeartBeat(); * } * </pre> * * */ public class HeartBeater extends Thread { private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); /** * count of threads asking for heart beat, at 0 no heart beat done. This could * be an atomic long but then missmatches in need/cancel could result in * negative counts. */ private volatile int threadsNeedingHeartBeat = 0; private Progressable progress; /** * The amount of time to wait between checks for the need to issue a heart * beat. In milliseconds. */ private final long waitTimeMs = TimeUnit.MILLISECONDS.convert(60, TimeUnit.SECONDS); private final CountDownLatch isClosing = new CountDownLatch(1); /** * Create the heart beat object thread set it to daemon priority and start the * thread. When the count in {@link #threadsNeedingHeartBeat} is positive, the * heart beat will be issued on the progress object every 60 seconds. */ public HeartBeater(Progressable progress) { setDaemon(true); this.progress = progress; LOG.info("Heart beat reporting class is " + progress.getClass().getName()); start(); } public Progressable getProgress() { return progress; } public void setProgress(Progressable progress) { this.progress = progress; } @Override public void run() { LOG.info("HeartBeat thread running"); while (true) { try { synchronized (this) { if (threadsNeedingHeartBeat > 0) { progress.progress(); if (LOG.isInfoEnabled()) { LOG.info(String.format(Locale.ENGLISH, "Issuing heart beat for %d threads", threadsNeedingHeartBeat)); } } else { if (LOG.isInfoEnabled()) { LOG.info(String.format(Locale.ENGLISH, "heartbeat skipped count %d", threadsNeedingHeartBeat)); } } } if (isClosing.await(waitTimeMs, TimeUnit.MILLISECONDS)) { return; } } catch (Throwable e) { LOG.error("HeartBeat throwable", e); } } } /** * inform the background thread that heartbeats are to be issued. Issue a * heart beat also */ public synchronized void needHeartBeat() { threadsNeedingHeartBeat++; // Issue a progress report right away, // just in case the the cancel comes before the background thread issues a // report. // If enough cases like this happen the 600 second timeout can occur progress.progress(); if (threadsNeedingHeartBeat == 1) { // this.notify(); // wake up the heartbeater } } /** * inform the background thread that this heartbeat request is not needed. * This must be called at some point after each {@link #needHeartBeat()} * request. */ public synchronized void cancelHeartBeat() { if (threadsNeedingHeartBeat > 0) { threadsNeedingHeartBeat--; } else { Exception e = new Exception("Dummy"); e.fillInStackTrace(); LOG.warn("extra call to cancelHeartBeat", e); } } public void setStatus(String status) { if (progress instanceof TaskInputOutputContext) { ((TaskInputOutputContext<?,?,?,?>) progress).setStatus(status); } } /** Releases any resources */ public void close() { isClosing.countDown(); } }