Module: Tina4::Background
- Defined in:
- lib/tina4/background.rb
Overview
Periodic background task registry.
Matches Python's tina4_python.core.server.background(fn, interval) and
PHP's $app->background($callback, $interval) — a callback that runs
periodically alongside the server lifecycle.
Ruby has no asyncio event loop, so each task runs in its own thread. The GIL keeps it cooperative-enough for the periodic work this is meant for (queue draining, health checks, simulators). Errors in the callback are caught and logged so they don't kill the thread.
Class Method Summary collapse
-
.register(callback = nil, interval: 1.0, &block) ⇒ Hash
Register a periodic callback.
-
.stop_all(timeout: 2.0) ⇒ Object
Stop and join every running task.
-
.stop_task(task, timeout: 2.0) ⇒ Object
Stop a single task and DEREGISTER it.
-
.tasks ⇒ Object
All registered task descriptors.
Class Method Details
.register(callback = nil, interval: 1.0, &block) ⇒ Hash
Register a periodic callback.
22 23 24 25 26 27 28 29 30 31 |
# File 'lib/tina4/background.rb', line 22 def register(callback = nil, interval: 1.0, &block) cb = callback || block raise ArgumentError, "background requires a callback or block" if cb.nil? raise ArgumentError, "callback must respond to :call" unless cb.respond_to?(:call) task = { callback: cb, interval: interval.to_f, thread: nil, running: false } mutex.synchronize { tasks << task } start_task(task) task end |
.stop_all(timeout: 2.0) ⇒ Object
Stop and join every running task. Called on graceful shutdown.
Each stop_task deregisters its own descriptor, so there is no blanket
tasks.clear here: clearing would ALSO drop a task registered while
this loop was running — leaving its thread alive but invisible in the
registry, which is the worse of the two failure modes.
44 45 46 47 |
# File 'lib/tina4/background.rb', line 44 def stop_all(timeout: 2.0) snapshot = mutex.synchronize { tasks.dup } snapshot.each { |task| stop_task(task, timeout: timeout) } end |
.stop_task(task, timeout: 2.0) ⇒ Object
Stop a single task and DEREGISTER it. Used by tests that register, fire, then stop, and by any subsystem that owns a task for part of its life (e.g. Mqtt::Client#stop_keepalive).
The descriptor is removed from tasks so the registry never reports a
stopped task as registered — leaving it in place made tasks grow for
the life of the process on every start/stop cycle and made introspection
lie about what is actually running.
Idempotent: a second call on the same descriptor removes nothing, finds no thread and returns safely.
60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/tina4/background.rb', line 60 def stop_task(task, timeout: 2.0) task[:running] = false # Identity, not equality: `tasks.delete(task)` uses `==`, which would # take out any OTHER descriptor that happens to hold an equal Hash # (same callback, same interval). Only this exact descriptor goes. mutex.synchronize { tasks.delete_if { |registered| registered.equal?(task) } } thread = task[:thread] return unless thread thread.join(timeout) || thread.kill task[:thread] = nil end |
.tasks ⇒ Object
All registered task descriptors. Tests use this for introspection.
34 35 36 |
# File 'lib/tina4/background.rb', line 34 def tasks @tasks ||= [] end |