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.
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 forLimitPolicy::executeTimeout/Bridge::setExecuteDeadline) andNetworkMonitor's fixed-interval connectivity probe — both fixed at construction, no general facility. userver'sutils::PeriodicTaskruns user code on every machine in the cluster on a configurable interval that's mutable at runtime viaSetSettings(), 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 wayTimeoutScheduler/NetworkMonitordo 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 differentemscripten_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-loopQTimers — 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 singlepost()by declared design: "intentionally minimal", no cancellation, fire-and-forget.Alternatives weighed. (a) Standalone
PeriodicTask(interval + callback, start/stop, runtimesetInterval, own thread + condvar mirroringNetworkMonitor::run()): S–M effort for the threaded build, but honest delivery requires a WASM browser-timer twin —TimeoutSchedulerneeded ~310 documented lines for the simpler one-shot case — with zero library-tier consumers waiting. (b)postAfter/postAtonIExecutor: M–L and the worst option. It breaks the spec's minimalism across five implementations, forces state onto the deliberately statelessQtExecutor, 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 takessleepas an injected dependency, showing the codebase's working pattern is inject timing, don't centralize it.Cost/benefit. Benefit today accrues to no one:
NetworkMonitoris 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.