monkrb 0.19.0 → 0.20.0
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/CHANGELOG.md +89 -0
- data/README.md +1 -1
- data/lib/monk/base.rb +20 -0
- data/lib/monk/jobs/adapters/pg.rb +3 -1
- data/lib/monk/live/policy.rb +39 -4
- data/lib/monk/live/session.rb +7 -0
- data/lib/monk/live.rb +5 -3
- data/lib/monk/persistence/errors.rb +32 -0
- data/lib/monk/persistence/pg.rb +92 -1
- data/lib/monk/persistence/pool.rb +513 -0
- data/lib/monk/persistence.rb +196 -7
- data/lib/monk/scaffold.rb +3 -1
- data/lib/monk/templates/auth/config/auth.rb +6 -0
- data/lib/monk/templates/base/bin/websocket_server +11 -2
- data/lib/monk/templates/live/bin/websocket_server +9 -1
- data/lib/monk/templates/postgres/db/migrate/.keep +0 -0
- data/lib/monk/version.rb +1 -1
- data/lib/monk/websocket/connection.rb +25 -6
- data/lib/monk/websocket/listeners.rb +24 -0
- data/lib/monk/websocket/pg_fanout.rb +143 -20
- data/lib/monk/websocket/redis_fanout.rb +78 -29
- data/lib/monk/websocket/registry.rb +24 -0
- data/lib/monk/websocket/server.rb +54 -15
- data/lib/monk/websocket.rb +2 -0
- data/lib/monk.rb +10 -0
- metadata +3 -1
|
@@ -38,6 +38,83 @@ module Monk
|
|
|
38
38
|
# already shareable.
|
|
39
39
|
CHANNEL_PREFIX = "monk:ws:".freeze
|
|
40
40
|
|
|
41
|
+
# The body of #listen!'s subscriber Ractor. A class method because
|
|
42
|
+
# the Ractor has no RedisFanout instance to call into. Reports :ok,
|
|
43
|
+
# or why it couldn't subscribe, on `ready`, then relays messages for
|
|
44
|
+
# the rest of the process's life.
|
|
45
|
+
#
|
|
46
|
+
# A dropped connection (a Redis restart, a failover) is resubscribed
|
|
47
|
+
# with backoff. Messages published while it was down are lost, so
|
|
48
|
+
# once subscribed again every open socket is closed
|
|
49
|
+
# (Listeners::MISSED_CODE), and each client reconnects and resyncs.
|
|
50
|
+
def self.subscribe(registry, url, own_origin, ready)
|
|
51
|
+
listening = false
|
|
52
|
+
delay = Listeners::RETRY_INITIAL
|
|
53
|
+
loop do
|
|
54
|
+
Redis.new(url: url).psubscribe("#{CHANNEL_PREFIX}*") do |on|
|
|
55
|
+
on.psubscribe do
|
|
56
|
+
delay = Listeners::RETRY_INITIAL
|
|
57
|
+
if listening
|
|
58
|
+
closed = registry.close_all(Listeners::MISSED_CODE, Listeners::MISSED_REASON)
|
|
59
|
+
log(:info, "subscribed again; closed #{closed} socket(s) so their pages resync")
|
|
60
|
+
else
|
|
61
|
+
listening = true
|
|
62
|
+
ready.send(:ok)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
on.pmessage { |_pattern, channel, envelope| relay(registry, own_origin, channel, envelope) }
|
|
66
|
+
end
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
# Not narrower: redis-rb lets redis-client's own
|
|
69
|
+
# RedisClient::ConnectionError out of psubscribe, not a
|
|
70
|
+
# Redis::BaseConnectionError. #relay rescues its own errors, so
|
|
71
|
+
# what gets here is the connection.
|
|
72
|
+
return ready.send("#{e.class}: #{e.message}") unless listening
|
|
73
|
+
|
|
74
|
+
log(:error, "subscriber connection failed (#{e.class}: #{e.message}); retrying in #{delay}s")
|
|
75
|
+
sleep delay
|
|
76
|
+
delay = Listeners.next_delay(delay)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# One message into this process's registry. Anything that goes wrong
|
|
81
|
+
# here drops this message only: escaping, it would end the
|
|
82
|
+
# subscriber Ractor, and with it every later broadcast, silently.
|
|
83
|
+
#
|
|
84
|
+
# Redis channel names are always Strings, but Registry's own keys
|
|
85
|
+
# are ordinary Hash keys the app chooses -- every existing caller
|
|
86
|
+
# (Registry's own tests, the websocket_server scaffold's :chat
|
|
87
|
+
# channel) uses a Symbol. #to_sym here is what makes
|
|
88
|
+
# registry.broadcast(:chat, ...) land on the same Hash key
|
|
89
|
+
# #register(:chat, port) used -- without it this silently
|
|
90
|
+
# broadcasts to an unrelated "chat" String key that nothing is ever
|
|
91
|
+
# registered under, and #broadcast still returns true either way
|
|
92
|
+
# (Registry#broadcast succeeds whether or not any port is registered
|
|
93
|
+
# for a key).
|
|
94
|
+
def self.relay(registry, own_origin, channel, envelope)
|
|
95
|
+
sender, payload = envelope.split("\0", 2)
|
|
96
|
+
raise ArgumentError, "no origin separator in the message" unless payload
|
|
97
|
+
return if sender == own_origin
|
|
98
|
+
|
|
99
|
+
registry.broadcast(channel.delete_prefix(CHANNEL_PREFIX).to_sym, payload)
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
log(:error, "dropped a message on #{channel}: #{e.class}: #{e.message}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.log(level, message) = Listeners.log(name, level, message)
|
|
105
|
+
private_class_method :relay, :log
|
|
106
|
+
|
|
107
|
+
# Closes the calling Ractor's publisher client (#publisher), if it
|
|
108
|
+
# opened one; the next broadcast in this Ractor reconnects.
|
|
109
|
+
# Server.serve runs it as each socket ends, as PgFanout's.
|
|
110
|
+
def self.disconnect_publisher
|
|
111
|
+
client = Ractor.current[:monk_redis_fanout_publisher]
|
|
112
|
+
return unless client
|
|
113
|
+
|
|
114
|
+
Ractor.current[:monk_redis_fanout_publisher] = nil
|
|
115
|
+
client.close
|
|
116
|
+
end
|
|
117
|
+
|
|
41
118
|
def initialize(registry, redis_url:)
|
|
42
119
|
@registry = registry
|
|
43
120
|
@redis_url = redis_url.dup.freeze
|
|
@@ -73,35 +150,7 @@ module Monk
|
|
|
73
150
|
# Redis client itself is built inside the block, never passed in --
|
|
74
151
|
# mirrors PG::Connection's per-Ractor pattern (persistence/pg.rb).
|
|
75
152
|
Ractor.new(@registry, @redis_url, @origin, ready) do |registry, url, origin, ready|
|
|
76
|
-
|
|
77
|
-
Redis.new(url: url).psubscribe("#{Monk::WebSocket::RedisFanout::CHANNEL_PREFIX}*") do |on|
|
|
78
|
-
on.psubscribe do
|
|
79
|
-
subscribed = true
|
|
80
|
-
ready.send(:ok)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
on.pmessage do |_pattern, channel, envelope|
|
|
84
|
-
sender, payload = envelope.split("\0", 2)
|
|
85
|
-
next if sender == origin
|
|
86
|
-
|
|
87
|
-
# Redis channel names are always Strings, but Registry's own
|
|
88
|
-
# keys are ordinary Hash keys the app chooses -- every
|
|
89
|
-
# existing caller (Registry's own tests, the websocket_server
|
|
90
|
-
# scaffold's :chat channel) uses a Symbol. #to_sym here is
|
|
91
|
-
# what makes registry.broadcast(:chat, ...) land on the same
|
|
92
|
-
# Hash key #register(:chat, port) used -- without it this
|
|
93
|
-
# silently broadcasts to an unrelated "chat" String key that
|
|
94
|
-
# nothing is ever registered under, and #broadcast still
|
|
95
|
-
# returns true either way (Registry#broadcast succeeds
|
|
96
|
-
# whether or not any port is registered for a key).
|
|
97
|
-
key = channel.delete_prefix(Monk::WebSocket::RedisFanout::CHANNEL_PREFIX).to_sym
|
|
98
|
-
registry.broadcast(key, payload)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
rescue StandardError => e
|
|
102
|
-
raise if subscribed
|
|
103
|
-
|
|
104
|
-
ready.send("#{e.class}: #{e.message}")
|
|
153
|
+
Monk::WebSocket::RedisFanout.subscribe(registry, url, origin, ready)
|
|
105
154
|
end
|
|
106
155
|
|
|
107
156
|
result = ready.receive
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
module Monk
|
|
2
2
|
module WebSocket
|
|
3
|
+
# Sent through a registered port instead of a payload: the socket
|
|
4
|
+
# behind it closes with this code and reason. Registry#close_all sends
|
|
5
|
+
# it; Connection's and Live::Session's relays act on it.
|
|
6
|
+
CloseRequest = Data.define(:code, :reason)
|
|
7
|
+
|
|
3
8
|
# A dedicated Ractor holding the live set of connection handles keyed by
|
|
4
9
|
# an app-assigned channel/subject -- mirrors StateRactor's shape
|
|
5
10
|
# (docs/design/ractor.md): the mutable Hash stays hidden inside this one
|
|
@@ -41,6 +46,17 @@ module Monk
|
|
|
41
46
|
true
|
|
42
47
|
when :count
|
|
43
48
|
keys[key].size
|
|
49
|
+
when :close_all
|
|
50
|
+
# Once per port: a Live page holds one port for all its
|
|
51
|
+
# topics. Entries stay until each connection's own cleanup
|
|
52
|
+
# unregisters them, as on any other close.
|
|
53
|
+
ports = keys.values.flatten.uniq
|
|
54
|
+
ports.each do |port|
|
|
55
|
+
port.send(arg)
|
|
56
|
+
rescue Ractor::ClosedError
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
ports.size
|
|
44
60
|
end
|
|
45
61
|
reply_port.send(result)
|
|
46
62
|
end
|
|
@@ -67,6 +83,14 @@ module Monk
|
|
|
67
83
|
ask(:count, key, nil)
|
|
68
84
|
end
|
|
69
85
|
|
|
86
|
+
# Closes every registered connection's socket with code and reason,
|
|
87
|
+
# so each client reconnects -- a fanout's subscriber does this after
|
|
88
|
+
# missing broadcasts, to make every page resync. Returns how many
|
|
89
|
+
# connections were asked to close.
|
|
90
|
+
def close_all(code, reason)
|
|
91
|
+
ask(:close_all, nil, Ractor.make_shareable(CloseRequest.new(code: code, reason: reason.to_s)))
|
|
92
|
+
end
|
|
93
|
+
|
|
70
94
|
# Nothing to listen to: every broadcast reaching this registry comes
|
|
71
95
|
# from this process. Here so a WS process calls #listen! the same
|
|
72
96
|
# way whether it holds a Registry or a RedisFanout/PgFanout around
|
|
@@ -40,6 +40,15 @@ module Monk
|
|
|
40
40
|
# under true, and to an anonymous connection that sends an Origin,
|
|
41
41
|
# since only a browser does.
|
|
42
42
|
#
|
|
43
|
+
# db_pool: (a pool name, docs/history/plan-pg-pool.md) runs those
|
|
44
|
+
# Monk::Auth.verify calls -- the handshake's and reverify_interval's
|
|
45
|
+
# -- in that Monk::Persistence::Pg pool instead of in the socket's
|
|
46
|
+
# own Ractor. Without it, each socket's Ractor opens a connection of
|
|
47
|
+
# its own on the first verify and holds it while the socket is open:
|
|
48
|
+
# one Postgres connection per open page. With it, the server holds
|
|
49
|
+
# the pool's. Requires authenticate:, and the pool started
|
|
50
|
+
# (start_pools!) before the server.
|
|
51
|
+
#
|
|
43
52
|
# max_payload_size: (bytes) -- see Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
44
53
|
# for what it guards against (gap 5). Unlike ping_interval:/
|
|
45
54
|
# reverify_interval:, always on: this is insurance against a
|
|
@@ -53,7 +62,7 @@ module Monk
|
|
|
53
62
|
|
|
54
63
|
def initialize(
|
|
55
64
|
port:, bind: "0.0.0.0", allowed_origins: nil, authenticate: false, ping_interval: nil,
|
|
56
|
-
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
65
|
+
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil
|
|
57
66
|
)
|
|
58
67
|
if ping_interval && !ping_interval.positive?
|
|
59
68
|
raise ArgumentError, "ping_interval must be positive, got #{ping_interval.inspect}"
|
|
@@ -70,6 +79,8 @@ module Monk
|
|
|
70
79
|
raise ArgumentError, "reverify_interval requires authenticate: true or :optional" unless authenticate
|
|
71
80
|
end
|
|
72
81
|
|
|
82
|
+
raise ArgumentError, "db_pool requires authenticate: true or :optional" if db_pool && !authenticate
|
|
83
|
+
|
|
73
84
|
unless max_payload_size.positive?
|
|
74
85
|
raise ArgumentError, "max_payload_size must be positive, got #{max_payload_size.inspect}"
|
|
75
86
|
end
|
|
@@ -91,12 +102,17 @@ module Monk
|
|
|
91
102
|
Monk.freeze!
|
|
92
103
|
end
|
|
93
104
|
|
|
105
|
+
# Raises now, not on the first connection, if the pool was never
|
|
106
|
+
# declared or isn't started in this process.
|
|
107
|
+
Monk::Persistence::Pg.pool(db_pool) if db_pool
|
|
108
|
+
|
|
94
109
|
@tcp_server = TCPServer.new(bind, port)
|
|
95
110
|
@allowed_origins = allowed_origins ? Ractor.make_shareable(allowed_origins.dup) : nil
|
|
96
111
|
@authenticate = authenticate
|
|
97
112
|
@ping_interval = ping_interval
|
|
98
113
|
@reverify_interval = reverify_interval
|
|
99
114
|
@max_payload_size = max_payload_size
|
|
115
|
+
@db_pool = db_pool
|
|
100
116
|
end
|
|
101
117
|
|
|
102
118
|
def port
|
|
@@ -117,12 +133,13 @@ module Monk
|
|
|
117
133
|
loop do
|
|
118
134
|
socket = @tcp_server.accept
|
|
119
135
|
ractor = Ractor.new(
|
|
120
|
-
shareable_block, @authenticate, @allowed_origins, @ping_interval, @reverify_interval, @max_payload_size
|
|
121
|
-
|
|
136
|
+
shareable_block, @authenticate, @allowed_origins, @ping_interval, @reverify_interval, @max_payload_size,
|
|
137
|
+
@db_pool
|
|
138
|
+
) do |blk, authenticate, origins, ping_interval, reverify_interval, max_payload_size, db_pool|
|
|
122
139
|
Monk::WebSocket::Server.serve(
|
|
123
140
|
Ractor.receive, blk,
|
|
124
|
-
authenticate: authenticate, allowed_origins: origins,
|
|
125
|
-
|
|
141
|
+
authenticate: authenticate, allowed_origins: origins, ping_interval: ping_interval,
|
|
142
|
+
reverify_interval: reverify_interval, max_payload_size: max_payload_size, db_pool: db_pool
|
|
126
143
|
)
|
|
127
144
|
end
|
|
128
145
|
ractor.send(socket, move: true)
|
|
@@ -152,7 +169,7 @@ module Monk
|
|
|
152
169
|
# TCPServer) is never Ractor-shareable and can't cross into here.
|
|
153
170
|
def self.serve(
|
|
154
171
|
socket, block, authenticate: false, allowed_origins: nil, ping_interval: nil, reverify_interval: nil,
|
|
155
|
-
max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
172
|
+
max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE, db_pool: nil
|
|
156
173
|
)
|
|
157
174
|
request = read_handshake_request(socket)
|
|
158
175
|
return socket.close unless request
|
|
@@ -167,12 +184,12 @@ module Monk
|
|
|
167
184
|
subject = nil
|
|
168
185
|
case authenticate
|
|
169
186
|
when true
|
|
170
|
-
subject = authenticate!(socket, headers, allowed_origins)
|
|
187
|
+
subject = authenticate!(socket, headers, allowed_origins, db_pool)
|
|
171
188
|
return unless subject
|
|
172
189
|
when :optional
|
|
173
190
|
return unless origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
174
191
|
|
|
175
|
-
subject = identify(headers)
|
|
192
|
+
subject = identify(headers, db_pool)
|
|
176
193
|
end
|
|
177
194
|
|
|
178
195
|
socket.write(Handshake.response_for(request))
|
|
@@ -181,7 +198,7 @@ module Monk
|
|
|
181
198
|
connection.start_heartbeat(ping_interval) if ping_interval
|
|
182
199
|
reverify_thread =
|
|
183
200
|
if reverify_interval && subject
|
|
184
|
-
start_reverify(connection, Handshake.credential_from(headers)[:token], reverify_interval)
|
|
201
|
+
start_reverify(connection, Handshake.credential_from(headers)[:token], reverify_interval, db_pool)
|
|
185
202
|
end
|
|
186
203
|
begin
|
|
187
204
|
block.call(connection)
|
|
@@ -201,7 +218,21 @@ module Monk
|
|
|
201
218
|
connection&.unsubscribe!
|
|
202
219
|
connection&.stop_heartbeat!
|
|
203
220
|
socket.close
|
|
221
|
+
disconnect_all
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Closes the connections this socket's Ractor opened: its database
|
|
225
|
+
# connection (verifying the session without db_pool:, or a block
|
|
226
|
+
# that queries) and a fan-out's publisher (a block that broadcasts).
|
|
227
|
+
# Left alone, each would stay open until the garbage collector
|
|
228
|
+
# happened to run, which a mostly idle server may not do for a long
|
|
229
|
+
# time. The fan-outs are opt-in, so each is closed only if loaded.
|
|
230
|
+
def self.disconnect_all
|
|
231
|
+
Monk::Persistence.disconnect_all
|
|
232
|
+
PgFanout.disconnect_publisher if defined?(PgFanout)
|
|
233
|
+
RedisFanout.disconnect_publisher if defined?(RedisFanout)
|
|
204
234
|
end
|
|
235
|
+
private_class_method :disconnect_all
|
|
205
236
|
|
|
206
237
|
# docs/history/plan-websocket.md steps 22-23: the Origin allowlist check runs
|
|
207
238
|
# before Monk::Auth.verify, and only for a cookie-derived
|
|
@@ -209,7 +240,7 @@ module Monk
|
|
|
209
240
|
# by construction (mirrors require_csrf!'s own Bearer exemption in
|
|
210
241
|
# docs/history/plan-auth.md). Returns the verified subject, or nil after writing
|
|
211
242
|
# the appropriate 403/401 response itself.
|
|
212
|
-
def self.authenticate!(socket, headers, allowed_origins)
|
|
243
|
+
def self.authenticate!(socket, headers, allowed_origins, db_pool)
|
|
213
244
|
credential = Handshake.credential_from(headers)
|
|
214
245
|
|
|
215
246
|
if credential&.fetch(:via) == :cookie && allowed_origins
|
|
@@ -220,7 +251,7 @@ module Monk
|
|
|
220
251
|
end
|
|
221
252
|
end
|
|
222
253
|
|
|
223
|
-
subject = credential &&
|
|
254
|
+
subject = credential && verify(credential[:token], db_pool)
|
|
224
255
|
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n") unless subject
|
|
225
256
|
subject
|
|
226
257
|
end
|
|
@@ -248,9 +279,9 @@ module Monk
|
|
|
248
279
|
|
|
249
280
|
# The subject of a valid session, or nil -- no credential, or one
|
|
250
281
|
# Monk::Auth no longer accepts, both mean anonymous under :optional.
|
|
251
|
-
def self.identify(headers)
|
|
282
|
+
def self.identify(headers, db_pool)
|
|
252
283
|
credential = Handshake.credential_from(headers)
|
|
253
|
-
credential &&
|
|
284
|
+
credential && verify(credential[:token], db_pool)
|
|
254
285
|
end
|
|
255
286
|
private_class_method :identify
|
|
256
287
|
|
|
@@ -263,11 +294,11 @@ module Monk
|
|
|
263
294
|
# own Thread rather than blocking the handler's own read loop,
|
|
264
295
|
# mirroring Connection#start_heartbeat's shape; killed from #serve's
|
|
265
296
|
# ensure the same way.
|
|
266
|
-
def self.start_reverify(connection, raw_token, interval)
|
|
297
|
+
def self.start_reverify(connection, raw_token, interval, db_pool)
|
|
267
298
|
Thread.new do
|
|
268
299
|
loop do
|
|
269
300
|
sleep interval
|
|
270
|
-
break unless
|
|
301
|
+
break unless verify(raw_token, db_pool)
|
|
271
302
|
end
|
|
272
303
|
connection.close(code: 1008, reason: "session no longer valid")
|
|
273
304
|
rescue IOError, Errno::EPIPE, Errno::ECONNRESET
|
|
@@ -278,6 +309,14 @@ module Monk
|
|
|
278
309
|
end
|
|
279
310
|
private_class_method :start_reverify
|
|
280
311
|
|
|
312
|
+
# Monk::Auth.verify, here or in the db_pool: pool (Server.new).
|
|
313
|
+
def self.verify(raw_token, db_pool)
|
|
314
|
+
return Monk::Auth.verify(raw_token) unless db_pool
|
|
315
|
+
|
|
316
|
+
Monk::Persistence::Pg.pool(db_pool).call(Monk::Auth, :verify, raw_token)
|
|
317
|
+
end
|
|
318
|
+
private_class_method :verify
|
|
319
|
+
|
|
281
320
|
def self.read_handshake_request(socket)
|
|
282
321
|
request = +""
|
|
283
322
|
until request.end_with?("\r\n\r\n")
|
data/lib/monk/websocket.rb
CHANGED
|
@@ -3,6 +3,8 @@ require_relative "errors"
|
|
|
3
3
|
# true) always raises this precise error, per ADR 0003, rather than a bare
|
|
4
4
|
# NameError when an app calls it without ever requiring "monk/auth" at all.
|
|
5
5
|
require_relative "auth/errors"
|
|
6
|
+
# Dependency-free too: Server.serve closes a socket Ractor's connections.
|
|
7
|
+
require_relative "persistence"
|
|
6
8
|
require_relative "websocket/errors"
|
|
7
9
|
require_relative "websocket/handshake"
|
|
8
10
|
require_relative "websocket/frame"
|
data/lib/monk.rb
CHANGED
|
@@ -24,6 +24,8 @@ module Monk
|
|
|
24
24
|
parts << " - auth=on" if defined?(Auth) && Auth.config
|
|
25
25
|
backends = persistence_backends
|
|
26
26
|
parts << " - persistence=#{backends.join(",")}" unless backends.empty?
|
|
27
|
+
pools = running_pools
|
|
28
|
+
parts << " - pools=#{pools.join(",")}" unless pools.empty?
|
|
27
29
|
parts << "\n"
|
|
28
30
|
|
|
29
31
|
$stdout.puts parts.join("\n")
|
|
@@ -42,4 +44,12 @@ module Monk
|
|
|
42
44
|
.filter_map { |hook| "#{hook.name.split("::").last.downcase}:#{hook.names.join(",")}" if hook.names.any? }
|
|
43
45
|
end
|
|
44
46
|
private_class_method :persistence_backends
|
|
47
|
+
|
|
48
|
+
# "name(size)" for every pool started in this process, by any backend.
|
|
49
|
+
def self.running_pools
|
|
50
|
+
freeze_hooks
|
|
51
|
+
.select { |hook| hook.is_a?(Module) && hook.singleton_class.include?(Persistence::Registry) }
|
|
52
|
+
.flat_map { |hook| hook.running_pools.map { |name, size| "#{name}(#{size})" } }
|
|
53
|
+
end
|
|
54
|
+
private_class_method :running_pools
|
|
45
55
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monkrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matteo Folin
|
|
@@ -212,6 +212,7 @@ files:
|
|
|
212
212
|
- lib/monk/persistence/pg/errors.rb
|
|
213
213
|
- lib/monk/persistence/pg/migrator.rb
|
|
214
214
|
- lib/monk/persistence/pg/model.rb
|
|
215
|
+
- lib/monk/persistence/pool.rb
|
|
215
216
|
- lib/monk/scaffold.rb
|
|
216
217
|
- lib/monk/settings.rb
|
|
217
218
|
- lib/monk/state_ractor.rb
|
|
@@ -261,6 +262,7 @@ files:
|
|
|
261
262
|
- lib/monk/templates/postgres/bin/migrate
|
|
262
263
|
- lib/monk/templates/postgres/bin/setup_db
|
|
263
264
|
- lib/monk/templates/postgres/config/persistence.rb
|
|
265
|
+
- lib/monk/templates/postgres/db/migrate/.keep
|
|
264
266
|
- lib/monk/templates/redis/Gemfile.extra
|
|
265
267
|
- lib/monk/version.rb
|
|
266
268
|
- lib/monk/views.rb
|