Factored the automatically scheduled reboot code out into a reusable class.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4276 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2006-07-19 00:43:08 +00:00
parent 78c7b49f9b
commit 41b73ffcb4
@@ -0,0 +1,334 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.server;
import java.util.Calendar;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
import com.samskivert.util.StringUtil;
import com.threerings.util.MessageBundle;
import static com.threerings.presents.Log.log;
/**
* Handles scheduling and execution of automated server reboots. Note that this
* service simply shuts down the server and assumes it will be automatically
* restarted by some external entity. It is generally useful to run a server
* from a script that automatically restarts it when it terminates.
*/
public abstract class RebootManager
{
/**
* An interface for receiving notifications about pending automated
* shutdowns.
*/
public static interface PendingShutdownObserver
{
/**
* Called prior to an automated shutdown. NOTE that the shutdown
* is not guaranteed to happen, this interface is merely for
* notification purposes.
*
* @param warningsLeft The number of warnings left prior to the
* shutdown. This value will be 0 on the last warning, usually 1-2
* minutes prior to the actual shutdown.
* @param msLeft the approximate number of milliseconds left prior to
* the shutdown.
*/
public void shutdownPlanned (int warningsLeft, long msLeft);
}
/**
* Creates a reboot manager that will shutdown the supplied server when the
* time comes.
*/
public RebootManager (PresentsServer server)
{
_server = server;
}
/**
* Finish initialization of the manager.
*/
public void init ()
{
// maybe schedule an automatic reboot based on our configuration
int freq = getDayFrequency();
if (freq == -1) {
return;
}
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, freq);
cal.set(Calendar.HOUR_OF_DAY, getRebootHour());
cal.clear(Calendar.MINUTE);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MILLISECOND);
// maybe avoid weekends
if (getSkipWeekends()) {
int dow = cal.get(Calendar.DAY_OF_WEEK);
switch (dow) {
case Calendar.SATURDAY:
if (freq > 1) {
cal.add(Calendar.DATE, -1);
}
break;
case Calendar.SUNDAY:
if (freq > 2) {
cal.add(Calendar.DATE, -2);
}
break;
}
}
scheduleReboot(cal.getTimeInMillis(), "automatic");
}
/**
* Is the manager planning a shutdown in the near future?
*/
public boolean willShutdownSoon ()
{
return _rebootSoon;
}
/**
* Add an observer to the observer list.
*/
public void addObserver (PendingShutdownObserver observer)
{
_observers.add(observer);
}
/**
* Schedules a reboot for the specified time.
*/
public void scheduleReboot (long rebootTime, String initiator)
{
// if there's already a reboot scheduled, cancel it
if (_interval != null) {
_interval.cancel();
_interval = null;
}
// note our new reboot time and its initiator
_nextReboot = rebootTime;
_initiator = initiator;
// see if the reboot is happening within the time specified by the
// longest warning; if so, issue the appropriate warning
long now = System.currentTimeMillis();
for (int ii = WARNINGS.length - 1; ii >= 0; ii--) {
long warnTime = WARNINGS[ii] * 60 * 1000;
if (now + warnTime > _nextReboot) {
doWarning(ii);
return;
}
}
// otherwise, it's further off; schedule an interval to wake up when we
// should issue the first pre-reboot warning
_rebootSoon = false;
long firstWarnTime = (_nextReboot - (WARNINGS[0] * 60 * 1000)) - now;
_interval = new Interval(PresentsServer.omgr) {
public void expired () {
doWarning(0);
}
};
_interval.schedule(firstWarnTime);
}
/**
* Called by an entity that would like to prevent a reboot.
*/
public int preventReboot (String whereFrom)
{
if (whereFrom == null) {
throw new IllegalArgumentException(
"whereFrom must be descriptive.");
}
int lockId = _nextRebootLockId++;
_rebootLocks.put(lockId, whereFrom);
return lockId;
}
/**
* Release a reboot lock.
*/
public void allowReboot (int lockId)
{
if (null == _rebootLocks.remove(lockId)) {
throw new IllegalArgumentException(
"no such lockId (" + lockId + ")");
}
}
/**
* Broadcasts a message to everyone on the server.
*/
protected abstract void broadcast (String message);
/**
* Returns the frequency in days of our automatic reboots, or -1 to disable
* automatically scheduled reboots.
*/
protected abstract int getDayFrequency ();
/**
* Returns the desired hour at which to perform our reboot.
*/
protected abstract int getRebootHour ();
/**
* Returns true if the reboot manager should avoid scheduling automated
* reboots on the weekends.
*/
protected abstract boolean getSkipWeekends ();
/**
* Returns a custom message to be used when broadcasting a pending reboot.
*/
protected abstract String getCustomRebootMessage ();
/**
* Do a warning, schedule the next.
*/
protected void doWarning (final int level)
{
_rebootSoon = true;
if (level == WARNINGS.length) {
if (checkLocks()) {
return;
}
// that's it! do the reboot
log.info("Performing automatic server reboot/shutdown, " +
"as scheduled by: " + _initiator);
broadcast("m.rebooting_now");
// wait 1 second, then do it
new Interval(PresentsServer.omgr) {
public void expired () {
_server.shutdown();
}
}.schedule(1000);
return;
}
// issue the warning
String msg = getCustomRebootMessage();
if (StringUtil.isBlank(msg)) {
msg = "m.reboot_msg_standard";
}
int minutes = WARNINGS[level];
msg = MessageBundle.compose(
"m.reboot_warning", MessageBundle.taint("" + minutes), msg);
broadcast(msg);
if (level < WARNINGS.length - 1) {
minutes -= WARNINGS[level + 1];
}
// schedule the next warning
_interval = new Interval(PresentsServer.omgr) {
public void expired () {
doWarning(level + 1);
}
};
_interval.schedule(minutes * 60 * 1000);
notifyObservers(level);
}
/**
* Check to see if there are outstanding reboot locks that may delay the
* reboot, returning false if there are none.
*/
protected boolean checkLocks ()
{
if (_rebootLocks.isEmpty()) {
return false;
}
log.info("Reboot delayed due to outstanding locks: " +
StringUtil.toString(_rebootLocks.elements()));
broadcast("m.reboot_delayed");
_interval = new Interval(PresentsServer.omgr) {
public void expired () {
doWarning(WARNINGS.length);
}
};
_interval.schedule(60 * 1000);
return true;
}
/**
* Notify all PendingShutdownObservers of the pending shutdown!
*/
protected void notifyObservers (int level)
{
final int warningsLeft = WARNINGS.length - level - 1;
final long msLeft = 1000L * 60L * WARNINGS[level];
_observers.apply(
new ObserverList.ObserverOp<PendingShutdownObserver>() {
public boolean apply (PendingShutdownObserver observer) {
observer.shutdownPlanned(warningsLeft, msLeft);
return true;
}
});
}
/** The server we will reboot. */
protected PresentsServer _server;
/** The time at which our next reboot is scheduled or 0L. */
protected long _nextReboot;
/** The entity that scheduled the reboot. */
protected String _initiator;
/** True if the reboot is coming soon, within the earliest warning. */
protected boolean _rebootSoon = false;
/** The interval scheduled to perform the next step the reboot process. */
protected Interval _interval;
/** A list of PendingShutdownObservers. */
protected ObserverList<PendingShutdownObserver> _observers =
new ObserverList<PendingShutdownObserver>(
ObserverList.FAST_UNSAFE_NOTIFY);
/** The next reboot lock id. */
protected int _nextRebootLockId = 0;
/** Things that can delay the reboot. */
protected HashIntMap<String> _rebootLocks = new HashIntMap<String>();
/** The minutes at which we give warnings. The last value is also the
* minimum time at which we can possibly reboot after the value of the
* nextReboot field is changed, to prevent accidentally causing instant
* server reboots. */
public static final int[] WARNINGS = { 30, 20, 15, 10, 5, 2 };
}