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

decodes.routing.ScheduleEntryExecutive Maven / Gradle / Ivy

Go to download

A collection of software for aggregatting and processing environmental data such as from NOAA GOES satellites.

The newest version!
package decodes.routing;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import lrgs.common.DcpMsg;
import ilex.util.IDateFormat;
import ilex.util.Logger;
import opendcs.dai.ScheduleEntryDAI;
import decodes.db.Constants;
import decodes.db.Database;
import decodes.db.DatabaseException;
import decodes.db.Platform;
import decodes.db.RoutingSpec;
import decodes.db.ScheduleEntry;
import decodes.db.ScheduleEntryStatus;
import decodes.dcpmon.DcpMonitor;
import decodes.sql.DbKey;
import decodes.sql.SqlDatabaseIO;
import decodes.tsdb.DbIoException;
import decodes.tsdb.IntervalIncrement;

/**
 * This class supervises the execution of a routing spec according to a 
 * ScheduleEntry stored in the database. It handles the scheduling, constructs
 * the ScheduleeEntryThread at the appropriate time(s), and provides a link
 * to the daemon's resources.
 * @author mmaloney Mike Maloney, Cove Software, LLC
 */
public class ScheduleEntryExecutive
{
	/** The schedule entry this executive is responsible for */
	private ScheduleEntry scheduleEntry = null;
	
	/** The current run state of this executive */
	private RunState runState = RunState.initializing;
	
	/** The RS Thread or null when no RoutingSpec is running */
	private RoutingSpecThread seThread = null;

	/** Status of latest, or currently running routing spec */
	private ScheduleEntryStatus seStatus = null;
	TimeZone jtz = null;
	
	private RoutingScheduler parent = null;
	
	private long shutdownStarted = 0L;
	private long maxShutdownTime = 20000L; // allow 20 sec to gracefully shut down.
	private long shutdownComplete = 0L;
	
	protected static boolean rereadRsBeforeExec = true;
	private DcpMsg lastDcpMsg = null;
	private DacqEventLogger dacqEventLogger = null;
	private int schedEntryMinLogPriority = Logger.E_INFORMATION;
	private long lastEventsPurge = 0L;
	private long lastSchedDebug = 0L;
	private int appMinLogPriority = Logger.E_INFORMATION;

	
	/** Constructor called from RoutingScheduler */
	public ScheduleEntryExecutive(ScheduleEntry scheduleEntry, RoutingScheduler parent)
	{
		this.scheduleEntry = scheduleEntry;
		this.parent = parent;
		schedEntryMinLogPriority = 
			appMinLogPriority = parent != null ? parent.getAppDebugMinPriority() 
				: Logger.instance().getMinLogPriority();
		
		if (parent == null) // This means that this is the 'rs' command.
		{
			dacqEventLogger = new DacqEventLogger(Logger.instance());

			if (Database.getDb().getDbIo() instanceof SqlDatabaseIO)
			{
				// This means that this is a stand-alone rs from command line, the DCPmon
				// daemon, or some other single-threaded app.

				// If rs thread has an app ID, set it in the logger.
				if (seThread != null && seThread.rsProcRecord != null 
					&& !DbKey.isNull(seThread.rsProcRecord.getAppId()))
				{
					dacqEventLogger.setAppId(seThread.rsProcRecord.getAppId());
				}
			}
			// Else this is an XML database, no actual DACQ Event Logging
			
			// For RS, set default logger to the event logger.
			Logger.setLogger(dacqEventLogger);
		}
		else if (parent != null && (parent instanceof DcpMonitor))
		{
			// I don't want any DACQ Events logged by Dcp Monitor Daemon
			// Since no DAO, it will just act as a pass-through.
			dacqEventLogger = new DacqEventLogger(Logger.instance());
		}
		else // This is a child of RoutingScheduler
		{
			dacqEventLogger = new DacqEventLogger(parent.origLogger);
		}
	}
	
private long lastDebug = 0L;
	/**
	 * Called periodically from RoutingScheduler, this method checks the status
	 * and schedule of the entry it is managing and takes appropriate action,
	 * such as starting/stopping the thread, etc.
	 */
	public void check()
	{
		if (System.currentTimeMillis() - lastDebug > 60000L)
		{
			dacqEventLogger.debug1("Checking schedule entry '" + scheduleEntry.getName() 
				+ "' enabled=" + scheduleEntry.isEnabled() + ", state=" + runState);
			lastDebug = System.currentTimeMillis();
		}
		
		if (!scheduleEntry.isEnabled())
			return;
		
		

		if (runState == RunState.initializing)
		{
			try
			{
				initialize();
				runState = RunState.waiting;
			}
			catch (DbIoException ex)
			{
				dacqEventLogger.warning(getName() + " Cannot initialize: " + ex);
				// Stay in initialize state and retry later.
			}
			return;
		}
		else if (runState == RunState.waiting)
		{
			if (checkSchedule())
				startThread();
		}
		else if (runState == RunState.running)
		{
			if (!this.seThread.isAlive() || seThread.currentStatus.startsWith("ERR"))
			{
				Logger.instance().failure("Thread " + getName() + " has failed.");
				seThread.shutdown();
				seThread.quit(); // TODO: shutdown AND quit? why?
				//seThread = null; // can't set seThread null here as it is within rsFinished
				runState = RunState.shutdown;
				seStatus.setRunStatus("ERROR-system");
			}
		}
		else if (runState == RunState.shutdown)
		{
			if (System.currentTimeMillis() - shutdownStarted > maxShutdownTime
			 && seThread != null)
			{
				dacqEventLogger.warning(getName() + " taking too long to shutdown, "
					+ "will attempt thread interrupt.");
				seThread.interrupt();
			}
			
			// A continuous schedule entry might exit for various transient errors.
			// Example, a timeout on an LRGS data source.
			// Attempt to restart after one minute if it does.
			long now = System.currentTimeMillis();
			if (scheduleEntry.getStartTime() == null                    // means continuous
			 && shutdownComplete != 0L                                  // Completely shutdown
			 && now - shutdownComplete > 60000L  // over a minute ago
			 && seStatus.getRunStatus().contains("ERR")                 // Exited due to error
			 && !dataSourceFinite())                                    // data source not a file
			{
				runState = RunState.initializing;
				RoutingSpec rs = Database.getDb().routingSpecList.find(
					scheduleEntry.getRoutingSpecName());
				if (rs != null && lastDcpMsg != null)
					rs.sinceTime = IDateFormat.toString(lastDcpMsg.getXmitTime(), false);
			}
			else
			{
				dacqEventLogger.debug1("Sched Entry '" + scheduleEntry.getName()
				+ "' startTime=" + scheduleEntry.getStartTime()
				+ ", shutdownComplete=" + shutdownComplete
				+ ", now=" + now
				+ ", runStatus=" + seStatus.getRunStatus()
				+ ", dataSourceFinite=" + dataSourceFinite());
			}
		}
		else // runState == complete.
		{
		}
	}
	
	/**
	 * A 'finite' data source is like a file. Once it's processed it's done.
	 * @return
	 */
	private boolean dataSourceFinite()
	{
		// A real-time routing spec that is enabled and has a streaming data
		// source like an LRGS or directory should never complete.
		RoutingSpec rs = Database.getDb().routingSpecList.find(
			scheduleEntry.getRoutingSpecName());
		if (rs == null || rs.dataSource == null)
		{
			return true;
		}
		if (rs.untilTime != null && rs.untilTime.trim().length() > 0)
		{
			return true;
		}
		String dsType = rs.dataSource.dataSourceType.toLowerCase();
		if (dsType.equals("lrgs") || dsType.equals("hotbackupgroup") || dsType.equals("directory"))
			return false;
		return true;
	}
		
