Class: ConvertSdk::BackgroundTimer

Inherits:
Object
  • Object
show all
Defined in:
lib/convert_sdk/background_timer.rb

Overview

The SDK's single background-thread primitive — the ONLY +Thread.new+ site in the gem (architecture Decision 6, thread/fork boundary). One class, two future instances: the config-refresh timer (Story 2.7) and the queue-flush timer (Story 4.2). It is never subclassed.

A timer wraps an +interval+, a tick +block+, and a Mutex'd lifecycle state machine. #start lazily spawns a single loop thread (NFR4 — no threads until first use); #stop signals it to exit and joins it; #mark_dead clears the state WITHOUT joining (the fork re-arm hook — a thread reference copied into a forked child is dead and joining it can hang), so the next #start transparently re-arms a fresh thread.

The loop sleeps for +interval+ on a Thread::ConditionVariable (interruptible — #stop is responsive instead of waiting out a bare +sleep+), then runs the block. Each tick is wrapped in +rescue StandardError+ and logged (never +rescue Exception+): a raising tick is logged and the loop continues — an exception must never silently kill a timer thread (never-crash contract).

A +nil+ or zero +interval+ is the timer-off mode: #start is a guarded no-op and no thread is ever created.

Instance Method Summary collapse

Constructor Details

#initialize(interval:, log_manager:, name:) { ... } ⇒ BackgroundTimer

Returns a new instance of BackgroundTimer.

Parameters:

  • interval (Numeric, nil)

    seconds between ticks; +nil+/zero disables the timer (no thread is ever started).

  • log_manager (ConvertSdk::LogManager)

    sink for debug (thread creation) and error (raising tick) lines.

  • name (String)

    identifies the timer in log lines (e.g. "refresh").

Yields:

  • the tick block, invoked once per interval.



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/convert_sdk/background_timer.rb', line 33

def initialize(interval:, log_manager:, name:, &block)
  @interval = interval
  @log_manager = log_manager
  @name = name
  @block = block
  # Thread safety: @thread and @running are guarded by @state_mutex; the
  # condition variable wakes the loop's interruptible sleep on #stop.
  @state_mutex = Thread::Mutex.new
  @sleep_cv = Thread::ConditionVariable.new
  @thread = nil
  @running = false
end

Instance Method Details

#alive?Boolean

Returns whether the loop thread is currently running.

Returns:

  • (Boolean)

    whether the loop thread is currently running.



93
94
95
# File 'lib/convert_sdk/background_timer.rb', line 93

def alive?
  @state_mutex.synchronize { @running && !@thread.nil? }
end

#mark_deadvoid

This method returns an undefined value.

Fork re-arm hook: clear the lifecycle state WITHOUT joining the thread. The thread reference is stale in a forked child (fork copies only the calling thread), so joining it can hang. The next #start creates a fresh thread.



85
86
87
88
89
90
# File 'lib/convert_sdk/background_timer.rb', line 85

def mark_dead
  @state_mutex.synchronize do
    @running = false
    @thread = nil
  end
end

#startvoid

This method returns an undefined value.

Start the loop thread if not already running. Idempotent: concurrent calls and repeat calls produce exactly one thread. Re-arms transparently after #mark_dead. A +nil+/zero interval is a no-op (timer-off mode).



50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/convert_sdk/background_timer.rb', line 50

def start
  @state_mutex.synchronize do
    # Timer-off guard: a nil/zero interval never starts (and narrows the
    # interval to a concrete positive Numeric for the loop's sleep).
    interval = @interval
    return if interval.nil? || interval <= 0
    return if @running

    @running = true
    @thread = Thread.new { run_loop(interval.to_f) }
    @log_manager.debug("BackgroundTimer#start: started ##{@name} (interval=#{@interval}s)")
  end
end

#stopvoid

This method returns an undefined value.

Signal the loop to exit and join the thread. Idempotent: a no-op when not running (including after #mark_dead).



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/convert_sdk/background_timer.rb', line 67

def stop
  thread = nil #: Thread?
  @state_mutex.synchronize do
    return unless @running

    @running = false
    @sleep_cv.broadcast
    thread = @thread
    @thread = nil
  end
  thread&.join
end