Documentation
¶
Overview ¶
Package scheduler provides a simple IST-aware task scheduler that runs named tasks at specific times on trading days (Monday–Friday).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsMarketHoliday ¶
IsMarketHoliday returns true if the given time falls on an NSE trading holiday.
func IsTradingDay ¶
IsTradingDay returns true if the given time is a weekday and not a market holiday.
func MarketStatus ¶
MarketStatus returns the current market status based on IST time. Possible values: "pre_open", "open", "closing_session", "closed", "closed_weekend", "closed_holiday".
Types ¶
type Clock ¶
Clock returns the current time. Defaults to time.Now. Override in tests via SetClock to control time.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler checks every minute whether any registered task should fire. Tasks only run on weekdays (Mon-Fri) and at most once per calendar day.
func (*Scheduler) ListTaskNames ¶
ListTaskNames returns the Name field of every task currently held by the scheduler in registration order. Intended for tests, admin tooling, and log diagnostics ("what's the active schedule?").
Snapshot: safe for concurrent use; the returned slice is a copy.
func (*Scheduler) RegisterTaskProvider ¶
func (s *Scheduler) RegisterTaskProvider(p TaskProvider)
RegisterTaskProvider installs a TaskProvider. Safe to call before Start; panic-safe against nil (nil providers are silently dropped so a plugin that returns a typed-nil from a feature-flag path doesn't crash scheduler init).
Registering the same provider twice is allowed but discouraged — both registrations will fire at Start, producing duplicate tasks whose Name() collision will cause the tick loop to treat them as "already ran today" after the first fires. Use a single provider instance per logical source of tasks.
func (*Scheduler) SetClock ¶
SetClock overrides the time source (for testing). Safe to call concurrently with a running scheduler; tick() reads the clock under s.mu.
func (*Scheduler) SetTickInterval ¶
SetTickInterval overrides the loop tick interval (for testing). Must be called before Start — the loop caches the interval at launch and won't observe later changes.
func (*Scheduler) Start ¶
func (s *Scheduler) Start()
Start launches a background goroutine that ticks every minute.
Before the goroutine launches, every registered TaskProvider is consulted via Tasks() and its returned tasks are appended to the scheduler's task list. This is the single collection point for plugin-contributed schedules — Tasks() is NOT called again until the scheduler is stopped and a new one is constructed.
func (*Scheduler) Stop ¶
func (s *Scheduler) Stop()
Stop signals the scheduler goroutine to exit and waits for it to finish. Safe to call multiple times (select guards the channel close, loopDone reads are idempotent on a closed channel). Safe to call without Start: loopDone is initialised in New and only closed by loop — the bounded wait below protects against "Stop before Start" by returning once a small window passes with no signal.
type Task ¶
type Task struct {
Name string // unique identifier for dedup tracking
Hour int // IST hour (0-23)
Minute int // IST minute (0-59)
Fn func() // the work to perform
}
Task describes a named function that should run once per trading day at a specific IST time.
type TaskProvider ¶
type TaskProvider interface {
// Name is a stable identifier for logs and duplicate detection.
// Must be unique across registered providers; a second provider
// with the same Name replaces the first (last-wins). Use snake_case.
Name() string
// Tasks returns the tasks this provider wishes to contribute.
// Called exactly once at Start(). A provider that wishes to
// contribute no tasks (e.g. feature-flagged off) should return a
// nil or empty slice — the provider registration itself is not
// an error.
//
// The returned slice is copied into the scheduler's task list
// before Start launches the tick goroutine, so callers need not
// worry about the scheduler retaining or mutating the slice.
Tasks() []Task
}
TaskProvider is the plugin extension point for Scheduler.
A TaskProvider contributes zero or more Tasks at startup. Unlike the pre-existing Add(Task) API — which expects callers to know their task definitions at construction time — a provider is consulted once at Start(): every registered provider has its Tasks() method invoked, and the returned tasks are appended to the scheduler's task list before the tick loop begins.
Design rationale:
Plugins cannot always statically list their tasks at wire-up time. A billing plugin might only emit a "collect_usage_metrics" task if Stripe is configured; a compliance plugin might emit per-exchange holiday-reminder tasks that depend on an instruments manifest loaded after construction. Deferring task enumeration until Start lets providers inspect runtime state that isn't ready at New().
Collecting once at Start (as opposed to per-tick) preserves the existing "tasks are fixed once scheduler runs" invariant, which other callers depend on. Dynamic task registration would require re-examining the dedup map (lastRun) on every tick — a tradeoff we explicitly rejected because it makes "did this task run today?" harder to reason about.
The TaskProvider interface is intentionally narrow. It carries no lifecycle, config, or teardown hooks. Providers that need their own lifecycle should wire that through their enclosing module (e.g. the plugin's own Init/Shutdown); the scheduler cares only about "give me your tasks".
Typical usage in app wiring:
sched := scheduler.New(logger)
sched.RegisterTaskProvider(myplugin.NewScheduleProvider(deps))
// ... sched.Add(Task{...}) for built-in tasks ...
sched.Start()