	/**
	 * One time initialization
	 * @throws DbIoException 
	 */
	void initialize() 
		throws DbIoException
	{
		shutdownComplete = 0L;
		// Lookup the lastRunStart
		ScheduleEntryDAI scheduleEntryDAO = Database.getDb().getDbIo().makeScheduleEntryDAO();
		try
		{
			if (scheduleEntryDAO != null)
				seStatus = scheduleEntryDAO.getLastScheduleStatusFor(scheduleEntry);
			String stz = scheduleEntry.getTimezone();
			if (stz == null)
				stz = "UTC";
			jtz = TimeZone.getTimeZone(stz);
			if (jtz == null)
				jtz = TimeZone.getDefault();
		}
		finally
		{
			if (scheduleEntryDAO != null)
				scheduleEntryDAO.close();
		}
	}
	
	/**
	 * Check the schedule and return true if it's time to start the routing spec
	 */
	private boolean checkSchedule()
	{
		Date startTime = scheduleEntry.getStartTime();
		
		if (startTime == null) // means start immediately
			return true;
		
		Date now = new Date();
		if (now.before(startTime))
		{
			if (System.currentTimeMillis()-lastSchedDebug > 60000L)
			{
				dacqEventLogger.debug1(getName() + " Before start time of " + startTime);
				lastSchedDebug = System.currentTimeMillis();
			}

			return false;
		}
		
		if (seStatus == null)
			// It is after start time and it has never been run.
			return true;
		
		Date lastRunStart = seStatus.getRunStart();
		Calendar cal = Calendar.getInstance(jtz);
		cal.setTime(lastRunStart);
		
		String intv = scheduleEntry.getRunInterval();
		if (intv == null)
			// Use case: user re-enables a one-time entry that has already run.
			return true;
		
		IntervalIncrement intinc = IntervalIncrement.parse(intv);
		if (intinc.isLessThanDay())
		{
			// Set cal to same time as start time on current day.
			Calendar startCal = Calendar.getInstance(jtz);
			startCal.setTime(startTime);
		
			cal.set(Calendar.MILLISECOND, 0);
			cal.set(Calendar.SECOND, startCal.get(Calendar.SECOND));
			cal.set(Calendar.MINUTE, startCal.get(Calendar.MINUTE));
			cal.set(Calendar.HOUR_OF_DAY, startCal.get(Calendar.HOUR_OF_DAY));
			
			// Now increment back until we're before last run time
			while (cal.getTime().after(lastRunStart))
				cal.add(intinc.getCalConstant(), -intinc.getCount());
			
			// We are before last run. Now increment forward until we're just after it.
			while (cal.getTime().before(lastRunStart))
				cal.add(intinc.getCalConstant(), intinc.getCount());
		}
		else
		{
			// interval is more than a day. Set to schedule entry start time,
			// then increment forward until we're just after last run.
			cal.setTime(startTime);
			while (!cal.getTime().after(lastRunStart))
				cal.add(intinc.getCalConstant(), intinc.getCount());
		}
		
		Date nextRunTime = cal.getTime();
		if (System.currentTimeMillis()-lastSchedDebug > 60000L)
		{
			dacqEventLogger.debug1(getName() + " next run time = " + nextRunTime);
			lastSchedDebug = System.currentTimeMillis();
		}
		// Check if current time is after the next run time we computed.
		return !now.before(nextRunTime);
	}

	/**
	 * Called from RoutingScheduler when either A.) it is exiting, or
	 * B.) the ScheduleEntry for this executive has been modified or deleted.
	 * This method shuts down the routing spec thread if one is running. It
	 * then writes the final status record.
	 */
	public void shutdown()
	{
		Logger.instance().debug1("ScheduleEntryExec shutdown() called for " + getName());
		if (seThread != null)
		{
			seThread.shutdown();
			shutdownStarted = System.currentTimeMillis();
			runState = RunState.shutdown;
		}
	}

	RoutingSpecThread makeThread()
	{
		// Get the routing spec from the database
		RoutingSpec rs = Database.getDb().routingSpecList.find(
			scheduleEntry.getRoutingSpecName());
		if (rs == null)
		{
			dacqEventLogger.failure("ScheduleEntryExec.makeThread: "
				+ "No such routing spec '" + scheduleEntry.getRoutingSpecName()
				+ "' in database.");
			disableScheduleEntry();
			return null;
		}

		if (rereadRsBeforeExec)
		{
			Logger.instance().info("ScheduleEntryExecutive.makeThread -- rereading Routing Spec.");
			try
			{
				rs.read();
				rs.prepareForExec();
			}
			catch (DatabaseException ex)
			{
				dacqEventLogger.info("ScheduleEntryExec.makeThread: "
					+ " Routing Spec '" + scheduleEntry.getRoutingSpecName()
					+ "' no longer exists in database, disabling the schedule entry: " + ex);
				disableScheduleEntry();
			}
		}
		
		String s = rs.getProperty("debugLevel");
		if (s != null)
		{
			try
			{
				int dblev = Integer.parseInt(s);
				schedEntryMinLogPriority =
					dblev == 1 ? Logger.E_DEBUG1 :
					dblev == 2 ? Logger.E_DEBUG2 :
					dblev == 3 ? Logger.E_DEBUG3 : Logger.E_INFORMATION;
				// debugLevel property should only be used to increase debug level. Never decrease.
				if (schedEntryMinLogPriority > appMinLogPriority)
					schedEntryMinLogPriority = appMinLogPriority;
				dacqEventLogger.setMinLogPriority(schedEntryMinLogPriority);
			}
			catch(NumberFormatException ex)
			{
				dacqEventLogger.warning("RoutingSpec " + rs.getName() + " Ignoring invalid debugLevel property '" 
					+ s + "' -- must be 1, 2, or 3.");
			}
		}

		// Allocate a new status structure
		seStatus = new ScheduleEntryStatus(Constants.undefinedId);
		seStatus.setScheduleEntryId(scheduleEntry.getId());
		seStatus.setScheduleEntryName(scheduleEntry.getName());
		seStatus.setRunStart(new Date());
		if (parent != null)
			seStatus.setHostname(parent.getHostname());
		else
			try
			{
				seStatus.setHostname(InetAddress.getLocalHost().getHostName());
			}
			catch (UnknownHostException ex)
			{
				dacqEventLogger.warning("Cannot get hostname: " + ex);
				seStatus.setHostname("unknown");
			}
		runState = RunState.running;
		writeStatus("Starting");
		
		seThread = new RoutingSpecThread(rs);
		seThread.scheduleEntryStatusId = seStatus.getId();
		seThread.setMyExec(this);
		seThread.setMyStatus(seStatus);
		seThread.closeDbOnQuit = false;
		seThread.doRoutingSpecCheck = rereadRsBeforeExec;

		if (parent != null)
			parent.setThreadLogger(seThread, dacqEventLogger);
		return seThread;
	}
	
	/**
	 * Called from the check() method when it is time to start the routing
	 * spec thread.
	 */
	void startThread()
	{
		makeThread();
		if (seThread != null)
			seThread.start();
	}
	
	/** Write the status structure to the database */
	public void writeStatus(String status)
	{
		seStatus.setLastModified(new Date());
		seStatus.setRunStatus(status == null || status.trim().length()==0 ? "-" : status);
		ScheduleEntryDAI scheduleEntryDAO = null;
		try
		{
			scheduleEntryDAO = Database.getDb().getDbIo().makeScheduleEntryDAO();
			dacqEventLogger.setSchedEntryStatusId(seStatus.getId());
		}
		catch(NullPointerException ex)
		{
			dacqEventLogger.debug1("transient error shutting down: " + ex);
			return;
		}
		if (scheduleEntryDAO == null)
		{
			dacqEventLogger.debug1("Cannot write schedule entry. Not supported on this database.");
			return;
		}

		try
		{
			scheduleEntryDAO.writeScheduleStatus(seStatus);
		}
		catch (DbIoException ex)
		{
			dacqEventLogger.warning(getName() + " Cannot write status: " + ex);
		}
		finally
		{
			scheduleEntryDAO.close();
		}
	}
	
