// // $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2012 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: *