/// <summary> /// Returns (currently not used): 1 if scheduler should retry later; 0 otherwise /// </summary> /// <param name="heContext"></param> /// <param name="timeout"></param> /// <returns></returns> public static int Execute(HeContext heContext, int timeout) { string schedule; int cyclicJobId; bool isShared; DateTime dbNow, newNextRun; int eSpaceId = Global.eSpaceId; int tenantId = heContext.AppInfo.Tenant.Id; string errorLogId = ""; DateTime dbStartTime, lastRun, nextRun, previousNextRun, isRunnSince; DateTime localStartedDateTime = DateTime.Now; // used to measure the timer duration DateTime newIsRunningSince, currentIsRunningSince; int duration = 0; string isRunningBy; int numberOfTries = 0; int maxNumberOfRetries = RuntimePlatformSettings.Timers.NumberOfRetries.GetValue(); // ExceptionLogged signals that this type of exception has been logged bool ExceptionLogged = false; try { try { using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) { dbStartTime = DBRuntimePlatform.Instance.GetDbDatetime(mainTrans); cyclicJobId = DBRuntimePlatform.Instance.GetCyclicJobId(mainTrans, eSpaceId, tenantId, TimerKey, out isShared); } using (Transaction privTrans = DatabaseAccess.ForRuntimeDatabase.GetCommitableTransaction()) { try { bool ok = DBRuntimePlatform.Instance.GetCyclicJobForUpdate(privTrans, cyclicJobId, isShared, out isRunnSince, out schedule, out lastRun, out previousNextRun, out isRunningBy); if (!ok) { throw new DataBaseException("Unable to get data for Bootstrap Timer (" + cyclicJobId + ") in Execute method."); } // return if not yet time to execute if (dbStartTime.CompareTo(previousNextRun) < 0) { return(0); } // some node is executing it if (isRunnSince != BuiltInFunction.NullDate()) { // In normal cases, it will return from execution because // some other node is executing this timer if (dbStartTime.CompareTo(isRunnSince.AddSeconds(1.2 * timeout)) < 0) { return(0); } // recover mechanism: if it is executing for too long // maybe there was a problem with the node, but the database // was left with data stating it is still executing. TimeSpan ts; string recoverDetail = ""; try { string isRunnBy = "?"; ts = (dbStartTime - isRunnSince); recoverDetail = "Marked as running since " + isRunnSince.ToString() + " (" + ts.TotalMinutes.ToString() + " minutes) by hubnode '" + isRunnBy + "'. Timeout defined is " + (timeout / 60.0).ToString() + " minutes"; } catch {} ErrorLog.LogApplicationError("Recovering timer Bootstrap execution. It is marked as running for too long", recoverDetail, heContext, ""); } // lets update db, stating that I'm executing the job numberOfTries = DBRuntimePlatform.Instance.GetNumberOfTries(privTrans, cyclicJobId, isShared); if (numberOfTries >= maxNumberOfRetries) { DBRuntimePlatform.Instance.GetTimerScheduleNextRunAndDbDate(privTrans, cyclicJobId, isShared, out schedule, out dbNow, out newNextRun, out currentIsRunningSince); RuntimeScheduler.NextRun(schedule, dbNow, out nextRun); DBRuntimePlatform.Instance.SetTimerNextRun(privTrans, cyclicJobId, isShared, nextRun); errorLogId = ErrorLog.StaticWrite(DateTime.Now, heContext.Session.SessionID, heContext.AppInfo.eSpaceId, heContext.AppInfo.Tenant.Id, heContext.Session.UserId, String.Format("Timer Bootstrap reached maximum number of retries ({0}) [Calculating next run]", maxNumberOfRetries), "", ""); duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; // lets update the rest of the cyclic job stuff... DBRuntimePlatform.Instance.SetTimerLastRun(privTrans, cyclicJobId, isShared, dbStartTime, duration); CyclicJobLog jobLog = new CyclicJobLog(); jobLog.Write(dbStartTime, duration, TimerKey, heContext.AppInfo.eSpaceId, heContext.AppInfo.Tenant.Id, RuntimeEnvironment.MachineName, errorLogId, previousNextRun, nextRun, heContext.AppInfo.eSpaceName, heContext.AppInfo.ApplicationName, heContext.AppInfo.ApplicationUIDAsKey, TimerName); return(0); } DBRuntimePlatform.Instance.SetTimerRunningBy(privTrans, cyclicJobId, isShared, RuntimeEnvironment.MachineName, out newIsRunningSince); DBRuntimePlatform.Instance.IncrementNumberOfTries(privTrans, cyclicJobId, isShared); } finally { privTrans.Commit(); // NOTE: Must release and reget the session... the timer action might commit or something! } } } catch (Exception e) { duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; errorLogId = ErrorLog.LogApplicationError("Timer Bootstrap error (before executing action 'BootstrapContacts'). Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, ""); ExceptionLogged = true; return(1); // assume some error in update, so it will retry later... } // from tests in framework 1.0, it seems this ScriptTimeout is not working as expected... // but it seems in framework 1.1 it works fine (if not in debug mode) heContext.Context.Server.ScriptTimeout = timeout; try { Actions.ActionBootstrapContacts(heContext); } catch (Exception e) { // error in timer action: we must rollback all pending transactions DatabaseAccess.RollbackAllTransactions(); using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) { if (!ExceptionLogged) // if already logged, don't log the same error { duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; currentIsRunningSince = DBRuntimePlatform.Instance.GetTimerRunningSince(mainTrans, cyclicJobId, isShared); if (currentIsRunningSince != newIsRunningSince) { ErrorLog.LogApplicationError("Timer 'Bootstrap' Running Since Date changed unexpectedly. Expected '" + BuiltInFunction.DateTimeToText(newIsRunningSince) + "' but was '" + BuiltInFunction.DateTimeToText(currentIsRunningSince) + "'.", "", heContext, ""); } DBRuntimePlatform.Instance.ClearTimerRunningBy(mainTrans, cyclicJobId, isShared); ExceptionLogged = true; errorLogId = ErrorLog.LogApplicationError("Timer Bootstrap error (inside action 'BootstrapContacts'). Timer duration = " + duration + " secs:" + e.Message + String.Format(" [retry {0} of {1} scheduled]", numberOfTries + 1, maxNumberOfRetries), e.ToString(), heContext, ""); CyclicJobLog jobLog = new CyclicJobLog(); jobLog.Write( dbStartTime, duration, TimerKey, heContext.AppInfo.eSpaceId, heContext.AppInfo.Tenant.Id, RuntimeEnvironment.MachineName, errorLogId, previousNextRun, previousNextRun, heContext.AppInfo.eSpaceName, heContext.AppInfo.ApplicationName, heContext.AppInfo.ApplicationUIDAsKey, TimerName); return(0); } } } using (Transaction mainTrans = DatabaseAccess.ForRuntimeDatabase.GetRequestTransaction()) { try { // calculate next run based on current date in db server and also // on scheduler (that may have been changed inside the user action // for example) DBRuntimePlatform.Instance.GetTimerScheduleNextRunAndDbDate(mainTrans, cyclicJobId, isShared, out schedule, out dbNow, out newNextRun, out currentIsRunningSince); RuntimeScheduler.NextRun(schedule, dbNow, out nextRun); if (newNextRun == previousNextRun) { DBRuntimePlatform.Instance.SetTimerNextRun(mainTrans, cyclicJobId, isShared, nextRun); } else { nextRun = newNextRun; } duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; if (currentIsRunningSince != newIsRunningSince) { ErrorLog.LogApplicationError("Timer 'Bootstrap' Running Since Date changed unexpectedly. Expected '" + BuiltInFunction.DateTimeToText(newIsRunningSince) + "' but was '" + BuiltInFunction.DateTimeToText(currentIsRunningSince) + "'.", "", heContext, ""); } // lets update the rest of the cyclic job stuff... DBRuntimePlatform.Instance.SetTimerLastRun(mainTrans, cyclicJobId, isShared, dbStartTime, duration); DBRuntimePlatform.Instance.ResetNumberOfTries(mainTrans, cyclicJobId, isShared); CyclicJobLog jobLog = new CyclicJobLog(); jobLog.Write( dbStartTime, duration, TimerKey, heContext.AppInfo.eSpaceId, heContext.AppInfo.Tenant.Id, RuntimeEnvironment.MachineName, errorLogId, previousNextRun, nextRun, heContext.AppInfo.eSpaceName, heContext.AppInfo.ApplicationName, heContext.AppInfo.ApplicationUIDAsKey, TimerName); return(0); } catch (Exception e) { if (!ExceptionLogged) // if already logged, don't log the same error { duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; errorLogId = ErrorLog.LogApplicationError("Timer Bootstrap error (after executing action 'BootstrapContacts'). Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, ""); ExceptionLogged = true; } return(1); // assume some error in update, so it will retry later... } } } catch (Exception e) { if (!ExceptionLogged) // if already logged, don't log the same error { duration = (int)((TimeSpan)(DateTime.Now - localStartedDateTime)).TotalSeconds; errorLogId = ErrorLog.LogApplicationError("Timer Bootstrap error. Timer duration = " + duration + " secs: " + e.Message + " [Will retry later]", e.ToString(), heContext, ""); ExceptionLogged = true; } return(1); } }