/*
* Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.task;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.PFComponent;
/**
* Loads, stores and initializes persistent Tasks. While RuntimeExceptions on
* de-/initialization are caught and not propagated further, tasks which block
* in those cases are not killed and can therefore prevent this manager from
* working properly. (In an older revision they actually are killed after a
* certain amount of time. I removed it because those tasks represent real
* "faulty" implementations which need to be fixed.)
*
* @author Dennis "Bytekeeper" Waldherr </a>
* @version $Revision$
*/
public class PersistentTaskManager extends PFComponent {
private static final Logger log = Logger
.getLogger(PersistentTaskManager.class.getName());
private List<PersistentTask> tasks;
/**
* Pending tasks that await initialization.
*/
private List<PersistentTask> pendingTasks;
private volatile boolean shuttingDown = false;
public PersistentTaskManager(Controller controller) {
super(controller);
}
/**
* Returns the file which represents the persistent store of tasks.
*
* @return the tasklist-file
*/
private File getTaskFile() {
String filename = getController().getConfigName() + ".tasks";
File taskFile = new File(Controller.getMiscFilesLocation(), filename);
new File(taskFile.getParent()).mkdirs();
return taskFile;
}
/**
* Starts this manager.
*/
public synchronized void start() {
shuttingDown = false;
pendingTasks = new Vector<PersistentTask>();
File taskfile = getTaskFile();
if (taskfile.exists()) {
logFine("Loading taskfile: " + taskfile);
ObjectInputStream oin = null;
try {
oin = new ObjectInputStream(new FileInputStream(taskfile));
tasks = new LinkedList<PersistentTask>();
PersistentTask task = null;
while (true) {
try {
task = (PersistentTask) oin.readObject();
} catch (ClassNotFoundException e) {
logSevere("ClassNotFoundException", e);
continue;
} catch (ClassCastException e) {
logSevere("ClassCastException", e);
continue;
}
if (task == null) {
break;
}
tasks.add(task);
}
oin.close();
logInfo("Loaded " + tasks.size() + " tasks.");
} catch (FileNotFoundException e) {
logSevere("FileNotFoundException", e);
} catch (EOFException e) {
// End of File. OK!
} catch (IOException e) {
logSevere("IOException", e);
} catch (ClassCastException e) {
logSevere("ClassCastException", e);
} finally {
if (oin != null) {
try {
oin.close();
} catch (IOException e) {
logSevere("IOException", e);
}
}
}
} else {
logInfo("No taskfile found - probably first start of PF.");
}
// If no taskfile was found or errors occurred while loading it
if (tasks == null) {
tasks = new LinkedList<PersistentTask>();
}
for (PersistentTask t : tasks.toArray(new PersistentTask[tasks.size()]))
{
try {
t.init(this);
} catch (RuntimeException e) {
logSevere("RuntimeException", e);
tasks.remove(t);
}
}
}
/**
* Shuts down the manager. Saves all remaining tasks - they'll continue
* execution once the manager has been restarted (Not necessarily in this
* session of PowerFolder)
*/
public synchronized void shutdown() {
shuttingDown = true;
if (tasks == null || pendingTasks == null) {
logFine("Shutdown before initialization!");
return;
}
waitForPendingTasks();
for (PersistentTask t : tasks) {
try {
t.shutdown();
} catch (RuntimeException e) {
logSevere("RuntimeException", e);
}
}
File taskFile = getTaskFile();
ObjectOutputStream oout = null;
try {
logInfo("There are " + tasks.size() + " tasks not completed yet.");
oout = new ObjectOutputStream(new FileOutputStream(taskFile));
for (PersistentTask task : tasks) {
oout.writeUnshared(task);
}
} catch (FileNotFoundException e) {
logSevere("FileNotFoundException", e);
} catch (IOException e) {
logSevere("IOException", e);
} finally {
if (oout != null) {
try {
oout.close();
} catch (IOException e) {
logSevere("IOException", e);
}
}
tasks = null;
}
}
public boolean isStarted() {
return tasks != null;
}
/**
* Schedules a new task. The given task will be started as soon as possible
* by the shared ThreadPool of the Controller class.
*
* @param task
* the task to start
*/
public synchronized void scheduleTask(final PersistentTask task) {
if (tasks == null) {
log.log(Level.SEVERE,
"Unable to schedule task, taskmanager not initialized! Task: "
+ task, new RuntimeException("here"));
return;
}
if (!tasks.contains(task) && !shuttingDown) {
if (isFine()) {
logFine("Adding " + task);
}
tasks.add(task);
Runnable adder = new Runnable() {
public void run() {
task.init(PersistentTaskManager.this);
pendingTasks.remove(task);
synchronized (PersistentTaskManager.this) {
PersistentTaskManager.this.notify();
}
}
};
pendingTasks.add(task);
getController().getThreadPool().execute(adder);
}
}
/**
* Shuts down and removes a given task. This method will block until all
* tasks are properly initialized before removing the given task.
*
* @param task
* the task to remove
*/
public synchronized void removeTask(PersistentTask task) {
boolean oldSD = shuttingDown;
shuttingDown = true;
if (pendingTasks.contains(task)) {
pendingTasks.remove(task);
} else {
waitForPendingTasks();
}
if (oldSD == false) { // Prevent cyclic calls from task.shutdown() ->
// task.remove() on faulty tasks.
task.shutdown();
tasks.remove(task);
} else {
logInfo(task
+ " shouldn't call remove() in shutdown(), it will automatically be removed!");
}
shuttingDown = oldSD;
}
/**
* Removes all pending tasks. This is useful for tests or to clear all tasks
* in case some are erroneous. This method will block until all tasks are
* properly initialized.
*/
public synchronized void purgeAllTasks() {
boolean oldSD = shuttingDown;
shuttingDown = true;
waitForPendingTasks();
while (!tasks.isEmpty()) {
tasks.remove(0).shutdown();
}
shuttingDown = oldSD;
}
/**
* Returns if there are any pending tasks.
*
* @return true if there are 1 or more active tasks
*/
public synchronized boolean hasTasks() {
return !tasks.isEmpty();
}
/**
* Returns the number of active tasks
*
* @return the active task count
*/
public synchronized int activeTaskCount() {
return tasks.size();
}
/**
* Required to solve TRAC #1124. Nicer API method would be:
* hasPendingMessagesTo(MemberInfo);
*
* @return if there are pending messages to be sent.
*/
public synchronized boolean hasSendMessageTask() {
if (tasks == null) {
return false;
}
for (PersistentTask task : tasks) {
if (task instanceof SendMessageTask) {
if (isFiner()) {
logFiner("Found pending message(s). total active tasks: "
+ tasks.size());
}
return true;
}
// SendMessageTask sendTask = (SendMessageTask) task;
// if (!sendTask.getTargetID().equals(node.id)) {
// logWarning("Found pending message(s) to " + node);
// return true;
// }
}
// No pending found
return false;
}
/** Assumes the caller to have locked the manager. */
private void waitForPendingTasks() {
while (!pendingTasks.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
logSevere("InterruptedException", e);
}
}
if (!pendingTasks.isEmpty()) {
StringBuilder b = new StringBuilder();
b.append("The following tasks are blocking:");
for (PersistentTask t : pendingTasks)
b.append(' ').append(t);
b.append(" and will be removed!");
logSevere(b.toString());
// Note: This will also remove tasks which "might" still finish
// initialization
tasks.removeAll(pendingTasks);
pendingTasks.clear();
}
}
}