/*
* Copyright 1999-2008 University of Chicago
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.globus.workspace.service.impls;
import edu.emory.mathcs.backport.java.util.concurrent.Callable;
import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.FutureTask;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.globus.workspace.service.Sweepable;
import org.globus.workspace.service.WorkspaceHome;
import org.globus.workspace.Lager;
import java.util.Calendar;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Used to sweep for terminated instances.
*
* Functionality will improve and be pushed to scheduler module in the future
*/
public class ResourceSweeper implements Runnable {
// -------------------------------------------------------------------------
// STATIC VARIABLES
// -------------------------------------------------------------------------
private static final Log logger =
LogFactory.getLog(ResourceSweeper.class.getName());
// -------------------------------------------------------------------------
// INSTANCE VARIABLES
// -------------------------------------------------------------------------
protected final ExecutorService executor;
protected final WorkspaceHome home;
protected final Lager lager;
// instance ID : attempt count
protected final Map<Integer,Integer> zombieBackoffs = new Hashtable<Integer,Integer>();
// -------------------------------------------------------------------------
// CONSTRUCTORS
// -------------------------------------------------------------------------
public ResourceSweeper(ExecutorService executorService,
WorkspaceHome whome,
Lager lagerImpl) {
if (executorService == null) {
throw new IllegalArgumentException("executorService may not be null");
}
this.executor = executorService;
if (whome == null) {
throw new IllegalArgumentException("whome may not be null");
}
this.home = whome;
if (lagerImpl == null) {
throw new IllegalArgumentException("lagerImpl may not be null");
}
this.lager = lagerImpl;
}
// -------------------------------------------------------------------------
// implements Runnable
// -------------------------------------------------------------------------
public void run() {
try {
this.findAndDestroy();
} catch (Throwable t) {
logger.error("Problem sweeping resources: " + t.getMessage(), t);
}
}
// -------------------------------------------------------------------------
// IMPL
// -------------------------------------------------------------------------
protected void findAndDestroy() {
if (this.lager.pollLog) {
logger.trace("findAndDestroy - sweeping");
}
// this doesn't need to be synchronized anymore
final Sweepable[] toSweep = this.home.currentSweeps();
if (toSweep == null || toSweep.length == 0) {
return; // *** EARLY RETURN ***
}
// find things to destroy
final List killList = this.getDestroyTasks(toSweep);
if (killList.isEmpty()) {
return; // *** EARLY RETURN ***
}
// destroy things
final Iterator iter = killList.iterator();
while (iter.hasNext()) {
final Runnable task = (Runnable) iter.next();
this.executor.submit(task);
}
// Log any unexpected errors. Wait 30s (normal destroy time
// should be a matter of seconds even if there is high congestion).
// todo: make timeout configurable when this class comes via IoC
final Iterator iter2 = killList.iterator();
while (iter2.hasNext()) {
final FutureTask task = (FutureTask) iter2.next();
try {
final String msg = (String) task.get(30L, TimeUnit.SECONDS);
if (msg != null) {
logger.debug(msg);
}
} catch (Exception e) {
final String err = "Error while destroying sweeped " +
"instance: " + e.getMessage();
if (logger.isDebugEnabled()) {
logger.error(err, e);
} else {
logger.error(err);
}
}
}
}
protected synchronized List getDestroyTasks(Sweepable[] toSweep) {
final Calendar currentTime = Calendar.getInstance();
final LinkedList killList = new LinkedList();
for (int i = 0; i < toSweep.length; i++) {
final Sweepable sw = toSweep[i];
if (sw != null) {
final boolean expired = isExpired(sw.getTerminationTime(),
currentTime);
if (expired) {
logger.debug("Sweep found that " + Lager.id(sw.getID()) + " is expired.");
}
if (sw.isZombie()) {
// Only attempt on the following attempt # of sweeper runs:
// 1st, 2nd, 3rd, 6th, 10th, 15th, 25th, and then on modulo 20's
final Integer exists = this.zombieBackoffs.get(sw.getID());
final Integer attemptCount;
if (exists == null) {
attemptCount = 1;
} else {
attemptCount = exists + 1;
}
this.zombieBackoffs.put(sw.getID(), attemptCount);
int actualRetryNumber = attemptCount;
if (attemptCount < 40) {
switch (attemptCount) {
case 1:
break;
case 2:
break;
case 3:
break;
case 6:
actualRetryNumber = 4;
break;
case 10:
actualRetryNumber = 5;
break;
case 15:
actualRetryNumber = 6;
break;
case 25:
actualRetryNumber = 7;
break;
default:
continue;
}
} else {
if (attemptCount % 20 != 0) {
continue;
} else {
actualRetryNumber = 6 + attemptCount / 20;
}
}
logger.warn(Lager.ev(sw.getID()) + "Node that could not be destroyed " +
"previously, attempting again. Retry #" + actualRetryNumber);
}
if (expired || sw.isZombie()) {
final DestroyFutureTask task =
new DestroyFutureTask(sw.getID(), this.home);
killList.add(new FutureTask(task));
}
}
}
return killList;
}
public static boolean isExpired(Calendar terminationTime,
Calendar currentTime) {
return terminationTime != null && terminationTime.before(currentTime);
}
}