volcano-sdk 0.30.0 → 0.32.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/lib/volcano/auth.rb +4 -48
- data/lib/volcano/auth_session_construction.rb +67 -0
- data/lib/volcano/auth_sign_up.rb +2 -0
- data/lib/volcano/realtime/broadcast_delivery.rb +26 -2
- data/lib/volcano/realtime/channel_callbacks.rb +19 -14
- data/lib/volcano/realtime/channel_lifecycle.rb +9 -9
- data/lib/volcano/realtime/connection.rb +9 -5
- data/lib/volcano/realtime/connection_callbacks.rb +5 -1
- data/lib/volcano/realtime/lifecycle.rb +2 -1
- data/lib/volcano/realtime/postgres_batch.rb +11 -3
- data/lib/volcano/realtime/postgres_changes.rb +31 -16
- data/lib/volcano/realtime/postgres_database.rb +14 -1
- data/lib/volcano/realtime/postgres_delivery.rb +6 -1
- data/lib/volcano/realtime/postgres_expansion.rb +10 -4
- data/lib/volcano/realtime/presence.rb +18 -4
- data/lib/volcano/realtime/presence_state.rb +23 -6
- data/lib/volcano/realtime/protocol.rb +14 -9
- data/lib/volcano/realtime/protocol_dispatch.rb +25 -7
- data/lib/volcano/realtime/protocol_io.rb +8 -4
- data/lib/volcano/realtime/protocol_lifecycle.rb +2 -0
- data/lib/volcano/realtime/protocol_publication_dispatch.rb +12 -10
- data/lib/volcano/realtime/protocol_recovery.rb +7 -3
- data/lib/volcano/realtime/protocol_recovery_dispatch.rb +2 -0
- data/lib/volcano/realtime/reconnect.rb +4 -1
- data/lib/volcano/realtime.rb +122 -99
- data/lib/volcano/storage.rb +18 -34
- data/lib/volcano/storage_models.rb +18 -5
- data/lib/volcano/storage_mutations.rb +8 -3
- data/lib/volcano/storage_object_response.rb +88 -0
- data/lib/volcano/storage_response.rb +92 -0
- data/lib/volcano/storage_resumable_upload.rb +13 -2
- data/lib/volcano/storage_session_adapter.rb +61 -0
- data/lib/volcano/storage_transport_adapter.rb +73 -0
- data/lib/volcano/storage_upload_models.rb +51 -27
- data/lib/volcano/storage_upload_sessions.rb +1 -35
- data/lib/volcano/version.rb +1 -1
- data/lib/volcano.rb +6 -1
- data/sig/auth_credentials.rbs +15 -0
- data/sig/realtime.rbs +46 -0
- data/sig/realtime_postgres_change.rbs +1 -2
- data/sig/storage.rbs +29 -0
- data/sig/storage_records.rbs +3 -1
- data/sig/upload_session_status.rbs +3 -1
- metadata +9 -1
|
@@ -4,6 +4,7 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Presence behavior mixed into realtime channels.
|
|
6
6
|
module Presence
|
|
7
|
+
# @dynamic on, emit, with_lifecycle_lock, ensure_open!, replace_presence, apply_presence_event
|
|
7
8
|
def on_presence_sync(callback = nil, &)
|
|
8
9
|
ensure_presence!
|
|
9
10
|
on('presence_sync', callback, &)
|
|
@@ -14,7 +15,8 @@ module Volcano
|
|
|
14
15
|
ensure_open!
|
|
15
16
|
ensure_presence!
|
|
16
17
|
raise ClosedError, 'realtime channel is not subscribed' unless @subscribed
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
validate_presence_state(state)
|
|
18
20
|
|
|
19
21
|
@tracked_state = Immutable.call(state)
|
|
20
22
|
end
|
|
@@ -24,14 +26,19 @@ module Volcano
|
|
|
24
26
|
def presence_state = @presence_state
|
|
25
27
|
def tracked_state = @tracked_state
|
|
26
28
|
|
|
29
|
+
# @dynamic get_presence_state
|
|
27
30
|
alias get_presence_state presence_state
|
|
28
31
|
|
|
29
32
|
private
|
|
30
33
|
|
|
31
34
|
def initialize_presence(type)
|
|
32
35
|
@type = type
|
|
33
|
-
@
|
|
34
|
-
|
|
36
|
+
# @type var empty_presence: Hash[String, PresenceInfo]
|
|
37
|
+
empty_presence = {}
|
|
38
|
+
# @type var empty_tracked: Hash[String, json_value]
|
|
39
|
+
empty_tracked = {}
|
|
40
|
+
@presence_state = Immutable.call(empty_presence)
|
|
41
|
+
@tracked_state = Immutable.call(empty_tracked)
|
|
35
42
|
@presence_handler = nil
|
|
36
43
|
@presence_lock = nil
|
|
37
44
|
@subscription_epoch = 0
|
|
@@ -45,6 +52,10 @@ module Volcano
|
|
|
45
52
|
raise ArgumentError, 'operation is only available for presence channels'
|
|
46
53
|
end
|
|
47
54
|
|
|
55
|
+
def validate_presence_state(state)
|
|
56
|
+
raise ArgumentError, 'presence state must be a hash' unless state.is_a?(Hash)
|
|
57
|
+
end
|
|
58
|
+
|
|
48
59
|
def register_presence_handler(protocol, epoch)
|
|
49
60
|
return unless presence?
|
|
50
61
|
|
|
@@ -67,6 +78,8 @@ module Volcano
|
|
|
67
78
|
next unless active_presence_epoch?(epoch)
|
|
68
79
|
|
|
69
80
|
result = protocol.presence(channel: @name)
|
|
81
|
+
raise TypeError, 'realtime presence result must be an object' unless result.is_a?(Hash)
|
|
82
|
+
|
|
70
83
|
replace_presence(result['presence']) if active_presence_epoch?(epoch)
|
|
71
84
|
end
|
|
72
85
|
end
|
|
@@ -108,7 +121,8 @@ module Volcano
|
|
|
108
121
|
end
|
|
109
122
|
|
|
110
123
|
def detach_presence_handler(protocol)
|
|
111
|
-
|
|
124
|
+
handler = @presence_handler
|
|
125
|
+
protocol.off_presence(@name, handler) if handler
|
|
112
126
|
@presence_handler = nil
|
|
113
127
|
end
|
|
114
128
|
|
|
@@ -4,10 +4,16 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Maintains immutable presence state independently of protocol lifecycle.
|
|
6
6
|
module PresenceState
|
|
7
|
+
# @dynamic presence_lock, active_presence_epoch?, presence?
|
|
8
|
+
|
|
7
9
|
private
|
|
8
10
|
|
|
9
11
|
def replace_presence(clients)
|
|
10
|
-
|
|
12
|
+
unless clients.is_a?(Hash)
|
|
13
|
+
# @type var empty_clients: Hash[String, Object?]
|
|
14
|
+
empty_clients = {}
|
|
15
|
+
clients = empty_clients
|
|
16
|
+
end
|
|
11
17
|
@presence_state = clients.to_h do |client_id, info|
|
|
12
18
|
[client_id.to_s.freeze, presence_info(info, client_id)]
|
|
13
19
|
end.freeze
|
|
@@ -32,7 +38,10 @@ module Volcano
|
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def presence_info(info, fallback_client)
|
|
35
|
-
|
|
41
|
+
# @type var empty_data: Hash[String, json_value]
|
|
42
|
+
empty_data = {}
|
|
43
|
+
raw_data = info['conn_info']
|
|
44
|
+
data = raw_data.is_a?(Hash) ? raw_data : empty_data
|
|
36
45
|
PresenceInfo.new(
|
|
37
46
|
client: info.fetch('client', fallback_client).to_s,
|
|
38
47
|
user: info['user']&.to_s,
|
|
@@ -44,16 +53,24 @@ module Volcano
|
|
|
44
53
|
return unless presence?
|
|
45
54
|
|
|
46
55
|
presence_lock.acquire do
|
|
47
|
-
@
|
|
48
|
-
|
|
56
|
+
# @type var empty_presence: Hash[String, PresenceInfo]
|
|
57
|
+
empty_presence = {}
|
|
58
|
+
# @type var empty_tracked: Hash[String, json_value]
|
|
59
|
+
empty_tracked = {}
|
|
60
|
+
@presence_state = Immutable.call(empty_presence)
|
|
61
|
+
@tracked_state = Immutable.call(empty_tracked)
|
|
49
62
|
@presence_state
|
|
50
63
|
end
|
|
51
64
|
end
|
|
52
65
|
|
|
53
66
|
def reset_presence
|
|
54
67
|
@subscription_epoch += 1
|
|
55
|
-
@
|
|
56
|
-
|
|
68
|
+
# @type var empty_presence: Hash[String, PresenceInfo]
|
|
69
|
+
empty_presence = {}
|
|
70
|
+
# @type var empty_tracked: Hash[String, json_value]
|
|
71
|
+
empty_tracked = {}
|
|
72
|
+
@presence_state = Immutable.call(empty_presence)
|
|
73
|
+
@tracked_state = Immutable.call(empty_tracked)
|
|
57
74
|
end
|
|
58
75
|
end
|
|
59
76
|
private_constant :PresenceState
|
|
@@ -14,6 +14,8 @@ module Volcano
|
|
|
14
14
|
class ServerError < StandardError
|
|
15
15
|
attr_reader :code
|
|
16
16
|
|
|
17
|
+
# @dynamic code
|
|
18
|
+
|
|
17
19
|
def initialize(message, code: nil)
|
|
18
20
|
super(message)
|
|
19
21
|
@code = code.is_a?(String) ? code.dup.freeze : code
|
|
@@ -46,12 +48,10 @@ module Volcano
|
|
|
46
48
|
def self.connect(id:, token:) = { 'id' => id, 'connect' => { 'token' => token } }
|
|
47
49
|
|
|
48
50
|
def self.subscribe(id:, channel:, recoverable: false, join_leave: false, recovery: nil)
|
|
51
|
+
# @type var options: Hash[String, Object]
|
|
49
52
|
options = { 'channel' => channel }
|
|
50
|
-
if recovery
|
|
51
|
-
|
|
52
|
-
elsif recoverable
|
|
53
|
-
options['recoverable'] = true
|
|
54
|
-
end
|
|
53
|
+
add_recovery_options(options, recovery) if recovery
|
|
54
|
+
options['recoverable'] = true if !recovery && recoverable
|
|
55
55
|
options['join_leave'] = true if join_leave
|
|
56
56
|
{ 'id' => id, 'subscribe' => options }
|
|
57
57
|
end
|
|
@@ -88,8 +88,9 @@ module Volcano
|
|
|
88
88
|
def connect(token:)
|
|
89
89
|
result = request('connect') { |id| self.class.connect(id: id, token: token) }
|
|
90
90
|
ensure_open!
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
raise TypeError, 'realtime connect result must be an object' unless result.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
result.tap { @connected = true }
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def subscribe(channel:, recoverable: false, join_leave: false, recovery: nil)
|
|
@@ -111,7 +112,11 @@ module Volcano
|
|
|
111
112
|
def unsubscribe(channel:)
|
|
112
113
|
@subscription_lock.acquire do
|
|
113
114
|
ensure_open!
|
|
114
|
-
|
|
115
|
+
unless @subscriptions.include?(channel)
|
|
116
|
+
# @type var empty_reply: Hash[String, Object]
|
|
117
|
+
empty_reply = {}
|
|
118
|
+
next empty_reply
|
|
119
|
+
end
|
|
115
120
|
|
|
116
121
|
result = request('unsubscribe') { |id| self.class.unsubscribe(id: id, channel: channel) }
|
|
117
122
|
@subscriptions.delete(channel)
|
|
@@ -146,7 +151,7 @@ module Volcano
|
|
|
146
151
|
end
|
|
147
152
|
|
|
148
153
|
def ensure_open!
|
|
149
|
-
raise
|
|
154
|
+
raise(@closed_error || ClosedError.new('realtime connection closed')) if @closed
|
|
150
155
|
|
|
151
156
|
self
|
|
152
157
|
end
|
|
@@ -4,6 +4,9 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Dispatches protocol replies and publications to their waiting consumers.
|
|
6
6
|
module ProtocolDispatch
|
|
7
|
+
# @dynamic write_frame, dispatch_publication, dispatch_queued_callback, close_with, closed_error
|
|
8
|
+
PresenceDelivery = Data.define(:handlers, :event, :data)
|
|
9
|
+
|
|
7
10
|
private
|
|
8
11
|
|
|
9
12
|
def process(frame)
|
|
@@ -12,7 +15,10 @@ module Volcano
|
|
|
12
15
|
elsif frame.key?('id')
|
|
13
16
|
dispatch_reply(frame)
|
|
14
17
|
elsif frame.key?('push')
|
|
15
|
-
|
|
18
|
+
push = frame.fetch('push')
|
|
19
|
+
raise TypeError, 'realtime push must be an object' unless push.is_a?(Hash)
|
|
20
|
+
|
|
21
|
+
dispatch_push(push)
|
|
16
22
|
end
|
|
17
23
|
end
|
|
18
24
|
|
|
@@ -27,7 +33,10 @@ module Volcano
|
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
def dispatch_reply(frame)
|
|
30
|
-
|
|
36
|
+
id = frame.fetch('id')
|
|
37
|
+
return unless id.is_a?(Integer)
|
|
38
|
+
|
|
39
|
+
pending = @pending.fetch(id, nil)
|
|
31
40
|
return unless pending
|
|
32
41
|
|
|
33
42
|
reply = reply_value(frame, pending.reply_key)
|
|
@@ -45,12 +54,18 @@ module Volcano
|
|
|
45
54
|
|
|
46
55
|
def reply_value(frame, reply_key)
|
|
47
56
|
error = frame['error']
|
|
48
|
-
return
|
|
57
|
+
return successful_reply(frame, reply_key) unless error
|
|
58
|
+
|
|
59
|
+
raise TypeError, 'realtime error must be an object' unless error.is_a?(Hash)
|
|
49
60
|
|
|
50
61
|
message = error['message'] || 'realtime command failed'
|
|
51
62
|
Protocol::Failure.new(error: ServerError.new(message, code: error['code']))
|
|
52
63
|
end
|
|
53
64
|
|
|
65
|
+
def successful_reply(frame, reply_key)
|
|
66
|
+
frame.fetch(reply_key) { frame.fetch('result') { frame.except('id') } }
|
|
67
|
+
end
|
|
68
|
+
|
|
54
69
|
def dispatch_presence(push, event)
|
|
55
70
|
channel = matching_channel(@presence_handlers, push['channel'].to_s)
|
|
56
71
|
info = push.dig(event, 'info')
|
|
@@ -61,7 +76,8 @@ module Volcano
|
|
|
61
76
|
return
|
|
62
77
|
end
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
delivery = PresenceDelivery.new(handlers: @presence_handlers.fetch(channel).dup, event:, data: info)
|
|
80
|
+
@callback_queue.enqueue(delivery)
|
|
65
81
|
end
|
|
66
82
|
|
|
67
83
|
def matching_channel(handlers, channel)
|
|
@@ -93,7 +109,7 @@ module Volcano
|
|
|
93
109
|
|
|
94
110
|
def dispatch_callback_delivery(handlers, event, data)
|
|
95
111
|
handlers.each do |handler|
|
|
96
|
-
break if @callback_stopping
|
|
112
|
+
break handlers if @callback_stopping
|
|
97
113
|
|
|
98
114
|
handler.call(event, data)
|
|
99
115
|
rescue StandardError
|
|
@@ -105,9 +121,11 @@ module Volcano
|
|
|
105
121
|
return if @callback_queue.size >= @max_callback_queue
|
|
106
122
|
|
|
107
123
|
channel, handlers = @pending_presence_resyncs.shift
|
|
108
|
-
return unless channel
|
|
124
|
+
return unless channel && handlers
|
|
109
125
|
|
|
110
|
-
@
|
|
126
|
+
# @type var empty_info: Hash[String, Object?]
|
|
127
|
+
empty_info = {}
|
|
128
|
+
@callback_queue.enqueue(PresenceDelivery.new(handlers:, event: 'sync_required', data: empty_info))
|
|
111
129
|
end
|
|
112
130
|
end
|
|
113
131
|
end
|
|
@@ -6,12 +6,15 @@ module Volcano
|
|
|
6
6
|
class Protocol
|
|
7
7
|
# Coordinates protocol requests and the socket reader loop.
|
|
8
8
|
module IO
|
|
9
|
+
# @dynamic ensure_open!, close_with, process
|
|
10
|
+
|
|
9
11
|
private
|
|
10
12
|
|
|
11
13
|
def request(reply_key, on_reply: nil)
|
|
14
|
+
# @type var registered_id: Integer?
|
|
12
15
|
ensure_open!
|
|
13
|
-
|
|
14
|
-
write_frame(yield(id))
|
|
16
|
+
registered_id, reply_queue = register_request(reply_key, on_reply)
|
|
17
|
+
write_frame(yield(registered_id || raise(TypeError, 'invalid realtime request id')))
|
|
15
18
|
reply = await_reply(reply_queue)
|
|
16
19
|
raise reply.error if reply.is_a?(Failure)
|
|
17
20
|
|
|
@@ -19,7 +22,7 @@ module Volcano
|
|
|
19
22
|
rescue StandardError => e
|
|
20
23
|
raise Redaction.exception(e, secrets: @secrets), cause: nil
|
|
21
24
|
ensure
|
|
22
|
-
@pending.delete(
|
|
25
|
+
@pending.delete(registered_id) if registered_id
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def register_request(reply_key, on_reply)
|
|
@@ -83,7 +86,8 @@ module Volcano
|
|
|
83
86
|
|
|
84
87
|
def closed_error(error)
|
|
85
88
|
ClosedError.new(Redaction.message(error.message, secrets: @secrets)).tap do |closed|
|
|
86
|
-
|
|
89
|
+
trace = error.backtrace
|
|
90
|
+
closed.set_backtrace(trace) if trace
|
|
87
91
|
end
|
|
88
92
|
end
|
|
89
93
|
end
|
|
@@ -5,6 +5,8 @@ module Volcano
|
|
|
5
5
|
class Protocol
|
|
6
6
|
# Exposes connection state and manages publication handlers.
|
|
7
7
|
module Lifecycle
|
|
8
|
+
# @dynamic ensure_open!, initialize_recovery_positions, initialize_publication_producer, dispatch_callbacks
|
|
9
|
+
# @dynamic dispatch_publication_batches, read_loop
|
|
8
10
|
def connected? = @connected && !@closed
|
|
9
11
|
|
|
10
12
|
def on_publication(channel, on_rejection: nil, &handler)
|
|
@@ -4,6 +4,8 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Normalizes publications and admits live deliveries without blocking the reader.
|
|
6
6
|
module ProtocolPublicationDispatch
|
|
7
|
+
# @dynamic enqueue_live_publication, matching_channel, drop_publication, mark_unknown_gap
|
|
8
|
+
# @dynamic dispatch_callback_delivery
|
|
7
9
|
Registration = Data.define(:handler, :on_rejection)
|
|
8
10
|
PublicationDelivery = Data.define(
|
|
9
11
|
:channel, :handlers, :event, :data, :publication, :recovered, :on_rejection, :rejection_key
|
|
@@ -24,12 +26,15 @@ module Volcano
|
|
|
24
26
|
def publication_delivery(channel, publication, recovered:)
|
|
25
27
|
matched_channel = matching_publication_channel(channel)
|
|
26
28
|
return unless matched_channel
|
|
27
|
-
return reject_invalid_publication(matched_channel, publication) unless
|
|
29
|
+
return reject_invalid_publication(matched_channel, publication) unless publication.is_a?(Hash)
|
|
30
|
+
|
|
31
|
+
data = publication['data']
|
|
32
|
+
return reject_invalid_publication(matched_channel, publication) unless data.is_a?(Hash)
|
|
28
33
|
unless @publication_handlers.key?(matched_channel)
|
|
29
34
|
return drop_handlerless_publication(matched_channel, publication)
|
|
30
35
|
end
|
|
31
36
|
|
|
32
|
-
build_publication_delivery(matched_channel, publication, recovered: recovered)
|
|
37
|
+
build_publication_delivery(matched_channel, publication, data, recovered: recovered)
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
def matching_publication_channel(channel)
|
|
@@ -38,10 +43,6 @@ module Volcano
|
|
|
38
43
|
[handler_channel, recovery_channel].compact.max_by(&:length)
|
|
39
44
|
end
|
|
40
45
|
|
|
41
|
-
def valid_publication?(publication)
|
|
42
|
-
publication.is_a?(Hash) && publication['data'].is_a?(Hash)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
46
|
def reject_invalid_publication(channel, publication)
|
|
46
47
|
if publication.is_a?(Hash)
|
|
47
48
|
drop_publication(channel, publication)
|
|
@@ -56,9 +57,8 @@ module Volcano
|
|
|
56
57
|
nil
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
def build_publication_delivery(channel, publication, recovered:)
|
|
60
|
+
def build_publication_delivery(channel, publication, data, recovered:)
|
|
60
61
|
registrations = @publication_handlers.fetch(channel).dup.freeze
|
|
61
|
-
data = publication.fetch('data')
|
|
62
62
|
PublicationDelivery.new(
|
|
63
63
|
channel: channel.dup.freeze,
|
|
64
64
|
handlers: registrations.map(&:handler).freeze,
|
|
@@ -98,14 +98,16 @@ module Volcano
|
|
|
98
98
|
def dispatch_queued_callback(delivery)
|
|
99
99
|
if delivery.is_a?(PublicationDelivery)
|
|
100
100
|
dispatch_publication_callbacks(delivery)
|
|
101
|
+
elsif delivery.is_a?(ProtocolDispatch::PresenceDelivery)
|
|
102
|
+
dispatch_callback_delivery(delivery.handlers, delivery.event, delivery.data)
|
|
101
103
|
else
|
|
102
|
-
|
|
104
|
+
raise TypeError, 'invalid realtime callback delivery'
|
|
103
105
|
end
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
def dispatch_publication_callbacks(delivery)
|
|
107
109
|
delivery.handlers.each do |handler|
|
|
108
|
-
break if @callback_stopping
|
|
110
|
+
break delivery.handlers if @callback_stopping
|
|
109
111
|
|
|
110
112
|
handler.call(
|
|
111
113
|
delivery.event, delivery.data, delivery.publication, recovered: delivery.recovered
|
|
@@ -4,6 +4,10 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Validates recovery subscribe results before updating stream positions.
|
|
6
6
|
module ProtocolRecovery
|
|
7
|
+
# @dynamic class, request, dispatch_recovered_publications, recovery_position
|
|
8
|
+
# @dynamic delete_recovery_state, set_recovery_position, mark_unknown_gap
|
|
9
|
+
# @dynamic mark_publication_gap, same_epoch?
|
|
10
|
+
|
|
7
11
|
private
|
|
8
12
|
|
|
9
13
|
def subscribe_request(channel:, recoverable:, join_leave:, recovery:)
|
|
@@ -28,7 +32,7 @@ module Volcano
|
|
|
28
32
|
|
|
29
33
|
def parse_recovery_result(channel:, recovery:, result:)
|
|
30
34
|
result_position, publications = validated_recovery_result(result)
|
|
31
|
-
return block_malformed_recovery_result(channel, recovery) unless result_position
|
|
35
|
+
return block_malformed_recovery_result(channel, recovery) unless result_position && publications
|
|
32
36
|
|
|
33
37
|
requested_position = requested_recovery_position(recovery)
|
|
34
38
|
return block_recovery_result(channel, requested_position) if unrecovered_empty_result?(
|
|
@@ -50,7 +54,7 @@ module Volcano
|
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
def unrecovered_empty_result?(result, requested_position, publications)
|
|
53
|
-
requested_position && result['recovered'] == false && publications.empty?
|
|
57
|
+
requested_position && result.is_a?(Hash) && result['recovered'] == false && publications.empty?
|
|
54
58
|
end
|
|
55
59
|
|
|
56
60
|
def validated_recovery_result(result)
|
|
@@ -68,7 +72,7 @@ module Volcano
|
|
|
68
72
|
validated = publications.map { |publication| validated_recovery_publication(publication, result_position) }
|
|
69
73
|
return [nil, nil] if validated.any?(&:nil?)
|
|
70
74
|
|
|
71
|
-
[result_position, validated]
|
|
75
|
+
[result_position, validated.compact]
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def validated_recovery_publication(publication, fallback_position)
|
|
@@ -4,6 +4,8 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Preserves publication order while recovered deliveries wait for callback capacity.
|
|
6
6
|
module ProtocolRecoveryDispatch
|
|
7
|
+
# @dynamic publication_delivery, reject_publication_delivery, admit_live_publication
|
|
8
|
+
# @dynamic close_with, closed_error
|
|
7
9
|
PublicationBatch = Data.define(:channel, :deliveries, :rejection_key)
|
|
8
10
|
private_constant :PublicationBatch
|
|
9
11
|
|
|
@@ -4,6 +4,7 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Reconnects unexpected transport losses and restores subscription intent.
|
|
6
6
|
module Reconnect
|
|
7
|
+
# @dynamic channel_lock, protocol_lock, connect_protocol, report_channel_error
|
|
7
8
|
RECONNECT_MIN_DELAY = 0.1
|
|
8
9
|
RECONNECT_MAX_DELAY = 20.0
|
|
9
10
|
MAX_BACKOFF_EXPONENT = 8
|
|
@@ -19,7 +20,9 @@ module Volcano
|
|
|
19
20
|
def stop_reconnect
|
|
20
21
|
task = @reconnect_task
|
|
21
22
|
@reconnect_task = nil
|
|
22
|
-
|
|
23
|
+
return unless task
|
|
24
|
+
|
|
25
|
+
task.stop unless task.equal?(Async::Task.current)
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
def reconnect_loop
|