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 dedicated OS thread, started at registration time. Because the thread runs regardless of which web server (Puma/WEBrick) is in front, a Ruby background task is never a silent no-op under production — the thread IS the runtime (contrast the Python ASGI / PHP-FPM silent-no-op the other frameworks had to fix). 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.

Defined Under Namespace

Classes: Task

Class Method Summary collapse

Class Method Details

.countInteger

Number of REGISTERED background tasks (stopped ones are already gone). The count half of the ONE shared surface, matching Python's background_task_count(), PHP's backgroundTaskCount() and Node's backgroundTaskCount().

Returns:

  • (Integer)


107
108
109
# File 'lib/tina4/background.rb', line 107

def count
  mutex.synchronize { tasks.length }
end

.register(callback = nil, interval: 1.0, &block) ⇒ Task

Register a periodic callback.

Parameters:

  • callback (#call, nil) (defaults to: nil)

    Object responding to call with no args.

  • interval (Float) (defaults to: 1.0)

    Seconds between invocations (default 1.0).

  • block (Proc)

    Optional block (used if callback is nil).

Returns:

  • (Task)

    The registered task handle — call .stop to end it.

Raises:

  • (ArgumentError)


85
86
87
88
89
90
91
92
93
94
# File 'lib/tina4/background.rb', line 85

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 = Task.new(cb, interval.to_f)
  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 handle, 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.



117
118
119
120
# File 'lib/tina4/background.rb', line 117

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) ⇒ Boolean

Stop a single task and DEREGISTER it. Used by Task#stop, by graceful shutdown, and by any subsystem that owns a task for part of its life (e.g. Mqtt::Client#stop_keepalive).

The handle 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 handle removes nothing, finds no thread and returns false.

Returns:

  • (Boolean)

    true if this call removed a registered task, else false.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/tina4/background.rb', line 135

def stop_task(task, timeout: 2.0)
  task.running = false
  # Identity, not equality: `tasks.delete(task)` uses `==`, which would take
  # out any OTHER handle that happens to compare equal. Only this exact one
  # goes, and we record whether it WAS registered so stop() is a truthful bool.
  was_registered = mutex.synchronize do
    present = tasks.any? { |registered| registered.equal?(task) }
    tasks.delete_if { |registered| registered.equal?(task) }
    present
  end

  thread = task.thread
  if thread
    thread.join(timeout) || thread.kill
    task.thread = nil
  end

  was_registered
end

.tasksObject

All registered task handles. Tests use this for introspection.



97
98
99
# File 'lib/tina4/background.rb', line 97

def tasks
  @tasks ||= []
end