package org.mobicents.timers; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.apache.log4j.Logger; import org.jboss.cache.Fqn; import org.jgroups.Address; import org.mobicents.cluster.MobicentsCluster; import org.mobicents.cluster.cache.ClusteredCacheData; import org.mobicents.timers.cache.FaultTolerantSchedulerCacheData; import org.mobicents.timers.cache.TimerTaskCacheData; /** * * @author martins * */ public class FaultTolerantScheduler { private static final Logger logger = Logger.getLogger(FaultTolerantScheduler.class); /** * the executor of timer tasks */ private final ScheduledThreadPoolExecutor executor; /** * the scheduler cache data */ private final FaultTolerantSchedulerCacheData cacheData; /** * the jta tx manager */ private final TransactionManager txManager; /** * the local running tasks. NOTE: never ever check for values, class instances may differ due cache replication, ALWAYS use keys. */ private final ConcurrentHashMap<Serializable, TimerTask> localRunningTasks = new ConcurrentHashMap<Serializable, TimerTask>(); /** * the timer task factory associated with this scheduler */ private TimerTaskFactory timerTaskFactory; /** * the base fqn used to store tasks data in mobicents cluster's cache */ private final Fqn baseFqn; /** * the scheduler name */ private final String name; /** * the mobicents cluster */ private final MobicentsCluster cluster; /** * listener for fail over events in mobicents cluster */ private final ClientLocalListener clusterClientLocalListener; /** * * @param name * @param corePoolSize * @param cluster * @param priority * @param txManager * @param timerTaskFactory */ public FaultTolerantScheduler(String name, int corePoolSize, MobicentsCluster cluster, byte priority, TransactionManager txManager,TimerTaskFactory timerTaskFactory) { this.name = name; this.executor = new ScheduledThreadPoolExecutor(corePoolSize); this.baseFqn = Fqn.fromElements(name); this.cluster = cluster; this.cacheData = new FaultTolerantSchedulerCacheData(baseFqn,cluster); if (!cacheData.exists()) { cacheData.create(); } this.timerTaskFactory = timerTaskFactory; this.txManager = txManager; clusterClientLocalListener = new ClientLocalListener(priority); cluster.addLocalListener(clusterClientLocalListener); } /** * Retrieves the {@link TimerTaskData} associated with the specified taskID. * @param taskID * @return null if there is no such timer task data */ public TimerTaskData getTimerTaskData(Serializable taskID) { TimerTaskCacheData timerTaskCacheData = new TimerTaskCacheData(taskID, baseFqn, cluster); if (timerTaskCacheData.exists()) { return timerTaskCacheData.getTaskData(); } else { return null; } } /** * Retrieves the executor of timer tasks. * @return */ ScheduledThreadPoolExecutor getExecutor() { return executor; } /** * Retrieves local running tasks map. * @return */ ConcurrentHashMap<Serializable, TimerTask> getLocalRunningTasksMap() { return localRunningTasks; } /** * Retrieves a set containing all local running tasks. Removals on the set * will not be propagated to the internal state of the scheduler. * * @return */ public Set<TimerTask> getLocalRunningTasks() { return new HashSet<TimerTask>(localRunningTasks.values()); } /** * Retrieves the scheduler name. * @return the name */ public String getName() { return name; } /** * Retrieves the priority of the scheduler as a client local listener of the mobicents cluster. * @return the priority */ public byte getPriority() { return clusterClientLocalListener.getPriority(); } /** * Retrieves the jta tx manager. * @return */ public TransactionManager getTransactionManager() { return txManager; } /** * Retrieves the timer task factory associated with this scheduler. * @return */ public TimerTaskFactory getTimerTaskFactory() { return timerTaskFactory; } // logic /** * Schedules the specified task. * * @param task */ public void schedule(TimerTask task) { final TimerTaskData taskData = task.getData(); final Serializable taskID = taskData.getTaskID(); if (logger.isDebugEnabled()) { logger.debug("Scheduling task with id "+taskID); } localRunningTasks.put(taskID, task); // store the task and data TimerTaskCacheData timerTaskCacheData = new TimerTaskCacheData(taskID, baseFqn, cluster); if (timerTaskCacheData.create()) { timerTaskCacheData.setTaskData(taskData); } // schedule task SetTimerAfterTxCommitRunnable setTimerAction = new SetTimerAfterTxCommitRunnable(task, this); if (txManager != null) { try { Transaction tx = txManager.getTransaction(); if (tx != null) { Runnable rollbackAction = new Runnable() { public void run() { localRunningTasks.remove(taskID); } }; tx.registerSynchronization(new TransactionSynchronization(setTimerAction,rollbackAction)); task.setSetTimerTransactionalAction(setTimerAction); } else { setTimerAction.run(); } } catch (Throwable e) { remove(taskID,true); throw new RuntimeException("Unable to register tx synchronization object",e); } } else { setTimerAction.run(); } } /** * Cancels a local running task with the specified ID. * * @param taskID * @return the task canceled */ public TimerTask cancel(Serializable taskID) { if (logger.isDebugEnabled()) { logger.debug("Canceling task with timer id "+taskID); } TimerTask task = localRunningTasks.get(taskID); if (task != null) { // remove task data new TimerTaskCacheData(taskID, baseFqn, cluster).remove(); final SetTimerAfterTxCommitRunnable setAction = task.getSetTimerTransactionalAction(); if (setAction != null) { // we have a tx action scheduled to run when tx commits, to set the timer, lets simply cancel it setAction.cancel(); } else { // do cancellation Runnable cancelAction = new CancelTimerAfterTxCommitRunnable(task,this); if (txManager != null) { try { Transaction tx = txManager.getTransaction(); if (tx != null) { tx.registerSynchronization(new TransactionSynchronization(cancelAction,null)); } else { cancelAction.run(); } } catch (Throwable e) { throw new RuntimeException("unable to register tx synchronization object",e); } } else { cancelAction.run(); } } } return task; } void remove(Serializable taskID,boolean removeFromCache) { if(logger.isDebugEnabled()) { logger.debug("remove() : "+taskID+" - "+removeFromCache); } localRunningTasks.remove(taskID); if(removeFromCache) new TimerTaskCacheData(taskID, baseFqn, cluster).remove(); } /** * Recovers a timer task that was running in another node. * * @param taskData */ private void recover(TimerTaskData taskData) { TimerTask task = timerTaskFactory.newTimerTask(taskData); if (logger.isDebugEnabled()) { logger.debug("Recovering task with id "+taskData.getTaskID()); } task.beforeRecover(); schedule(task); } public void shutdownNow() { if (logger.isDebugEnabled()) { logger.debug("Shutdown now."); } cluster.removeLocalListener(clusterClientLocalListener); executor.shutdownNow(); localRunningTasks.clear(); } @Override public String toString() { return "FaultTolerantScheduler [ name = "+name+" ]"; } public String toDetailedString() { return "FaultTolerantScheduler [ name = "+name+" , local tasks = "+localRunningTasks.size()+" , all tasks "+cacheData.getTaskIDs().size()+" ]"; } public void stop() { this.shutdownNow(); } private class ClientLocalListener implements org.mobicents.cluster.ClientLocalListener { /** * the priority of the scheduler as a client local listener of the mobicents cluster */ private final byte priority; /** * @param priority */ public ClientLocalListener(byte priority) { this.priority = priority; } /* (non-Javadoc) * @see org.mobicents.cluster.client.LocalListener#getBaseFqn() */ public Fqn getBaseFqn() { return baseFqn; } /* (non-Javadoc) * @see org.mobicents.cluster.ClientLocalListener#getPriority() */ public byte getPriority() { return priority; } /* (non-Javadoc) * @see org.mobicents.cluster.ClientLocalListener#failOverClusterMember(org.jgroups.Address) */ public void failOverClusterMember(Address address) { } /* (non-Javadoc) * @see org.mobicents.ftf.FTFListener#lostOwnership(org.mobicents.slee.runtime.cache.ClusteredCacheData) */ public void lostOwnership(ClusteredCacheData clusteredCacheData) { } /* (non-Javadoc) * @see org.mobicents.ftf.FTFListener#wonOwnership(org.mobicents.slee.runtime.cache.ClusteredCacheData) */ public void wonOwnership(ClusteredCacheData clusteredCacheData) { if (logger.isDebugEnabled()) { logger.debug("wonOwnership( clusterCacheData = "+clusteredCacheData+")"); } try { Serializable taskID = TimerTaskCacheData.getTaskID(clusteredCacheData); TimerTaskCacheData timerTaskCacheData = new TimerTaskCacheData(taskID, baseFqn, cluster); recover(timerTaskCacheData.getTaskData()); } catch (Throwable e) { logger.error(e.getMessage(),e); } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return FaultTolerantScheduler.this.toString(); } } }