@@ -2,19 +2,32 @@ const modulename = 'FxScheduler';
22import { parseSchedule } from '@lib/misc' ;
33import consoleFactory from '@lib/console' ;
44import { SYM_SYSTEM_AUTHOR } from '@lib/symbols' ;
5+ import type { UpdateConfigKeySet } from './ConfigStore/utils' ;
56const console = consoleFactory ( modulename ) ;
67
78
9+ //Types
10+ type RestartInfo = {
11+ string : string ;
12+ minuteFloorTs : number ;
13+ }
14+ type ParsedTime = {
15+ string : string ;
16+ hours : number ;
17+ minutes : number ;
18+ }
19+
20+
821//Consts
922const scheduleWarnings = [ 30 , 15 , 10 , 5 , 4 , 3 , 2 , 1 ] ;
1023
24+
1125/**
1226 * Processes an array of HH:MM, gets the next timestamp (sorted by closest).
13- * When time matches, it will be dist: 0, distMins: 0, and nextTs likely in the past due to seconds and milliseconds being 0.
14- * @param {Array } schedule
15- * @returns {Object } {string, minuteFloorTs}
27+ * When time matches, it will be dist: 0, distMins: 0, and nextTs likely in the
28+ * past due to seconds and milliseconds being 0.
1629 */
17- const getNextScheduled = ( parsedSchedule ) => {
30+ const getNextScheduled = ( parsedSchedule : ParsedTime [ ] ) : RestartInfo => {
1831 const thisMinuteTs = new Date ( ) . setSeconds ( 0 , 0 ) ;
1932 const processed = parsedSchedule . map ( ( t ) => {
2033 const nextDate = new Date ( ) ;
@@ -37,11 +50,15 @@ const getNextScheduled = (parsedSchedule) => {
3750 */
3851export default class FxScheduler {
3952 static configKeysWatched = [ 'restarter.schedule' ] ; //FIXME: add readonly prop when moving to typescript
53+ private nextTempSchedule : RestartInfo | false = false ;
54+ private calculatedNextRestartMinuteFloorTs : number | false = false ;
55+ private nextSkip : number | false = false ;
4056
4157 constructor ( ) {
42- this . nextSkip = false ;
43- this . nextTempSchedule = false ;
44- this . calculatedNextRestartMinuteFloorTs = false ;
58+ //Initial check to update status
59+ setImmediate ( ( ) => {
60+ this . checkSchedule ( ) ;
61+ } ) ;
4562
4663 //Cron Function
4764 setInterval ( ( ) => {
@@ -54,13 +71,36 @@ export default class FxScheduler {
5471 /**
5572 * Refresh configs, resets skip and temp scheduled, runs checkSchedule.
5673 */
57- handleConfigUpdate ( updatedConfigs ) {
74+ handleConfigUpdate ( updatedConfigs : UpdateConfigKeySet ) {
5875 this . nextSkip = false ;
5976 this . nextTempSchedule = false ;
6077 this . checkSchedule ( ) ;
6178 txCore . webServer . webSocket . pushRefresh ( 'status' ) ;
6279 }
6380
81+
82+ /**
83+ * Updates state when server closes.
84+ * Clear temp skips and skips next scheduled if it's in less than 2 hours.
85+ */
86+ handleServerClose ( ) {
87+ //Clear temp schedule, recalculates next restart
88+ if ( this . nextTempSchedule ) this . nextTempSchedule = false ;
89+ this . checkSchedule ( true ) ;
90+
91+ //Check if next scheduled restart is in less than 2 hours
92+ const inTwoHours = Date . now ( ) + 2 * 60 * 60 * 1000 ;
93+ if ( this . calculatedNextRestartMinuteFloorTs && this . calculatedNextRestartMinuteFloorTs < inTwoHours ) {
94+ console . warn ( 'Server closed, skipping next scheduled restart because it\'s in less than 2 hours.' ) ;
95+ this . nextSkip = this . calculatedNextRestartMinuteFloorTs ;
96+ }
97+ this . checkSchedule ( true ) ;
98+
99+ //Push UI update
100+ txCore . webServer . webSocket . pushRefresh ( 'status' ) ;
101+ }
102+
103+
64104 /**
65105 * Returns the current status of scheduler
66106 * NOTE: sending relative because server might have clock skew
@@ -87,10 +127,8 @@ export default class FxScheduler {
87127 * Sets this.nextSkip.
88128 * Cancel scheduled button -> setNextSkip(true)
89129 * Enable scheduled button -> setNextSkip(false)
90- * @param {boolean } enabled
91- * @param {string } author
92130 */
93- setNextSkip ( enabled , author ) {
131+ setNextSkip ( enabled : boolean , author ?: string ) {
94132 if ( enabled ) {
95133 let prevMinuteFloorTs , temporary ;
96134 if ( this . nextTempSchedule ) {
@@ -103,18 +141,20 @@ export default class FxScheduler {
103141 this . nextSkip = this . calculatedNextRestartMinuteFloorTs ;
104142 }
105143
106- //Dispatch `txAdmin:events:scheduledRestartSkipped`
107- txCore . fxRunner . sendEvent ( 'scheduledRestartSkipped' , {
108- secondsRemaining : Math . floor ( ( prevMinuteFloorTs - Date . now ( ) ) / 1000 ) ,
109- temporary,
110- author,
111- } ) ;
112-
113- //FIXME: deprecate
114- txCore . fxRunner . sendEvent ( 'skippedNextScheduledRestart' , {
115- secondsRemaining : Math . floor ( ( prevMinuteFloorTs - Date . now ( ) ) / 1000 ) ,
116- temporary
117- } ) ;
144+ if ( prevMinuteFloorTs ) {
145+ //Dispatch `txAdmin:events:scheduledRestartSkipped`
146+ txCore . fxRunner . sendEvent ( 'scheduledRestartSkipped' , {
147+ secondsRemaining : Math . floor ( ( prevMinuteFloorTs - Date . now ( ) ) / 1000 ) ,
148+ temporary,
149+ author,
150+ } ) ;
151+
152+ //FIXME: deprecate
153+ txCore . fxRunner . sendEvent ( 'skippedNextScheduledRestart' , {
154+ secondsRemaining : Math . floor ( ( prevMinuteFloorTs - Date . now ( ) ) / 1000 ) ,
155+ temporary
156+ } ) ;
157+ }
118158 } else {
119159 this . nextSkip = false ;
120160 }
@@ -130,9 +170,8 @@ export default class FxScheduler {
130170 /**
131171 * Sets this.nextTempSchedule.
132172 * The value MUST be before the next setting scheduled time.
133- * @param {String } timeString
134173 */
135- setNextTempSchedule ( timeString ) {
174+ setNextTempSchedule ( timeString : string ) {
136175 //Process input
137176 if ( typeof timeString !== 'string' ) throw new Error ( 'expected string' ) ;
138177 const thisMinuteTs = new Date ( ) . setSeconds ( 0 , 0 ) ;
@@ -188,10 +227,9 @@ export default class FxScheduler {
188227 /**
189228 * Checks the schedule to see if it's time to announce or restart the server
190229 */
191- async checkSchedule ( ) {
192- //FIXME: if fxchild === null || span less than 1 minute, return
230+ async checkSchedule ( calculateOnly = false ) {
193231 //Check settings and temp scheduled restart
194- let nextRestart ;
232+ let nextRestart : RestartInfo ;
195233 if ( this . nextTempSchedule ) {
196234 nextRestart = this . nextTempSchedule ;
197235 } else if ( Array . isArray ( txConfig . restarter . schedule ) && txConfig . restarter . schedule . length ) {
@@ -203,6 +241,7 @@ export default class FxScheduler {
203241 return ;
204242 }
205243 this . calculatedNextRestartMinuteFloorTs = nextRestart . minuteFloorTs ;
244+ if ( calculateOnly ) return ;
206245
207246 //Checking if skipped
208247 if ( this . nextSkip === this . calculatedNextRestartMinuteFloorTs ) {
@@ -223,6 +262,13 @@ export default class FxScheduler {
223262 txCore . translator . t ( 'restarter.schedule_reason' , { time : nextRestart . string } ) ,
224263 ) ;
225264
265+ //Check if server is in boot cooldown
266+ const processUptime = Math . floor ( ( txCore . fxRunner . child ?. uptime ?? 0 ) / 1000 ) ;
267+ if ( processUptime < txConfig . restarter . bootCooldown ) {
268+ console . verbose . log ( `Server is in boot cooldown, skipping scheduled restart.` ) ;
269+ return ;
270+ }
271+
226272 //reset next scheduled
227273 this . nextTempSchedule = false ;
228274
@@ -252,12 +298,10 @@ export default class FxScheduler {
252298
253299 /**
254300 * Triggers FXServer restart and logs the reason.
255- * @param {string } reasonInternal
256- * @param {string } reasonTranslated
257301 */
258- async triggerServerRestart ( reasonInternal , reasonTranslated ) {
302+ async triggerServerRestart ( reasonInternal : string , reasonTranslated : string ) {
259303 //Sanity check
260- if ( txCore . fxRunner . isIdle ) {
304+ if ( txCore . fxRunner . isIdle || ! txCore . fxRunner . child ?. isAlive ) {
261305 console . verbose . warn ( 'Server not running, skipping scheduled restart.' ) ;
262306 return false ;
263307 }
0 commit comments