Skip to content

Consider: a general periodic-task facility #118

Description

@Yaraslaut

Part of #115 (userver-vs-morph survey).

Observation

morph's only timer-driven internals are narrowly special-purpose: morph::async::detail::TimeoutScheduler (single-shot timeouts for LimitPolicy::executeTimeout/Bridge::setExecuteDeadline) and NetworkMonitor's fixed-interval connectivity probe — both fixed at construction, no general facility. userver's utils::PeriodicTask runs user code on every machine in the cluster on a configurable interval that's mutable at runtime via SetSettings(), without reconstructing the task object.

Status: speculative, not a confirmed gap

morph doesn't currently need a general periodic-task facility — its existing timers are each narrowly scoped to their one caller.

Revisit only if

morph grows a general periodic-task facility for other reasons. If/when it does, PeriodicTask's runtime-mutable interval (SetSettings()) is a small, cheap idea worth borrowing rather than fixing the interval at construction the way TimeoutScheduler/NetworkMonitor do today.

Reference

Full comparison: #115, §3.7 and §4 of the attached document (docs/superpowers/findings/userver-vs-morph-2026-08-17.md).


Deep analysis: #118 periodic-task facility

Verdict: defer — the issue's own gating condition remains unmet, and code added since the last investigation actively points away from a shared facility.

Current state (verified 2026-08-19). morph now has four disjoint wait-then-act mechanisms, none sharing consolidatable logic: (1) TimeoutScheduler (include/morph/core/timeout_scheduler.hpp) — one-shot cancellable deadlines on a dedicated thread, with a structurally different emscripten_async_call-based build for single-threaded WASM, itself flagged as never locally compiled; (2) NetworkMonitor (include/morph/offline/network_monitor.hpp) — one fixed-interval probe loop with hysteresis; (3) Qt-event-loop QTimers — housekeeping and per-socket handshake timers (src/qt/qt_websocket_server.cpp:41,179) and the reconnect timer (src/qt/qt_websocket_backend.cpp:32); (4) EventPoller (examples/common/gui/event_poller.hpp) — QTimer-based fixed-interval event polling, which the kanban design (examples/kanban/README.md) plans to reuse as-is. IExecutor (docs/spec/core/executor.md) remains a single post() by declared design: "intentionally minimal", no cancellation, fire-and-forget.

Alternatives weighed. (a) Standalone PeriodicTask (interval + callback, start/stop, runtime setInterval, own thread + condvar mirroring NetworkMonitor::run()): S–M effort for the threaded build, but honest delivery requires a WASM browser-timer twin — TimeoutScheduler needed ~310 documented lines for the simpler one-shot case — with zero library-tier consumers waiting. (b) postAfter/postAt on IExecutor: M–L and the worst option. It breaks the spec's minimalism across five implementations, forces state onto the deliberately stateless QtExecutor, reopens the "no cancellation" decision, and amplifies open bug #127 (QtExecutor::post() use-after-free at teardown) — a delayed post maximizes the odds a task outlives its executor. Any timer facility that posts into executors inherits #127's lifetime hazard; building one before #127 defines an executor-lifetime contract would widen the blast radius. (c) Do nothing: free, and consistent with the newest code — ReconnectCoordinator (include/morph/offline/reconnect_coordinator.hpp) deliberately owns no thread and takes sleep as an injected dependency, showing the codebase's working pattern is inject timing, don't centralize it.

Cost/benefit. Benefit today accrues to no one: NetworkMonitor is correct and speced (docs/spec/offline/offline.md), Qt code already has the right Qt-native tool, and nobody needs runtime-mutable intervals. Costs are concrete: a second untestable WASM leg, a new spec file, and permanent maintenance of an abstraction with one hypothetical caller.

Recommendation. Keep #118 open as deferred, with two concrete revisit triggers: (1) a second library-tier (non-Qt-event-loop) consumer of fixed-interval repeating work appears, or (2) #127's fix establishes an executor teardown/lifetime contract that a delayed-post primitive could safely target — at which point option (b) becomes worth re-scoring against the standalone thread. Until either fires, the minimal sketch in the prior investigation comment remains the right shape to build.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions