
com.threerings.presents.server.RebootManager Maven / Gradle / Ivy
//
// $Id: RebootManager.java 6658 2011-06-14 20:06:38Z andrzej $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/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.Calendars;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Interval;
import com.samskivert.util.ObserverList;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Calendars.Builder;
import com.threerings.util.MessageBundle;
import com.threerings.presents.dobj.RootDObjectManager;
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.
*/
void shutdownPlanned (int warningsLeft, long msLeft);
}
/** The default warning times. */
public static final int[] DEFAULT_WARNINGS = { 30, 20, 15, 10, 5, 2 };
/**
* Finishes initialization of the manager.
*/
public void init ()
{
scheduleRegularReboot();
}
/**
* Schedules our next regularly scheduled reboot.
*
* @return true if a reboot was scheduled, false if regularly scheduled reboots are disabled.
*/
public boolean scheduleRegularReboot ()
{
// maybe schedule an automatic reboot based on our configuration
int freq = getDayFrequency();
if (freq == -1) {
return false;
}
Builder cal = Calendars.now().zeroTime().addHours(getRebootHour()).addDays(freq);
// maybe avoid weekends
if (getSkipWeekends()) {
int dow = cal.get(Calendar.DAY_OF_WEEK);
switch (dow) {
case Calendar.SATURDAY:
if (freq > 1) {
cal.addDays(-1);
}
break;
case Calendar.SUNDAY:
if (freq > 2) {
cal.addDays(-2);
}
break;
}
}
scheduleReboot(cal.toTime(), AUTOMATIC_INITIATOR);
return true;
}
/**
* 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();
int[] warnings = getWarnings();
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 = _omgr.newInterval(new Runnable() {
public void run () {
doWarning(0);
}
})).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 + ")");
}
}
/**
* Returns 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 int[] getWarnings ()
{
return DEFAULT_WARNINGS;
}
/**
* Provides us with our dependencies.
*/
protected RebootManager (PresentsServer server, RootDObjectManager omgr)
{
_server = server;
_omgr = omgr;
}
/**
* Broadcasts a message to everyone on the server. The following messages will be broadcast:
* - m.rebooting_now
*
- m.reboot_warning (minutes) (message or m.reboot_msg_standard)
*
- m.reboot_delayed
*
*/
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 ();
/**
* Composes the given reboot message with the minutes and either the custom or standard details
* about the pending reboot.
*/
protected String getRebootMessage (String key, int minutes)
{
String msg = getCustomRebootMessage();
if (StringUtil.isBlank(msg)) {
msg = "m.reboot_msg_standard";
}
return MessageBundle.compose(key, MessageBundle.taint("" + minutes), msg);
}
/**
* Do a warning, schedule the next.
*/
protected void doWarning (final int level)
{
_rebootSoon = true;
int[] warnings = getWarnings();
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() { // Note: This interval does not run on the dobj thread
@Override public void expired () {
_server.queueShutdown(); // this posts a LongRunnable
}
}.schedule(1000);
return;
}
// issue the warning
int minutes = warnings[level];
broadcast(getRebootMessage("m.reboot_warning", minutes));
if (level < warnings.length - 1) {
minutes -= warnings[level + 1];
}
// schedule the next warning
(_interval = _omgr.newInterval(new Runnable() {
public void run () {
doWarning(level + 1);
}
})).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", "locks", _rebootLocks.elements());
broadcast("m.reboot_delayed");
(_interval = _omgr.newInterval(new Runnable() {
public void run () {
doWarning(getWarnings().length);
}
})).schedule(60 * 1000);
return true;
}
/**
* Notify all PendingShutdownObservers of the pending shutdown!
*/
protected void notifyObservers (int level)
{
int[] warnings = getWarnings();
final int warningsLeft = warnings.length - level - 1;
final long msLeft = 1000L * 60L * warnings[level];
_observers.apply(new ObserverList.ObserverOp() {
public boolean apply (PendingShutdownObserver observer) {
observer.shutdownPlanned(warningsLeft, msLeft);
return true;
}
});
}
/** The server that we're going to reboot. */
protected PresentsServer _server;
/** Our distributed object manager. */
protected RootDObjectManager _omgr;
/** 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 _observers = ObserverList.newFastUnsafe();
/** The next reboot lock id. */
protected int _nextRebootLockId = 0;
/** Things that can delay the reboot. */
protected HashIntMap _rebootLocks = new HashIntMap();
protected static final String AUTOMATIC_INITIATOR = "automatic";
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy