ProtectedconstructorGets the current active polling interval in milliseconds.
ProtectedBaseAccess the contained SchedulingEngineBase instance for metadata operations.
Check if polling is currently active
Convenience accessor — lease duration as integer minutes. Production
code may use either this or LeaseTimeoutMs. The setter validates
positive integer minutes (no fractional minutes via this path; use
LeaseTimeoutMs for sub-minute precision, including tests).
Lock lease duration in milliseconds. Default 600000 (10 minutes).
Production callers should use this setter — matches the ms unit of
MJServer's scheduledJobs.defaultLockTimeout config.
Maximum concurrent scheduled jobs on this engine instance. Default 5.
Configurable via MJServer's scheduledJobs.maxConcurrentJobs config.
Gets recent scheduled job runs.
Gets scheduled jobs (active only by default).
Gets all scheduled job types.
StaticInstanceGet singleton instance
Configures the engine by loading scheduling metadata from the database. Delegates to SchedulingEngineBase.
OptionalforceRefresh: booleanOptionalcontextUser: UserInfoOptionalprovider: IMetadataProviderDispatch all currently-due scheduled jobs WITHOUT awaiting their completion.
This is the polling-path entry point introduced in v5.39 as part of the scheduler decoupling fix (GH #2736). The poll loop calls this and re-arms its timer based on the synchronous-portion return; jobs run in the background.
Two phases:
PHASE 1 — Stale-inflight sweep (decoupled from isJobDue AND from the cap):
Walks inflightJobPromises looking for jobs whose DB lease has expired.
Untracks each, frees its cap slot, and fire-and-forget marks any
orphaned Status='Running' run records as abandoned. Runs first so
it can free slots BEFORE the cap check throttles dispatch.
PHASE 2 — Cap-bounded dispatch loop:
For each due job, atomically acquire its lock via spAcquireScheduledJobLock.
Only jobs whose lock was acquired count against MaxConcurrentJobs.
Lock-failed jobs are reported via lockedOut counter.
If at-cap, remaining due jobs counted via skippedAtCapacity and
picked up by subsequent polls as slots free (no in-memory queueing).
Same-instance double-dispatch is structurally prevented by the atomic lock sproc — its WHERE clause filters held-and-not-stale locks, so any second attempt against the same job ID returns Acquired=0.
In-flight dispatched promises are tracked in inflightJobPromises so
StopPolling({ waitForInflight: true }) can perform graceful shutdown.
Counters for observability.
Execute a specific scheduled job by ID
ID of the job to execute
User context for execution
The scheduled job run record
Execute all scheduled jobs that are currently due
Evaluates each active job's cron expression against the current time. Jobs that are due are executed via their plugin's Execute method.
User context for execution
Optional time to evaluate against (defaults to now)
Array of scheduled job run records
The Global Object Store is a place to store global objects that need to be shared across the application. Depending on the execution environment, this could be the window object in a browser, or the global object in a node environment, or something else in other contexts. The key here is that in some cases static variables are not truly shared because it is possible that a given class might have copies of its code in multiple paths in a deployed application. This approach ensures that no matter how many code copies might exist, there is only one instance of the object in question by using the Global Object Store.
Handle job changes (create, update, delete) Reloads job metadata and restarts polling if needed
This method is called automatically by MJScheduledJobEntityExtended.Save() and Delete()
User context for reloading metadata
Start continuous polling for scheduled jobs.
Async (changed in v5.39) because upfront work — Config, initial-NextRunAt seeding, stale-lock cleanup, permission probe — runs ONCE before the first poll fires. Subsequent polls assume that work is complete.
The poll callback re-arms its timer FIRST, before any awaited work, so that any hang downstream (Config, DispatchScheduledJobs, etc.) cannot prevent the next poll from firing on schedule. This is the load-bearing invariant of the decoupling fix (see plans/scheduled-job-engine-decoupling.md).
User context for execution
Stop continuous polling.
Async (changed in v5.39). With opts.waitForInflight=true, awaits all currently-dispatched jobs to settle before returning. With opts.maxWaitMs, bounds that wait so a zombie can't make shutdown hang indefinitely.
Order matters: sets acceptingDispatches=false FIRST so no new entries can be added to inflightJobPromises during the snapshot for allSettled.
Optionalopts: { maxWaitMs?: number; waitForInflight?: boolean }OptionalmaxWaitMs?: numberBound the wait (only meaningful with waitForInflight)
OptionalwaitForInflight?: booleanAwait dispatched jobs before returning
Calculate and update the active polling interval.
Protected StaticgetReturns the singleton instance of the class. If the instance does not exist, it is created and stored in the Global Object Store. If className is provided it will be used as part of the key in the Global Object Store, otherwise the actual class name will be used. NOTE: the class name used by default is the lowest level of the object hierarchy, so if you have a class that extends another class, the lowest level class name will be used.
OptionalclassName: string
Engine for managing scheduled job execution
This engine uses composition to delegate metadata operations to SchedulingEngineBase while adding execution capabilities:
Description
ONLY USE ON SERVER-SIDE. For metadata only, use SchedulingEngineBase which can be used anywhere.
Example