Class: Tina4::ServiceRunner
- Inherits:
-
Object
- Object
- Tina4::ServiceRunner
- Defined in:
- lib/tina4/service_runner.rb
Overview
In-process service runner using Ruby threads. Supports cron schedules, simple intervals, and daemon (self-looping) handlers.
Tina4::ServiceRunner.register("cleanup", timing: "*/5 * * * *") { |ctx| ... }
Tina4::ServiceRunner.register("poller", interval: 10) { |ctx| ... }
Tina4::ServiceRunner.register("worker", daemon: true) { |ctx| while ctx.running; ...; end }
Tina4::ServiceRunner.start
Class Method Summary collapse
-
.clear! ⇒ Object
Remove all registrations and stop all services.
-
.discover(service_dir = nil) ⇒ Object
Auto-discover service files from a directory.
-
.is_running(name) ⇒ Object
Check if a specific service is currently running.
-
.list ⇒ Object
List all registered services with their status.
-
.match_cron?(pattern, time = Time.now) ⇒ Boolean
Check whether a 5-field cron pattern matches a given Time.
-
.register(name, handler = nil, options = {}, &block) ⇒ Object
Register a named service with options and a handler block (or callable).
-
.start(name = nil) ⇒ Object
Start all registered services, or a specific one by name.
-
.stop(name = nil) ⇒ Object
Stop all running services, or a specific one by name.
Class Method Details
.clear! ⇒ Object
Remove all registrations and stop all services. Useful for tests.
142 143 144 145 146 147 148 149 |
# File 'lib/tina4/service_runner.rb', line 142 def clear! stop @mutex.synchronize do @registry.clear @threads.clear @contexts.clear end end |
.discover(service_dir = nil) ⇒ Object
Auto-discover service files from a directory. Each file should call Tina4.service or Tina4::ServiceRunner.register.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/tina4/service_runner.rb', line 53 def discover(service_dir = nil) service_dir ||= ENV["TINA4_SERVICE_DIR"] || "src/services" full_dir = File.(service_dir, Tina4.root_dir || Dir.pwd) return unless Dir.exist?(full_dir) Dir.glob(File.join(full_dir, "**/*.rb")).sort.each do |file| begin load file Tina4::Log.debug("Service discovered: #{file}") rescue => e Tina4::Log.error("Failed to load service #{file}: #{e.}") end end end |
.is_running(name) ⇒ Object
Check if a specific service is currently running.
136 137 138 139 |
# File 'lib/tina4/service_runner.rb', line 136 def is_running(name) ctx = @contexts[name.to_s] ctx&.running == true && @threads[name.to_s]&.alive? == true end |
.list ⇒ Object
List all registered services with their status.
122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/tina4/service_runner.rb', line 122 def list @registry.map do |name, entry| ctx = @contexts[name] { name: name, options: entry[:options], running: ctx&.running == true && @threads[name]&.alive? == true, last_run: ctx&.last_run, error_count: ctx&.error_count || 0 } end end |
.match_cron?(pattern, time = Time.now) ⇒ Boolean
Check whether a 5-field cron pattern matches a given Time. Fields: minute hour day_of_month month day_of_week
155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/tina4/service_runner.rb', line 155 def match_cron?(pattern, time = Time.now) fields = pattern.strip.split(/\s+/) return false unless fields.length == 5 minute, hour, dom, month, dow = fields parse_cron_field(minute, time.min, 59) && parse_cron_field(hour, time.hour, 23) && parse_cron_field(dom, time.day, 31) && parse_cron_field(month, time.month, 12) && parse_cron_field(dow, time.wday, 7) end |
.register(name, handler = nil, options = {}, &block) ⇒ Object
Register a named service with options and a handler block (or callable).
Options:
timing: cron expression, e.g. "*/5 * * * *"
interval: run every N seconds
daemon: boolean — handler manages its own loop
max_retries: restart limit on crash (default 3)
41 42 43 44 45 46 47 48 49 |
# File 'lib/tina4/service_runner.rb', line 41 def register(name, handler = nil, = {}, &block) callable = handler || block raise ArgumentError, "provide a handler or block for service '#{name}'" unless callable @mutex.synchronize do @registry[name.to_s] = { handler: callable, options: } end Tina4::Log.debug("Service registered: #{name}") end |
.start(name = nil) ⇒ Object
Start all registered services, or a specific one by name.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/tina4/service_runner.rb', line 71 def start(name = nil) targets = if name entry = @registry[name.to_s] raise KeyError, "service '#{name}' not registered" unless entry { name.to_s => entry } else @registry.dup end targets.each do |svc_name, entry| next if @threads[svc_name]&.alive? ctx = ServiceContext.new(svc_name) @mutex.synchronize { @contexts[svc_name] = ctx } thread = Thread.new { run_loop(svc_name, entry[:handler], entry[:options], ctx) } thread.name = "tina4-service-#{svc_name}" if thread.respond_to?(:name=) @mutex.synchronize { @threads[svc_name] = thread } Tina4::Log.info("Service started: #{svc_name}") end end |
.stop(name = nil) ⇒ Object
Stop all running services, or a specific one by name.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/tina4/service_runner.rb', line 95 def stop(name = nil) targets = if name ctx = @contexts[name.to_s] ctx ? { name.to_s => ctx } : {} else @contexts.dup end targets.each do |svc_name, ctx| ctx.running = false Tina4::Log.info("Service stopping: #{svc_name}") end # Join threads with a timeout so we don't hang forever targets.each_key do |svc_name| thread = @threads[svc_name] next unless thread thread.join(5) @mutex.synchronize do @threads.delete(svc_name) @contexts.delete(svc_name) end end end |