/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.async; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.util.NamedThreadPoolFactory; /** * Abstraction of an aysnchronous housekeeping thread/service. * <p> * Once started the housekeeper will run until the target is garbage collected or it is explicitly stopped. * * @param <T> the target of the housekeeping action */ public abstract class AbstractHousekeeper<T> { private static final ScheduledThreadPoolExecutor s_executor = new ScheduledThreadPoolExecutor(2, new NamedThreadPoolFactory("Housekeeper")); private static final Logger s_logger = LoggerFactory.getLogger(AbstractHousekeeper.class); private static final int PERIOD = Integer.parseInt(System.getProperty("Housekeeper.period", "3")); private final Reference<T> _target; private int _startCount; private ScheduledFuture<?> _cancel; /** * Constructs a new instance. * * @param target the target to perform the action on, not null */ protected AbstractHousekeeper(final T target) { s_logger.debug("Created housekeeper {} for {}", this, target); _target = new WeakReference<T>(target); } /** * Returns the time between housekeeping operations in seconds, the default is controlled by a system property. Only override this if there is a specific requirement to run at a particular * frequency, or multiple of the default. * * @return the period in seconds, or 0 for no operations */ protected int getPeriodSeconds() { return PERIOD; } /** * Begins scheduling of the housekeeper. This must be matched by an equal number of calls to {@link #stop} to cancel the scheduling. */ public synchronized void start() { if (_startCount++ == 0) { s_logger.info("Starting housekeeper {} for {}", this, _target); final int period = getPeriodSeconds(); if (period > 0) { _cancel = s_executor.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { if (housekeep()) { return; } else { s_logger.info("Housekeeper {} for {} returned false", this, _target); } } catch (Throwable t) { s_logger.error("Cancelling errored {} for {}", this, _target); s_logger.warn("Caught exception", t); } cancel(); } }, period, period, TimeUnit.SECONDS); } } } /** * Decrements the count maintained by {@link #start} to cancel the scheduling when the original call is paired. */ public synchronized void stop() { if (_startCount > 0) { if (--_startCount == 0) { cancel(); } } } /** * Cancels the scheduling immediately, regardless of how many times {@link #start} was called. */ protected synchronized void cancel() { s_logger.info("Stopping housekeeper {} for {}", this, _target); if (_cancel != null) { _cancel.cancel(false); _cancel = null; } _startCount = 0; } /** * Returns the target of the housekeeper, possibly null if it has already been garbage collected. * * @return the target, possibly null */ protected T getTarget() { return _target.get(); } /** * Performs the housekeeping action. * * @param target the housekeeping target, not null * @return true to carry on scheduling, or false to stop the scheduling */ protected abstract boolean housekeep(T target); /** * Handles a scheduled tick from the underlying executor. If the target has not been garbage collected, calls the user {@link #housekeep} operation, otherwise ceases the scheduling. * * @return true to carry on scheduling, or false to stop the scheduling */ protected boolean housekeep() { final T target = getTarget(); if (target != null) { s_logger.debug("Tick {} for {}", this, target); return housekeep(target); } else { s_logger.info("Target discarded, releasing callback {}", this); return false; } } }