/*
* Copyright 2007-2010 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* --
*/
package com.sun.sgs.impl.kernel.schedule;
import com.sun.sgs.kernel.schedule.ScheduledTask;
import com.sun.sgs.kernel.schedule.SchedulerQueue;
import com.sun.sgs.app.TaskRejectedException;
import com.sun.sgs.auth.Identity;
import com.sun.sgs.impl.sharedutil.LoggerWrapper;
import com.sun.sgs.kernel.RecurringTaskHandle;
import com.sun.sgs.kernel.TaskReservation;
import java.util.Collection;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This implementation of <code>SchedulerQueue</code> tries to
* provide fairness between users without taking too long to service any
* given user's tasks. This is typified by the phrase "no one gets seconds
* until everyone who wants them has had firsts."
* <p>
* The implementation uses a priority queue, keyed off what is called a
* window value. Users submit tasks into increasing window values, and
* may never submit tasks into windows that have already passed.
*/
public class WindowSchedulerQueue implements SchedulerQueue, TimedTaskListener {
// logger for this class
private static final LoggerWrapper logger =
new LoggerWrapper(Logger.getLogger(WindowSchedulerQueue.
class.getName()));
// the priority queue
private PriorityBlockingQueue<QueueElement> queue;
// the map of users to their windows
private ConcurrentHashMap<Identity, QueueUser> userMap;
// the handler for all delayed tasks
private final TimedTaskHandler timedTaskHandler;
/**
* Creates an instance of <code>WindowSchedulerQueue</code>.
*
* @param properties the application <code>Properties</code>
*/
public WindowSchedulerQueue(Properties properties) {
logger.log(Level.CONFIG, "Creating a Window Scheduler Queue");
if (properties == null) {
throw new NullPointerException("Properties cannot be null");
}
queue = new PriorityBlockingQueue<QueueElement>();
userMap = new ConcurrentHashMap<Identity, QueueUser>();
timedTaskHandler = new TimedTaskHandler(this);
}
/**
* {@inheritDoc}
*/
public int getReadyCount() {
return queue.size();
}
/**
* {@inheritDoc}
*/
public ScheduledTask getNextTask(boolean wait)
throws InterruptedException
{
// try to get the next element, and return the result if we're
// not waiting, otherwise block
QueueElement element = queue.poll();
if (element != null) {
return element.getTask();
}
if (!wait) {
return null;
}
return queue.take().getTask();
}
/**
* {@inheritDoc}
*/
public int getNextTasks(Collection<? super ScheduledTask> tasks, int max) {
for (int i = 0; i < max; i++) {
QueueElement element = queue.poll();
if (element == null) {
return i;
}
tasks.add(element.getTask());
}
return max;
}
/**
* {@inheritDoc}
*/
public TaskReservation reserveTask(ScheduledTask task) {
if (task.isRecurring()) {
throw new TaskRejectedException("Recurring tasks cannot get " +
"reservations");
}
return new SimpleTaskReservation(this, task);
}
/**
* {@inheritDoc}
*/
public void addTask(ScheduledTask task) {
if (task == null) {
throw new NullPointerException("Task cannot be null");
}
if (!timedTaskHandler.runDelayed(task)) {
timedTaskReady(task);
}
}
/**
* {@inheritDoc}
*/
public RecurringTaskHandle createRecurringTaskHandle(ScheduledTask task) {
if (task == null) {
throw new NullPointerException("Task cannot be null");
}
if (!task.isRecurring()) {
throw new IllegalArgumentException("Not a recurring task");
}
return new RecurringTaskHandleImpl(this, task);
}
/**
* {@inheritDoc}
*/
public void notifyCancelled(ScheduledTask task) {
// FIXME: do we want to pull the task out of the queue?
}
/**
* {@inheritDoc}
*/
public void timedTaskReady(ScheduledTask task) {
// get the user details, creating an entry for the user if it's not
// there already
QueueUser user = userMap.get(task.getOwner());
if (user == null) {
userMap.putIfAbsent(task.getOwner(), new QueueUser());
user = userMap.get(task.getOwner());
}
QueueElement nextElement = null;
long scheduledWindow = 0L;
// make sure that we're only scheduling one task for a given user,
// so that we get a consistant view on the user's window counter
assert (user != null);
synchronized (user) {
// see what window we're currently on, which will be the user's
// next counter if there's nothing in the queue...this does
// break the intent of the counter always going up, but this
// is also a rare case in active schedulers, and active users
// should catch-up the window pretty quickly
long currentWindow = 0L;
nextElement = queue.peek();
if (nextElement != null) {
currentWindow = nextElement.getWindow();
}
scheduledWindow = (currentWindow > user.nextWindow) ?
currentWindow : user.nextWindow;
user.nextWindow = scheduledWindow + 1;
user.lastScheduled = task.getStartTime();
}
nextElement = new QueueElement(scheduledWindow, task);
boolean success;
do {
success = queue.offer(nextElement);
} while(!success);
}
/**
* {@inheritDoc}
*/
public void shutdown() {
timedTaskHandler.shutdown();
}
// Private class used to manage the priority queue
private static class QueueElement implements Comparable<QueueElement> {
private final long window;
private final ScheduledTask task;
private final long timestamp;
QueueElement(long window, ScheduledTask task) {
this.window = window;
this.task = task;
this.timestamp = task.getStartTime();
}
long getWindow() {
return window;
}
ScheduledTask getTask() {
return task;
}
/** {@inheritDoc} */
public int compareTo(QueueElement other) {
// if the other window is bigger, then their priority is lower
if (window < other.window) {
return -1;
}
// if the other window is smaller, then their priority is higher
if (window > other.window) {
return 1;
}
// the windows are the same, so check timestamps, with the
// same rules as above
if (timestamp < other.timestamp) {
return -1;
}
if (timestamp > other.timestamp) {
return 1;
}
// NOTE: if the windows and timestamps are the same, here is
// where we might fall-back on other values, but for now we'll
// just say they've got the same priority
return 0;
}
/** {@inheritDoc} */
public boolean equals(Object o) {
if ((o == null) || (!(o instanceof QueueElement))) {
return false;
}
QueueElement other = (QueueElement) o;
return ((window == other.window) &&
(timestamp == other.timestamp));
}
/** {@inheritDoc} */
public int hashCode() {
// Recipe from Effective Java
int result = 17;
result = 37 * result + (int) (window ^ (window >>> 32));
result = 37 * result + (int) (timestamp ^ (timestamp >>> 32));
return result;
}
}
// NOTE: for this first-pass implementation, we're not using any
// notion of priority, so there's just a single window counter
// NOTE: the lastScheduled field is there so that periodically we can
// kick off a thread to reap any users that haven't been scheduled
// within some delta, but for now there won't be enough users to worry
// about this case, so this feature isn't implemented
private static class QueueUser {
long nextWindow = 0L;
long lastScheduled;
}
}