Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions docs/articles/scheduler.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,48 @@ scheduler.Schedule("cleanup", "0 3 * * *", async ct =>
await DoCleanupAsync(ct);
});
```

## Event loop

For applications that need a tight, frame-driven loop (game servers, simulations, real-time
processing), `SquidStd.Services.Core` provides `EventLoopService` — a dedicated background thread
(`SquidStd-EventLoop`) that, every frame:

1. drains the `IMainThreadDispatcher` (deferred callbacks posted with `Post`), and
2. advances the timer wheel (`ITimerService.UpdateTicksDelta`),

then sleeps `IdleSleepMs` (default 1 ms) when a frame produced no work. It exposes tick metrics
(`tick_count`, `tick_avg_ms`, `tick_max_ms`, `idle_sleeps_total`) under the `eventloop` provider and
logs a warning when a tick takes longer than `SlowTickThresholdMs` (default 250 ms).

Register it (after `RegisterCoreServices`) with `RegisterEventLoop()`:

```csharp
using DryIoc;
using SquidStd.Core.Interfaces.Threading;
using SquidStd.Services.Core.Extensions;

container.RegisterEventLoop();

var loop = container.Resolve<IEventLoopService>();
Console.WriteLine($"{loop.TickCount} ticks, avg {loop.AverageTickMs:0.###} ms");
```

Configure it via the `eventLoop` section (section keys are matched as registered, property names are
PascalCase):

```yaml
eventLoop:
IdleCpuEnabled: true # sleep when a tick produced no work
IdleSleepMs: 1 # how long to sleep when idle
SlowTickThresholdMs: 250 # warn above this per-tick time
```

### Event loop vs. timer-wheel pump

Both `EventLoopService` and `TimerWheelPumpService` advance the timer wheel, so they are **mutually
exclusive** — register exactly one. The exclusivity is structural: both implement the
`ITimerWheelDriver` marker, `RegisterEventLoop()` throws if a driver is already registered, and modules
that need the wheel (the worker manager, the mail poller) auto-register the pump only when no driver is
present. Use the pump for ordinary apps where a coarse periodic pump is enough; use the event loop when
you want the wheel advanced at frame-rate alongside dispatcher draining.
5 changes: 5 additions & 0 deletions docs/tutorials/events-jobs-scheduling.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ You'll see `received: hello`, `job ran on a worker thread`, and—if you let it
The event bus dispatches to sync and async listeners; the job system is a fixed-size worker-thread pool; the cron
scheduler is driven by the timer wheel (registered by `RegisterSchedulerServices`).

The timer wheel is advanced by a *driver*. `RegisterSchedulerServices()` uses `TimerWheelPumpService`, a
periodic background pump. Apps that need a frame-rate loop can instead call `RegisterEventLoop()`, which advances
the wheel and drains the main-thread dispatcher on a dedicated thread — see
[Scheduler → Event loop](../articles/scheduler.md#event-loop). The two are mutually exclusive: register exactly one.

## See also

- [SquidStd.Services.Core reference](../articles/services-core.md)
Expand Down
1 change: 1 addition & 0 deletions src/SquidStd.Services.Core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ await seeded.DispatchAsync(command, connection); // factory maps connection ->
| `TimerWheelService` | Timer-wheel scheduling. |
| `MainThreadDispatcherService` | Main-thread work dispatch. |
| `MetricsCollectionService` | Metric sample aggregation. |
| `EventLoopService` | Frame-driven loop: drains the dispatcher and advances the timer wheel. |

## Related

Expand Down
Loading