activematrix 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +96 -28
- data/app/models/active_matrix/agent.rb +36 -1
- data/app/models/active_matrix/agent_store.rb +29 -0
- data/app/models/active_matrix/application_record.rb +8 -0
- data/app/models/active_matrix/chat_session.rb +29 -0
- data/app/models/active_matrix/knowledge_base.rb +26 -0
- data/exe/activematrix +7 -0
- data/lib/active_matrix/agent_manager.rb +160 -121
- data/lib/active_matrix/agent_registry.rb +25 -21
- data/lib/active_matrix/api.rb +8 -2
- data/lib/active_matrix/async_query.rb +58 -0
- data/lib/active_matrix/bot/base.rb +3 -3
- data/lib/active_matrix/bot/builtin_commands.rb +188 -0
- data/lib/active_matrix/bot/command_parser.rb +175 -0
- data/lib/active_matrix/cli.rb +273 -0
- data/lib/active_matrix/client.rb +21 -6
- data/lib/active_matrix/client_pool.rb +38 -27
- data/lib/active_matrix/daemon/probe_server.rb +118 -0
- data/lib/active_matrix/daemon/signal_handler.rb +156 -0
- data/lib/active_matrix/daemon/worker.rb +109 -0
- data/lib/active_matrix/daemon.rb +236 -0
- data/lib/active_matrix/engine.rb +7 -3
- data/lib/active_matrix/errors.rb +1 -1
- data/lib/active_matrix/event_router.rb +61 -49
- data/lib/active_matrix/events.rb +1 -0
- data/lib/active_matrix/instrumentation.rb +148 -0
- data/lib/active_matrix/memory/agent_memory.rb +7 -21
- data/lib/active_matrix/memory/conversation_memory.rb +4 -20
- data/lib/active_matrix/memory/global_memory.rb +15 -30
- data/lib/active_matrix/message_dispatcher.rb +197 -0
- data/lib/active_matrix/metrics.rb +424 -0
- data/lib/active_matrix/presence_manager.rb +181 -0
- data/lib/active_matrix/telemetry.rb +134 -0
- data/lib/active_matrix/version.rb +1 -1
- data/lib/active_matrix.rb +12 -2
- data/lib/generators/active_matrix/install/install_generator.rb +3 -15
- data/lib/generators/active_matrix/install/templates/README +5 -2
- data/lib/generators/active_matrix/install/templates/active_matrix.yml +32 -0
- metadata +142 -45
- data/lib/active_matrix/protocols/cs/message_relationships.rb +0 -318
- data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +0 -17
- data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +0 -21
- data/lib/generators/active_matrix/install/templates/create_global_memories.rb +0 -20
- data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +0 -26
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'singleton'
|
|
4
|
-
require '
|
|
4
|
+
require 'async'
|
|
5
|
+
require 'async/semaphore'
|
|
6
|
+
require 'async/condition'
|
|
5
7
|
|
|
6
8
|
module ActiveMatrix
|
|
7
|
-
# Manages
|
|
9
|
+
# Manages Matrix client connections per homeserver with rate limiting.
|
|
10
|
+
#
|
|
11
|
+
# NOTE: Despite the name, this is not a traditional connection pool.
|
|
12
|
+
# Each agent gets a dedicated long-lived client. The "pool" provides:
|
|
13
|
+
# - Semaphore-based rate limiting on client creation per homeserver
|
|
14
|
+
# - Tracking of active clients for health monitoring
|
|
15
|
+
#
|
|
16
|
+
# Clients are NOT returned to the pool after use - they remain active
|
|
17
|
+
# for the agent's lifetime. The semaphore prevents too many agents
|
|
18
|
+
# from connecting to a single homeserver simultaneously.
|
|
19
|
+
#
|
|
8
20
|
class ClientPool
|
|
9
21
|
include Singleton
|
|
10
22
|
include ActiveMatrix::Logging
|
|
11
23
|
|
|
12
24
|
def initialize
|
|
13
|
-
@pools =
|
|
25
|
+
@pools = {}
|
|
14
26
|
@config = ActiveMatrix.config
|
|
15
27
|
@mutex = Mutex.new
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
# Get or create a client for a homeserver
|
|
19
31
|
def get_client(homeserver, **)
|
|
20
|
-
@mutex.synchronize
|
|
21
|
-
|
|
22
|
-
pool.checkout(**)
|
|
23
|
-
end
|
|
32
|
+
pool = @mutex.synchronize { get_or_create_pool(homeserver) }
|
|
33
|
+
pool.checkout(**)
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
# Return a client to the pool
|
|
@@ -78,32 +88,37 @@ module ActiveMatrix
|
|
|
78
88
|
@available = []
|
|
79
89
|
@in_use = {}
|
|
80
90
|
@mutex = Mutex.new
|
|
81
|
-
@
|
|
91
|
+
@semaphore = Async::Semaphore.new(max_size)
|
|
82
92
|
end
|
|
83
93
|
|
|
84
94
|
def checkout(**)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
client = find_available_client
|
|
95
|
+
# Acquire semaphore temporarily to rate-limit client creation
|
|
96
|
+
@semaphore.acquire
|
|
88
97
|
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
client = @mutex.synchronize do
|
|
99
|
+
# Try to find an available client
|
|
100
|
+
existing = find_available_client
|
|
91
101
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
if existing
|
|
103
|
+
@available.delete(existing)
|
|
104
|
+
existing
|
|
105
|
+
else
|
|
106
|
+
create_client(**)
|
|
96
107
|
end
|
|
108
|
+
end
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
# Track as in use
|
|
111
|
+
@mutex.synchronize do
|
|
100
112
|
@in_use[client.object_id] = {
|
|
101
113
|
client: client,
|
|
102
114
|
checked_out_at: Time.current
|
|
103
115
|
}
|
|
104
|
-
|
|
105
|
-
client
|
|
106
116
|
end
|
|
117
|
+
|
|
118
|
+
client
|
|
119
|
+
ensure
|
|
120
|
+
# Release immediately - semaphore only rate-limits creation, not usage
|
|
121
|
+
@semaphore.release
|
|
107
122
|
end
|
|
108
123
|
|
|
109
124
|
def checkin(client)
|
|
@@ -115,12 +130,8 @@ module ActiveMatrix
|
|
|
115
130
|
if client_valid?(client)
|
|
116
131
|
@available << client
|
|
117
132
|
else
|
|
118
|
-
# Client is no longer valid, don't return to pool
|
|
119
133
|
logger.debug "Discarding invalid client for #{@homeserver}"
|
|
120
134
|
end
|
|
121
|
-
|
|
122
|
-
# Signal waiting threads
|
|
123
|
-
@condition.signal
|
|
124
135
|
end
|
|
125
136
|
end
|
|
126
137
|
|
|
@@ -140,7 +151,7 @@ module ActiveMatrix
|
|
|
140
151
|
@mutex.synchronize do
|
|
141
152
|
# Stop all clients
|
|
142
153
|
(@available + @in_use.values.map { |e| e[:client] }).each do |client|
|
|
143
|
-
client.
|
|
154
|
+
client.stop_listener if client.listening?
|
|
144
155
|
client.logout if client.logged_in?
|
|
145
156
|
rescue StandardError => e
|
|
146
157
|
logger.error "Error cleaning up client: #{e.message}"
|
|
@@ -158,7 +169,7 @@ module ActiveMatrix
|
|
|
158
169
|
@available.select! { |client| client_valid?(client) }
|
|
159
170
|
|
|
160
171
|
# Return first available
|
|
161
|
-
@available.
|
|
172
|
+
@available.shift
|
|
162
173
|
end
|
|
163
174
|
|
|
164
175
|
def create_client(**)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'async'
|
|
4
|
+
require 'async/http'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module ActiveMatrix
|
|
8
|
+
class Daemon
|
|
9
|
+
# Lightweight HTTP health probe server using Async::HTTP
|
|
10
|
+
#
|
|
11
|
+
# Endpoints:
|
|
12
|
+
# - GET /health - Returns 200 if healthy, 503 if shutting down
|
|
13
|
+
# - GET /status - Returns detailed JSON status
|
|
14
|
+
# - GET /metrics - Prometheus-compatible metrics
|
|
15
|
+
#
|
|
16
|
+
class ProbeServer
|
|
17
|
+
attr_reader :host, :port, :daemon
|
|
18
|
+
|
|
19
|
+
def initialize(host:, port:, daemon:)
|
|
20
|
+
@host = host
|
|
21
|
+
@port = port
|
|
22
|
+
@daemon = daemon
|
|
23
|
+
@thread = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start
|
|
27
|
+
@thread = Thread.new do
|
|
28
|
+
Sync do
|
|
29
|
+
endpoint = Async::HTTP::Endpoint.parse("http://#{host}:#{port}")
|
|
30
|
+
|
|
31
|
+
@server = Async::HTTP::Server.for(endpoint) do |request|
|
|
32
|
+
handle_request(request)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
logger.info "Probe server listening on #{host}:#{port}"
|
|
36
|
+
@server.run
|
|
37
|
+
end
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
logger.error "Probe server error: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def stop
|
|
44
|
+
@thread&.kill
|
|
45
|
+
@thread&.join(1)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def handle_request(request)
|
|
51
|
+
path = request.path
|
|
52
|
+
|
|
53
|
+
case path
|
|
54
|
+
when '/health'
|
|
55
|
+
health_response
|
|
56
|
+
when '/status'
|
|
57
|
+
status_response
|
|
58
|
+
when '/metrics'
|
|
59
|
+
metrics_response
|
|
60
|
+
else
|
|
61
|
+
not_found_response
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def health_response
|
|
66
|
+
status = daemon.status
|
|
67
|
+
code = status[:status] == 'ok' ? 200 : 503
|
|
68
|
+
|
|
69
|
+
::Protocol::HTTP::Response[code, { 'content-type' => 'text/plain' }, [status[:status]]]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def status_response
|
|
73
|
+
body = JSON.pretty_generate(daemon.status)
|
|
74
|
+
|
|
75
|
+
::Protocol::HTTP::Response[200, { 'content-type' => 'application/json' }, [body]]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def metrics_response
|
|
79
|
+
status = daemon.status
|
|
80
|
+
|
|
81
|
+
lines = [
|
|
82
|
+
'# HELP activematrix_up Is the daemon running',
|
|
83
|
+
'# TYPE activematrix_up gauge',
|
|
84
|
+
"activematrix_up #{status[:status] == 'ok' ? 1 : 0}",
|
|
85
|
+
'',
|
|
86
|
+
'# HELP activematrix_uptime_seconds Daemon uptime in seconds',
|
|
87
|
+
'# TYPE activematrix_uptime_seconds counter',
|
|
88
|
+
"activematrix_uptime_seconds #{status[:uptime]}",
|
|
89
|
+
'',
|
|
90
|
+
'# HELP activematrix_workers Number of worker processes',
|
|
91
|
+
'# TYPE activematrix_workers gauge',
|
|
92
|
+
"activematrix_workers #{status[:workers]}",
|
|
93
|
+
'',
|
|
94
|
+
'# HELP activematrix_agents_total Total number of agents',
|
|
95
|
+
'# TYPE activematrix_agents_total gauge',
|
|
96
|
+
"activematrix_agents_total #{status.dig(:agents, :total) || 0}",
|
|
97
|
+
'',
|
|
98
|
+
'# HELP activematrix_agents Agent count by state',
|
|
99
|
+
'# TYPE activematrix_agents gauge',
|
|
100
|
+
"activematrix_agents{state=\"online\"} #{status.dig(:agents, :online) || 0}",
|
|
101
|
+
"activematrix_agents{state=\"connecting\"} #{status.dig(:agents, :connecting) || 0}",
|
|
102
|
+
"activematrix_agents{state=\"error\"} #{status.dig(:agents, :error) || 0}",
|
|
103
|
+
"activematrix_agents{state=\"offline\"} #{status.dig(:agents, :offline) || 0}"
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
::Protocol::HTTP::Response[200, { 'content-type' => 'text/plain; version=0.0.4' }, [lines.join("\n")]]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def not_found_response
|
|
110
|
+
::Protocol::HTTP::Response[404, { 'content-type' => 'text/plain' }, ['Not Found']]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def logger
|
|
114
|
+
ActiveMatrix.logger
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveMatrix
|
|
4
|
+
class Daemon
|
|
5
|
+
# Handles Unix signals for the daemon coordinator
|
|
6
|
+
#
|
|
7
|
+
# Signals:
|
|
8
|
+
# - TERM/INT: Graceful shutdown
|
|
9
|
+
# - HUP: Reload configuration and restart agents
|
|
10
|
+
# - USR1: Log rotation (reopen log files)
|
|
11
|
+
# - USR2: Dump debug information
|
|
12
|
+
#
|
|
13
|
+
class SignalHandler
|
|
14
|
+
SIGNALS = %w[TERM INT HUP USR1 USR2].freeze
|
|
15
|
+
|
|
16
|
+
attr_reader :daemon
|
|
17
|
+
|
|
18
|
+
def initialize(daemon)
|
|
19
|
+
@daemon = daemon
|
|
20
|
+
@self_pipe_reader, @self_pipe_writer = IO.pipe
|
|
21
|
+
@old_handlers = {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def install
|
|
25
|
+
SIGNALS.each do |signal|
|
|
26
|
+
install_handler(signal)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Start signal processing thread
|
|
30
|
+
start_processor
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def uninstall
|
|
34
|
+
SIGNALS.each do |signal|
|
|
35
|
+
restore_handler(signal)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@self_pipe_writer.close
|
|
39
|
+
@self_pipe_reader.close
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def install_handler(signal)
|
|
45
|
+
@old_handlers[signal] = Signal.trap(signal) do
|
|
46
|
+
# Write signal to pipe (non-blocking, safe in signal handler)
|
|
47
|
+
@self_pipe_writer.write_nonblock("#{signal}\n")
|
|
48
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
49
|
+
# Pipe full, signal will be coalesced
|
|
50
|
+
end
|
|
51
|
+
rescue ArgumentError
|
|
52
|
+
# Signal not supported on this platform
|
|
53
|
+
logger.debug "Signal #{signal} not supported"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def restore_handler(signal)
|
|
57
|
+
return unless @old_handlers.key?(signal)
|
|
58
|
+
|
|
59
|
+
Signal.trap(signal, @old_handlers[signal])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def start_processor
|
|
63
|
+
Thread.new do
|
|
64
|
+
Thread.current.name = 'activematrix-signal-processor'
|
|
65
|
+
|
|
66
|
+
loop do
|
|
67
|
+
# rubocop:disable Lint/IncompatibleIoSelectWithFiberScheduler
|
|
68
|
+
ready = IO.select([@self_pipe_reader], nil, nil, 1)
|
|
69
|
+
# rubocop:enable Lint/IncompatibleIoSelectWithFiberScheduler
|
|
70
|
+
next unless ready
|
|
71
|
+
|
|
72
|
+
signal = @self_pipe_reader.gets&.strip
|
|
73
|
+
next unless signal
|
|
74
|
+
|
|
75
|
+
handle_signal(signal)
|
|
76
|
+
rescue IOError
|
|
77
|
+
break
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def handle_signal(signal)
|
|
83
|
+
logger.info "Received signal: #{signal}"
|
|
84
|
+
|
|
85
|
+
case signal
|
|
86
|
+
when 'TERM', 'INT'
|
|
87
|
+
handle_shutdown
|
|
88
|
+
when 'HUP'
|
|
89
|
+
handle_reload
|
|
90
|
+
when 'USR1'
|
|
91
|
+
handle_log_rotation
|
|
92
|
+
when 'USR2'
|
|
93
|
+
handle_debug_dump
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def handle_shutdown
|
|
98
|
+
logger.info 'Initiating graceful shutdown...'
|
|
99
|
+
daemon.shutdown
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def handle_reload
|
|
103
|
+
logger.info 'Reloading agent configuration...'
|
|
104
|
+
# TODO: Implement reload
|
|
105
|
+
# 1. Query for new/removed agents
|
|
106
|
+
# 2. Send HUP to workers for them to reload
|
|
107
|
+
daemon.worker_pids.each do |pid|
|
|
108
|
+
Process.kill('HUP', pid)
|
|
109
|
+
rescue Errno::ESRCH
|
|
110
|
+
# Worker already dead
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def handle_log_rotation
|
|
115
|
+
logger.info 'Rotating log files...'
|
|
116
|
+
|
|
117
|
+
# Reopen stdout/stderr if they're files
|
|
118
|
+
if $stdout.respond_to?(:path) && $stdout.path
|
|
119
|
+
$stdout.reopen($stdout.path, 'a')
|
|
120
|
+
$stdout.sync = true
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if $stderr.respond_to?(:path) && $stderr.path
|
|
124
|
+
$stderr.reopen($stderr.path, 'a')
|
|
125
|
+
$stderr.sync = true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Signal workers to rotate their logs too
|
|
129
|
+
daemon.worker_pids.each do |pid|
|
|
130
|
+
Process.kill('USR1', pid)
|
|
131
|
+
rescue Errno::ESRCH
|
|
132
|
+
# Worker already dead
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def handle_debug_dump
|
|
137
|
+
logger.info 'Dumping debug information...'
|
|
138
|
+
|
|
139
|
+
# Dump current state
|
|
140
|
+
status = daemon.status
|
|
141
|
+
logger.info "Status: #{status.inspect}"
|
|
142
|
+
|
|
143
|
+
# Dump thread backtraces
|
|
144
|
+
Thread.list.each do |thread|
|
|
145
|
+
logger.info "Thread: #{thread.name || thread.object_id}"
|
|
146
|
+
logger.info thread.backtrace&.join("\n") || '(no backtrace)'
|
|
147
|
+
logger.info '---'
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def logger
|
|
152
|
+
ActiveMatrix.logger
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveMatrix
|
|
4
|
+
class Daemon
|
|
5
|
+
# Worker process that runs a subset of agents
|
|
6
|
+
#
|
|
7
|
+
# Each worker is a forked child process that:
|
|
8
|
+
# - Initializes its own AgentManager
|
|
9
|
+
# - Runs only assigned agents (by ID)
|
|
10
|
+
# - Handles signals for graceful shutdown
|
|
11
|
+
#
|
|
12
|
+
class Worker
|
|
13
|
+
attr_reader :index, :agent_ids
|
|
14
|
+
|
|
15
|
+
def initialize(index:, agent_ids:)
|
|
16
|
+
@index = index
|
|
17
|
+
@agent_ids = agent_ids
|
|
18
|
+
@running = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
@running = true
|
|
23
|
+
|
|
24
|
+
set_process_name
|
|
25
|
+
install_signal_handlers
|
|
26
|
+
reconnect_database
|
|
27
|
+
|
|
28
|
+
logger.info "Worker #{index} starting with agents: #{agent_ids.join(', ')}"
|
|
29
|
+
|
|
30
|
+
run_agents
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
logger.error "Worker #{index} crashed: #{e.message}"
|
|
33
|
+
logger.error e.backtrace.join("\n")
|
|
34
|
+
raise
|
|
35
|
+
ensure
|
|
36
|
+
logger.info "Worker #{index} exiting"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def set_process_name
|
|
42
|
+
Process.setproctitle("activematrix[#{index}]: #{agent_ids.size} agents")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def install_signal_handlers
|
|
46
|
+
Signal.trap('TERM') do
|
|
47
|
+
@running = false
|
|
48
|
+
AgentManager.instance.stop_all
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Signal.trap('INT') do
|
|
52
|
+
@running = false
|
|
53
|
+
AgentManager.instance.stop_all
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
Signal.trap('HUP') do
|
|
57
|
+
# Reload - restart agents with new config
|
|
58
|
+
logger.info "Worker #{index} received HUP, reloading..."
|
|
59
|
+
# For now, just log. Full reload would require:
|
|
60
|
+
# 1. Stop all agents
|
|
61
|
+
# 2. Re-query DB for agent list
|
|
62
|
+
# 3. Start new agents
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Signal.trap('USR1') do
|
|
66
|
+
# Log rotation
|
|
67
|
+
if $stdout.respond_to?(:path) && $stdout.path
|
|
68
|
+
$stdout.reopen($stdout.path, 'a')
|
|
69
|
+
$stdout.sync = true
|
|
70
|
+
end
|
|
71
|
+
if $stderr.respond_to?(:path) && $stderr.path
|
|
72
|
+
$stderr.reopen($stderr.path, 'a')
|
|
73
|
+
$stderr.sync = true
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def reconnect_database
|
|
79
|
+
# After fork, we need fresh database connections
|
|
80
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!
|
|
81
|
+
ActiveRecord::Base.establish_connection
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def run_agents
|
|
85
|
+
manager = AgentManager.instance
|
|
86
|
+
|
|
87
|
+
# Install signal handlers for the manager
|
|
88
|
+
manager.install_signal_handlers!
|
|
89
|
+
|
|
90
|
+
# Load only our assigned agents
|
|
91
|
+
agents = ActiveMatrix::Agent.where(id: agent_ids)
|
|
92
|
+
|
|
93
|
+
if agents.empty?
|
|
94
|
+
logger.warn "Worker #{index} has no agents to run"
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Start the manager with only our agents
|
|
99
|
+
Sync do
|
|
100
|
+
manager.start_agents(agents)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def logger
|
|
105
|
+
ActiveMatrix.logger
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|