	/** 
	 * Called from my ScheduleEntryThread when it is finished.
	 */
	public void rsFinished()
	{
		// null interval and non-null start time means run-once-only
		if (scheduleEntry.getRunInterval() == null
		 && scheduleEntry.getStartTime() != null)
		{
			// If this is a one-time RS, set runState to complete and disable.
			runState = RunState.complete;
			disableScheduleEntry();
		}
		else if (scheduleEntry.getRunInterval() != null)
		{
			// A periodic SE has finished for now and is waiting for the next run.
			runState = RunState.waiting;
		}
		else
		{
			// A realtime SE has terminated due to data source error or 
			// because the scheduler is going down.
			shutdownStarted = System.currentTimeMillis();
			runState = RunState.shutdown;
		}

		// This will cause it to write the final status to this.writeStatus() below
		seThread.writeStatus();
		if (parent != null)
			parent.threadFinished(seThread);
		lastDcpMsg = seThread.getLastDcpMsg();
		seThread = null;
		shutdownComplete = System.currentTimeMillis();
		dacqEventLogger.setSchedEntryStatusId(DbKey.NullKey);
		Logger.instance().debug1("ScheduleEntryExecutive.rsFinished() " + getName() + " state is now " +
			runState.toString());
	}
	
	public RunState getRunState()
	{
		return runState;
	}

	public ScheduleEntry getScheduleEntry()
	{
		return scheduleEntry;
	}
	
	public String getName()
	{
		return "SchedEntry:" + scheduleEntry.getName();
	}
	
	/**
	 * Disable this ScheduleEntry in the database.
	 */
	private void disableScheduleEntry()
	{
		scheduleEntry.setEnabled(false);
		ScheduleEntryDAI scheduleEntryDAO = Database.getDb().getDbIo().makeScheduleEntryDAO();
		if (scheduleEntryDAO == null)
		{
			dacqEventLogger.debug1("Schedule entries not supported on this database.");
			return;
		}

		try
		{
			scheduleEntryDAO.checkScheduleEntry(scheduleEntry);
			scheduleEntryDAO.writeScheduleEntry(scheduleEntry);
		}
		catch (Exception ex)
		{
			dacqEventLogger.failure("Cannot disable schedule entry: " + ex);
		}
		finally
		{
			scheduleEntryDAO.close();
		}
	}

	public static void setRereadRsBeforeExec(boolean tf)
	{
		rereadRsBeforeExec = tf;
	}
	
	public void setPlatform(Platform p)
	{
		if (p == null)
		{
			dacqEventLogger.setPlatformId(DbKey.NullKey);
			dacqEventLogger.setMinLogPriority(schedEntryMinLogPriority);
		}
		else
		{	
			dacqEventLogger.setPlatformId(p.getId());
			int dblev = p.getDebugLevel();
			int newMinPriority =
				dblev == 1 ? Logger.E_DEBUG1 :
				dblev == 2 ? Logger.E_DEBUG2 :
				dblev == 3 ? Logger.E_DEBUG3 : schedEntryMinLogPriority;
			// Platform debug level can only increase debug info, not decrease it.
			if (newMinPriority > schedEntryMinLogPriority)
				newMinPriority = schedEntryMinLogPriority;
			dacqEventLogger.setMinLogPriority(newMinPriority);
		}
	}
	
	public void setMessageStart(Date timeStamp)
	{
		dacqEventLogger.setMsgStart(timeStamp);
	}

	
	public void setSubsystem(String subsystem)
	{
		dacqEventLogger.setSubsystem(subsystem);
	}

	public long getLastEventsPurge()
	{
		return lastEventsPurge;
	}

	public void setLastEventsPurge(long lastEventsPurge)
	{
		this.lastEventsPurge = lastEventsPurge;
	}

	public DacqEventLogger getDacqEventLogger()
	{
		return dacqEventLogger;
	}

	public DcpMsg getLastDcpMsg()
	{
		return lastDcpMsg;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy