All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.restcomm.timers.FaultTolerantScheduler Maven / Gradle / Ivy

There is a newer version: 7.0.0.16
Show newest version
/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2016, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see 
 */

package org.restcomm.timers;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.log4j.Logger;
import org.infinispan.remoting.transport.Address;
import org.infinispan.tree.Fqn;
import org.restcomm.cache.FqnWrapper;
import org.restcomm.cluster.DataRemovalListener;
import org.restcomm.cluster.FailOverListener;
import org.restcomm.cluster.MobicentsCluster;
import org.restcomm.cluster.cache.ClusteredCacheData;
import org.restcomm.cluster.election.ClientLocalListenerElector;
import org.restcomm.timers.cache.FaultTolerantSchedulerCacheData;
import org.restcomm.timers.cache.TimerTaskCacheData;

/**
 * 
 * @author martins
 * @author András Kőkuti
 *
 */
public class FaultTolerantScheduler {

	private static final Logger logger = Logger.getLogger(FaultTolerantScheduler.class);
	
	/**
	 * the executor of timer tasks
	 */
	private final ScheduledThreadPoolExecutor executor;
	
	/**
	 * 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 localRunningTasks = new ConcurrentHashMap();

	
	
	/**
	 * the timer task factory associated with this scheduler
	 */
	private TimerTaskFactory timerTaskFactory;
	
	/**
	 * the base fqn used to store tasks data in restcomm cluster's cache
	 */
	@SuppressWarnings("unchecked")
	private final Fqn baseFqn;
	
	private FaultTolerantSchedulerCacheData cacheData;
	
	/**
	 * the scheduler name
	 */
	private final String name;
		
	/**
	 * the restcomm cluster 
	 */
	private final MobicentsCluster cluster;
	
	/**
	 * listener for fail over events in restcomm 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, corePoolSize, cluster, priority, txManager, timerTaskFactory, 0, Executors.defaultThreadFactory());
	}

    /**
     *
     * @param name
     * @param corePoolSize
     * @param cluster
     * @param priority
     * @param txManager
     * @param timerTaskFactory
     * @param threadFactory
     */
    public FaultTolerantScheduler(String name, int corePoolSize, MobicentsCluster cluster, byte priority, TransactionManager txManager, TimerTaskFactory timerTaskFactory, ThreadFactory threadFactory) {
        this(name, corePoolSize, cluster, priority, txManager, timerTaskFactory, 0, threadFactory);
    }

    /**
     *
     * @param name
     * @param corePoolSize
     * @param cluster
     * @param priority
     * @param txManager
     * @param timerTaskFactory
     * @param purgePeriod
     */
	public FaultTolerantScheduler(String name, int corePoolSize, MobicentsCluster cluster, byte priority, TransactionManager txManager,TimerTaskFactory timerTaskFactory, int purgePeriod) {
        this(name, corePoolSize, cluster, priority, txManager, timerTaskFactory, purgePeriod, Executors.defaultThreadFactory());
	}

    /**
     *
     * @param name
     * @param corePoolSize
     * @param cluster
     * @param priority
     * @param txManager
     * @param timerTaskFactory
     * @param purgePeriod
     * @param threadFactory
     */
    public FaultTolerantScheduler(String name, int corePoolSize, MobicentsCluster cluster, byte priority, TransactionManager txManager,TimerTaskFactory timerTaskFactory, int purgePeriod, ThreadFactory threadFactory) {
        this.name = name;
        this.executor = new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
        if(purgePeriod > 0) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    try {
                        executor.purge();
                    }
                    catch (Exception e) {
                        logger.error("failed to execute purge",e);
                    }
                }
            };
            this.executor.scheduleWithFixedDelay(r, purgePeriod, purgePeriod, TimeUnit.MINUTES);
        }
        this.baseFqn = Fqn.fromElements(name);
        this.cluster = cluster;
        this.timerTaskFactory = timerTaskFactory;
        this.txManager = txManager;
        cacheData = new FaultTolerantSchedulerCacheData(new FqnWrapper(baseFqn),cluster);
        if (cluster.isStarted()) {
            cacheData.create();
        }
        clusterClientLocalListener = new ClientLocalListener(priority);
        cluster.addFailOverListener(clusterClientLocalListener);
        cluster.addDataRemovalListener(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 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 getLocalRunningTasks() {
		return new HashSet(localRunningTasks.values());
	}
	
	/**
	 * Retrieves a local running task by its id
	 * 
	 * @return the local task if found, null otherwise
	 */
	public TimerTask getLocalRunningTask(Serializable taskId) {
		return localRunningTasks.get(taskId);
	}
	
	/**
	 *  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 restcomm 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 
	
	public void schedule(TimerTask task) {
		schedule(task, true);
	}
	/**
	 * Schedules the specified task.
	 * 
	 * @param task
	 */
	public void schedule(TimerTask task, boolean checkIfAlreadyPresent) {
		
		final TimerTaskData taskData = task.getData(); 
		final Serializable taskID = taskData.getTaskID();
		task.setScheduler(this);
		
		if (logger.isDebugEnabled()) {
			logger.debug("Scheduling task with id " + taskID);
		}
		
		// store the task and data
		final TimerTaskCacheData timerTaskCacheData = new TimerTaskCacheData(taskID, baseFqn, cluster);
		if (timerTaskCacheData.create()) {
			if (logger.isInfoEnabled()) {
				logger.info("Storing task data " + taskID);
			}
			timerTaskCacheData.setTaskData(taskData);
		} else if(checkIfAlreadyPresent) {
            throw new IllegalStateException("timer task " + taskID + " already scheduled");
		}
				
		// schedule task
		final SetTimerAfterTxCommitRunnable setTimerAction = new SetTimerAfterTxCommitRunnable(task, this);
		if (txManager != null) {
			try {
				Transaction tx = txManager.getTransaction();
				if (tx != null) {
					TransactionContext txContext = TransactionContextThreadLocal.getTransactionContext();
					if (txContext == null) {
						txContext = new TransactionContext();
						tx.registerSynchronization(new TransactionSynchronization(txContext));
					}
					txContext.put(taskID, setTimerAction);					
					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
				AfterTxCommitRunnable runnable = new CancelTimerAfterTxCommitRunnable(task,this);
				if (txManager != null) {
					try {
						Transaction tx = txManager.getTransaction();
						if (tx != null) {
							TransactionContext txContext = TransactionContextThreadLocal.getTransactionContext();
							if (txContext == null) {
								txContext = new TransactionContext();
								tx.registerSynchronization(new TransactionSynchronization(txContext));
							}
							txContext.put(taskID, runnable);					
						}
						else {
							runnable.run();
						}
					}
					catch (Throwable e) {
						throw new RuntimeException("Unable to register tx synchronization object",e);
					}
				}
				else {
					runnable.run();
				}			
			}		
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Not a local task");
			}
			// not found locally
			// if there is a tx context there may be a set timer action there
			if (txManager != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("Txmanager not null");
				}
				try {
					Transaction tx = txManager.getTransaction();
					if (tx != null) {
						if (logger.isDebugEnabled()) {
							logger.debug("Tx not null");
						}
						TransactionContext txContext = TransactionContextThreadLocal.getTransactionContext();
						if (txContext != null) {
							if (logger.isDebugEnabled()) {
								logger.debug("Tx context not null");
							}
							final AfterTxCommitRunnable r = txContext.remove(taskID);
							if (r != null) {
								logger.debug("removing");
								task = r.task;
								// remove from cluster
								new TimerTaskCacheData(taskID, baseFqn, cluster).remove();
							}							
						}											
					}
				}
				catch (Throwable e) {
					throw new RuntimeException("Failed to check tx context.",e);
				}
			}			
		}
		
		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(task != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Recovering task with id "+taskData.getTaskID());
			}
			task.beforeRecover();
			// on recovery the task will already be in the cache so we don't check for it
			// or an IllegalStateException will be thrown
			schedule(task, false);
		}
	}

	public void shutdownNow() {
		if (logger.isDebugEnabled()) {
			logger.debug("Shutdown now.");
		}
		cluster.removeFailOverListener(clusterClientLocalListener);
		cluster.removeDataRemovalListener(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 FailOverListener, DataRemovalListener {

		/**
		 * the priority of the scheduler as a client local listener of the restcomm cluster
		 */
		private final byte priority;
				
		/**
		 * @param priority
		 */
		public ClientLocalListener(byte priority) {
			this.priority = priority;
		}

		/*
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#getBaseFqn()
		 */
		@SuppressWarnings("unchecked")
		public FqnWrapper getBaseFqn() {
			return new FqnWrapper(baseFqn);
		}

		/*
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#getElector()
		 */
		public ClientLocalListenerElector getElector() {
			return null;
		}
		
		/* 
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#getPriority()
		 */
		public byte getPriority() {
			return priority;
		}

		/*
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#failOverClusterMember(org.jgroups.Address)
		 */
		public void failOverClusterMember(Address address) {
			
		}
		
		/* 
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#lostOwnership(org.mobicents.cluster.cache.ClusteredCacheData)
		 */
		public void lostOwnership(ClusteredCacheData clusteredCacheData) {
			
		}

		/* 
		 * (non-Javadoc)
		 * @see org.mobicents.cluster.FailOverListener#wonOwnership(org.mobicents.cluster.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 org.mobicents.cluster.DataRemovalListener#dataRemoved(org.jboss.cache.Fqn)
		 */
		@SuppressWarnings("unchecked")
		public void dataRemoved(FqnWrapper clusteredCacheDataFqnWrapper) {
			Fqn clusteredCacheDataFqn = clusteredCacheDataFqnWrapper.getFqn();
			Object lastElement = clusteredCacheDataFqn.getLastElement();
			if (logger.isDebugEnabled()) {
				logger.debug("remote notification dataRemoved( clusterCacheDataFqn = "+clusteredCacheDataFqn+"), lastElement " + lastElement);
			}
			final TimerTask task = localRunningTasks.remove(lastElement);
			if (task != null) {
				if (logger.isDebugEnabled()) {
					logger.debug("remote notification dataRemoved( task = "+task.getData().getTaskID()+" removed locally cancelling it");
				}
				task.cancel();
			}			
		}
		
		/* (non-Javadoc)
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			return FaultTolerantScheduler.this.toString();
		}
		
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy