volcano-sdk 0.34.0 → 0.35.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/README.md +53 -4
- data/lib/volcano/client.rb +1 -1
- data/lib/volcano/durable.rb +9 -4
- data/lib/volcano/durable_approval_models.rb +1 -1
- data/lib/volcano/durable_message_arguments.rb +49 -0
- data/lib/volcano/durable_message_backlog.rb +38 -0
- data/lib/volcano/durable_message_catch_ups.rb +112 -0
- data/lib/volcano/durable_message_models.rb +62 -0
- data/lib/volcano/durable_message_responses.rb +107 -0
- data/lib/volcano/durable_message_session.rb +72 -0
- data/lib/volcano/durable_message_stream.rb +176 -0
- data/lib/volcano/durable_messages.rb +217 -0
- data/lib/volcano/durable_models.rb +32 -3
- data/lib/volcano/generated/lib/volcano-generated/api/auth_admin_api.rb +2 -2
- data/lib/volcano/generated/lib/volcano-generated/api/auth_configuration_api.rb +2 -2
- data/lib/volcano/generated/lib/volcano-generated/api/autofix_api.rb +222 -0
- data/lib/volcano/generated/lib/volcano-generated/api/durable_functions_api.rb +725 -0
- data/lib/volcano/generated/lib/volcano-generated/api/frontends_api.rb +2 -2
- data/lib/volcano/generated/lib/volcano-generated/api/issues_api.rb +254 -0
- data/lib/volcano/generated/lib/volcano-generated/api/sandboxes_api.rb +20 -20
- data/lib/volcano/generated/lib/volcano-generated/api/system_api.rb +57 -0
- data/lib/volcano/generated/lib/volcano-generated/api/variable_environments_api.rb +373 -0
- data/lib/volcano/generated/lib/volcano-generated/api/verified_domains_api.rb +2 -2
- data/lib/volcano/generated/lib/volcano-generated/models/auth_config.rb +46 -4
- data/lib/volcano/generated/lib/volcano-generated/models/auth_insights_series_point.rb +110 -2
- data/lib/volcano/generated/lib/volcano-generated/models/autofix_model.rb +45 -0
- data/lib/volcano/generated/lib/volcano-generated/models/autofix_provider.rb +40 -0
- data/lib/volcano/generated/lib/volcano-generated/models/autofix_settings.rb +385 -0
- data/lib/volcano/generated/lib/volcano-generated/models/autofix_stage.rb +40 -0
- data/lib/volcano/generated/lib/volcano-generated/models/capability.rb +214 -0
- data/lib/volcano/generated/lib/volcano-generated/models/capability_list.rb +166 -0
- data/lib/volcano/generated/lib/volcano-generated/models/capability_status.rb +40 -0
- data/lib/volcano/generated/lib/volcano-generated/models/create_sandbox_session_request.rb +16 -53
- data/lib/volcano/generated/lib/volcano-generated/models/create_variable_environment_request.rb +176 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_execution.rb +14 -4
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message.rb +396 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_direction.rb +40 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_list.rb +194 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_sender.rb +203 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_session_registration.rb +165 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_status.rb +43 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_wait.rb +278 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_wait_list.rb +167 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_wait_registration.rb +225 -0
- data/lib/volcano/generated/lib/volcano-generated/models/durable_message_wait_status.rb +43 -0
- data/lib/volcano/generated/lib/volcano-generated/models/frontend.rb +11 -1
- data/lib/volcano/generated/lib/volcano-generated/models/frontend_custom_domain_conflict_error.rb +1 -1
- data/lib/volcano/generated/lib/volcano-generated/models/frontend_custom_domain_response.rb +25 -2
- data/lib/volcano/generated/lib/volcano-generated/models/issue.rb +493 -0
- data/lib/volcano/generated/lib/volcano-generated/models/issue_status.rb +41 -0
- data/lib/volcano/generated/lib/volcano-generated/models/metric_usage_data.rb +1 -1
- data/lib/volcano/generated/lib/volcano-generated/models/open_durable_message_session_request.rb +228 -0
- data/lib/volcano/generated/lib/volcano-generated/models/paginated_issues.rb +230 -0
- data/lib/volcano/generated/lib/volcano-generated/models/project_config.rb +10 -1
- data/lib/volcano/generated/lib/volcano-generated/models/project_config_autofix.rb +206 -0
- data/lib/volcano/generated/lib/volcano-generated/models/project_config_custom_domain.rb +1 -1
- data/lib/volcano/generated/lib/volcano-generated/models/project_config_sandbox.rb +12 -50
- data/lib/volcano/generated/lib/volcano-generated/models/project_frontend_custom_domain.rb +25 -2
- data/lib/volcano/generated/lib/volcano-generated/models/publish_durable_message_request.rb +205 -0
- data/lib/volcano/generated/lib/volcano-generated/models/register_durable_message_wait_request.rb +284 -0
- data/lib/volcano/generated/lib/volcano-generated/models/rename_variable_environment_request.rb +176 -0
- data/lib/volcano/generated/lib/volcano-generated/models/request_durable_approval_request.rb +1 -1
- data/lib/volcano/generated/lib/volcano-generated/models/sandbox_command_request.rb +11 -12
- data/lib/volcano/generated/lib/volcano-generated/models/sandbox_execution_request.rb +51 -13
- data/lib/volcano/generated/lib/volcano-generated/models/sandbox_session.rb +4 -30
- data/lib/volcano/generated/lib/volcano-generated/models/send_durable_message_request.rb +205 -0
- data/lib/volcano/generated/lib/volcano-generated/models/update_auth_config_request.rb +79 -1
- data/lib/volcano/generated/lib/volcano-generated/models/update_autofix_settings_request.rb +273 -0
- data/lib/volcano/generated/lib/volcano-generated/models/update_issue_request.rb +186 -0
- data/lib/volcano/generated/lib/volcano-generated/models/variable_environment.rb +325 -0
- data/lib/volcano/generated/lib/volcano-generated/models/variable_environment_list.rb +175 -0
- data/lib/volcano/generated/lib/volcano-generated.rb +34 -0
- data/lib/volcano/generated_transport.rb +1 -0
- data/lib/volcano/generated_transport_durable_messages.rb +57 -0
- data/lib/volcano/realtime/channel_callbacks.rb +5 -1
- data/lib/volcano/realtime/channel_lifecycle.rb +4 -1
- data/lib/volcano/realtime/durable_channel.rb +47 -0
- data/lib/volcano/realtime/lifecycle.rb +37 -1
- data/lib/volcano/realtime/protocol.rb +7 -3
- data/lib/volcano/realtime/protocol_dispatch.rb +1 -1
- data/lib/volcano/realtime/protocol_io.rb +7 -4
- data/lib/volcano/realtime/protocol_lifecycle.rb +7 -0
- data/lib/volcano/realtime/protocol_recovery.rb +23 -12
- data/lib/volcano/realtime.rb +42 -8
- data/lib/volcano/redaction.rb +1 -1
- data/lib/volcano/version.rb +1 -1
- data/lib/volcano.rb +8 -0
- data/sig/core_facades.rbs +15 -0
- data/sig/durable_message_records.rbs +68 -0
- data/sig/durable_records.rbs +17 -9
- metadata +46 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Volcano
|
|
4
|
+
# Durable message operations for the internal generated transport. A target
|
|
5
|
+
# with a project id takes the owner's project route; one without takes the
|
|
6
|
+
# application route, with the execution's client token when there is one.
|
|
7
|
+
class GeneratedTransport
|
|
8
|
+
def send_durable_message(authorization:, target:, name:, data:, idempotency_key:)
|
|
9
|
+
invoke do
|
|
10
|
+
durable = @api_factory.call(authorization).durable
|
|
11
|
+
body = Generated::SendDurableMessageRequest.new(name: name, data: data)
|
|
12
|
+
payload, status, headers = send_on_route(durable, target, body, idempotency_key)
|
|
13
|
+
response(payload, status, headers)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def list_durable_messages(authorization:, target:, query:)
|
|
18
|
+
invoke do
|
|
19
|
+
data, status, headers = list_on_route(@api_factory.call(authorization).durable, target, query)
|
|
20
|
+
response(data, status, headers)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def send_on_route(durable, target, body, idempotency_key)
|
|
27
|
+
if target.project_id
|
|
28
|
+
durable.send_durable_message_with_http_info(
|
|
29
|
+
target.project_id, target.function_id, target.execution_id, body,
|
|
30
|
+
durable_message_options(idempotency_key: idempotency_key)
|
|
31
|
+
)
|
|
32
|
+
else
|
|
33
|
+
durable.send_durable_message_from_application_with_http_info(
|
|
34
|
+
target.function_id, target.execution_id, body,
|
|
35
|
+
durable_message_options(idempotency_key: idempotency_key, x_volcano_client_token: target.client_token)
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def list_on_route(durable, target, query)
|
|
41
|
+
if target.project_id
|
|
42
|
+
durable.list_durable_messages_with_http_info(
|
|
43
|
+
target.project_id, target.function_id, target.execution_id, durable_message_options(**query)
|
|
44
|
+
)
|
|
45
|
+
else
|
|
46
|
+
durable.list_durable_messages_from_application_with_http_info(
|
|
47
|
+
target.function_id, target.execution_id,
|
|
48
|
+
durable_message_options(**query, x_volcano_client_token: target.client_token)
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def durable_message_options(**options)
|
|
54
|
+
options.compact.merge(debug_return_type: 'Object')
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -61,14 +61,18 @@ module Volcano
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# A durable message has no event field; everything the server publishes
|
|
65
|
+
# on its channel is one.
|
|
64
66
|
def deliver_publication(event, data)
|
|
65
67
|
if postgres?
|
|
66
68
|
dispatch_postgres_change(data)
|
|
67
|
-
elsif event == 'message'
|
|
69
|
+
elsif event == 'message' || durable?
|
|
68
70
|
emit('message', data)
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
def durable? = @type == :durable
|
|
75
|
+
|
|
72
76
|
def emit(event, data, before_delivery: nil)
|
|
73
77
|
callbacks = @callbacks[event].dup
|
|
74
78
|
return if callbacks.empty?
|
|
@@ -10,6 +10,7 @@ module Volcano
|
|
|
10
10
|
def mark_closed
|
|
11
11
|
@closed = true
|
|
12
12
|
@subscription_desired = @subscribed = false
|
|
13
|
+
@follower&.close
|
|
13
14
|
end_postgres_delivery
|
|
14
15
|
reset_presence
|
|
15
16
|
end
|
|
@@ -81,7 +82,9 @@ module Volcano
|
|
|
81
82
|
detach_publication_handler(protocol)
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
def subscription_options(binding)
|
|
85
|
+
def subscription_options(binding)
|
|
86
|
+
{ recovery: binding&.last, recoverable: presence?, join_leave: presence?, data: @subscribe_data }
|
|
87
|
+
end
|
|
85
88
|
|
|
86
89
|
def recovery_binding
|
|
87
90
|
return unless broadcast?
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Volcano
|
|
4
|
+
class Realtime
|
|
5
|
+
# Follows a durable execution's messages on a channel.
|
|
6
|
+
module DurableChannel
|
|
7
|
+
# @dynamic durable?
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# +data+ goes with every subscribe, including the ones a reconnect
|
|
12
|
+
# restores. +follower+ is asked before each of those restores, hears each
|
|
13
|
+
# one and each publication the connection dropped, and is closed with the
|
|
14
|
+
# channel.
|
|
15
|
+
def follow_durable(data, follower)
|
|
16
|
+
@subscribe_data = data
|
|
17
|
+
@follower = follower
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# A durable channel's follower reads back what the connection dropped.
|
|
21
|
+
def publication_rejection(protocol, generation)
|
|
22
|
+
follower = @follower
|
|
23
|
+
follower ? ->(_publication) { follower.dropped } : super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The server refuses a durable subscribe for good once the execution is
|
|
27
|
+
# gone or hidden from the session, so the follower ends the subscription
|
|
28
|
+
# instead of the reconnect retrying it, and the refusal says so.
|
|
29
|
+
def restore_refused(error)
|
|
30
|
+
follower = @follower
|
|
31
|
+
return error unless follower
|
|
32
|
+
return error if error.temporary?
|
|
33
|
+
|
|
34
|
+
follower.refused
|
|
35
|
+
ServerError.new("#{error.message}; the durable message subscription has ended", code: error.code)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The server authorizes a durable subscribe by the socket's session, so
|
|
39
|
+
# the socket must belong to the client's current one.
|
|
40
|
+
def prepare_protocol_subscription(protocol, epoch)
|
|
41
|
+
@realtime.__send__(:capture_protocol_session) if durable?
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
private_constant :DurableChannel
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -4,7 +4,7 @@ module Volcano
|
|
|
4
4
|
class Realtime
|
|
5
5
|
# Channel collection and connection-state lifecycle operations.
|
|
6
6
|
module Lifecycle
|
|
7
|
-
# @dynamic channel_lock, normalize_channel_type
|
|
7
|
+
# @dynamic channel_lock, normalize_channel_type, report_channel_error, protocol
|
|
8
8
|
def connected? = !@closed && (@protocol&.connected? || false)
|
|
9
9
|
|
|
10
10
|
def remove_channel(name, type: :broadcast)
|
|
@@ -34,6 +34,42 @@ module Volcano
|
|
|
34
34
|
@channels.delete(name)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Leaves a durable channel unless a later subscription replaced it. Once
|
|
38
|
+
# the connection is closed there is nothing left to leave.
|
|
39
|
+
def remove_durable_channel(channel)
|
|
40
|
+
channel_lock.acquire do
|
|
41
|
+
leave_durable_channel(channel) if forget_durable_channel(channel) && !@closed
|
|
42
|
+
end
|
|
43
|
+
nil
|
|
44
|
+
rescue Async::Cancel
|
|
45
|
+
# Stopped waiting for the lock, so the channel is still subscribed.
|
|
46
|
+
abandon_durable_leave(channel) { protocol.drop } if forget_durable_channel(channel)
|
|
47
|
+
raise
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def forget_durable_channel(channel)
|
|
51
|
+
@channels.delete(channel.name) if @channels[channel.name].equal?(channel)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A leave the server did not confirm may have left the subscription in
|
|
55
|
+
# place, so the connection is dropped, which ends it. The reconnect
|
|
56
|
+
# restores only the channels still in use.
|
|
57
|
+
def leave_durable_channel(channel)
|
|
58
|
+
channel.__send__(:remove)
|
|
59
|
+
rescue StandardError => e
|
|
60
|
+
abandon_durable_leave(channel) { protocol.close }
|
|
61
|
+
report_channel_error(e)
|
|
62
|
+
rescue Async::Cancel
|
|
63
|
+
# Closing here would start the reconnect in the task being stopped.
|
|
64
|
+
abandon_durable_leave(channel) { protocol.drop }
|
|
65
|
+
raise
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def abandon_durable_leave(channel)
|
|
69
|
+
channel.mark_closed
|
|
70
|
+
yield if connected?
|
|
71
|
+
end
|
|
72
|
+
|
|
37
73
|
def remove_channel_entry(name, channel)
|
|
38
74
|
channel.__send__(:remove)
|
|
39
75
|
@channels.delete(name)
|
|
@@ -16,10 +16,14 @@ module Volcano
|
|
|
16
16
|
|
|
17
17
|
# @dynamic code
|
|
18
18
|
|
|
19
|
-
def initialize(message, code: nil)
|
|
19
|
+
def initialize(message, code: nil, temporary: false)
|
|
20
20
|
super(message)
|
|
21
21
|
@code = code.is_a?(String) ? code.dup.freeze : code
|
|
22
|
+
@temporary = temporary == true
|
|
22
23
|
end
|
|
24
|
+
|
|
25
|
+
# Whether the server expects the same command to succeed later.
|
|
26
|
+
def temporary? = @temporary
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
class ClosedError < StandardError; end
|
|
@@ -93,12 +97,12 @@ module Volcano
|
|
|
93
97
|
result.tap { @connected = true }
|
|
94
98
|
end
|
|
95
99
|
|
|
96
|
-
def subscribe(channel:, recoverable: false, join_leave: false, recovery: nil)
|
|
100
|
+
def subscribe(channel:, recoverable: false, join_leave: false, recovery: nil, data: nil)
|
|
97
101
|
@subscription_lock.acquire do
|
|
98
102
|
ensure_open!
|
|
99
103
|
raise DuplicateSubscriptionError, "already subscribed to #{channel}" if @subscriptions.include?(channel)
|
|
100
104
|
|
|
101
|
-
result = subscribe_request(channel:, recoverable:, join_leave:, recovery:)
|
|
105
|
+
result = subscribe_request(channel:, recoverable:, join_leave:, recovery:, data:)
|
|
102
106
|
ensure_open!
|
|
103
107
|
@subscriptions.add(channel)
|
|
104
108
|
result
|
|
@@ -59,7 +59,7 @@ module Volcano
|
|
|
59
59
|
raise TypeError, 'realtime error must be an object' unless error.is_a?(Hash)
|
|
60
60
|
|
|
61
61
|
message = error['message'] || 'realtime command failed'
|
|
62
|
-
Protocol::Failure.new(error: ServerError.new(message, code: error['code']))
|
|
62
|
+
Protocol::Failure.new(error: ServerError.new(message, code: error['code'], temporary: error['temporary']))
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def successful_reply(frame, reply_key)
|
|
@@ -10,11 +10,13 @@ module Volcano
|
|
|
10
10
|
|
|
11
11
|
private
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# on_write runs once the frame holds the write lock, when the server may
|
|
14
|
+
# start to receive it.
|
|
15
|
+
def request(reply_key, on_reply: nil, on_write: nil)
|
|
14
16
|
# @type var registered_id: Integer?
|
|
15
17
|
ensure_open!
|
|
16
18
|
registered_id, reply_queue = register_request(reply_key, on_reply)
|
|
17
|
-
write_frame(yield(registered_id || raise(TypeError, 'invalid realtime request id')))
|
|
19
|
+
write_frame(yield(registered_id || raise(TypeError, 'invalid realtime request id')), on_write)
|
|
18
20
|
reply = await_reply(reply_queue)
|
|
19
21
|
raise reply.error if reply.is_a?(Failure)
|
|
20
22
|
|
|
@@ -36,10 +38,11 @@ module Volcano
|
|
|
36
38
|
[@next_id, queue]
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def write_frame(frame) = write_serialized_frame("#{JSON.generate(frame)}\n")
|
|
41
|
+
def write_frame(frame, on_write = nil) = write_serialized_frame("#{JSON.generate(frame)}\n", on_write)
|
|
40
42
|
|
|
41
|
-
def write_serialized_frame(frame)
|
|
43
|
+
def write_serialized_frame(frame, on_write = nil)
|
|
42
44
|
@write_lock.acquire do
|
|
45
|
+
on_write&.call
|
|
43
46
|
@socket.write(frame)
|
|
44
47
|
@socket.flush
|
|
45
48
|
end
|
|
@@ -9,6 +9,13 @@ module Volcano
|
|
|
9
9
|
# @dynamic dispatch_publication_batches, read_loop
|
|
10
10
|
def connected? = @connected && !@closed
|
|
11
11
|
|
|
12
|
+
# Ends the connection from the reader, as a loss would, so the reconnect
|
|
13
|
+
# runs in the protocol's tasks rather than the caller's.
|
|
14
|
+
def drop
|
|
15
|
+
close_socket
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
12
19
|
def on_publication(channel, on_rejection: nil, &handler)
|
|
13
20
|
ensure_open!
|
|
14
21
|
@publication_handlers[channel] << ProtocolPublicationDispatch::Registration.new(
|
|
@@ -6,27 +6,38 @@ module Volcano
|
|
|
6
6
|
module ProtocolRecovery
|
|
7
7
|
# @dynamic class, request, dispatch_recovered_publications, recovery_position
|
|
8
8
|
# @dynamic delete_recovery_state, set_recovery_position, mark_unknown_gap
|
|
9
|
-
# @dynamic mark_publication_gap, same_epoch
|
|
9
|
+
# @dynamic mark_publication_gap, same_epoch?, close_socket
|
|
10
10
|
|
|
11
11
|
private
|
|
12
12
|
|
|
13
|
-
def subscribe_request(channel:, recoverable:, join_leave:, recovery:)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
publications = parse_recovery_result(channel: channel, recovery: recovery, result: reply)
|
|
18
|
-
dispatch_recovered_publications(channel, publications)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
request('subscribe', on_reply:) do |id|
|
|
23
|
-
self.class.subscribe(
|
|
13
|
+
def subscribe_request(channel:, recoverable:, join_leave:, recovery:, data:)
|
|
14
|
+
writing = false
|
|
15
|
+
request('subscribe', on_reply: recovery_reply(channel, recovery), on_write: -> { writing = true }) do |id|
|
|
16
|
+
command = self.class.subscribe(
|
|
24
17
|
id: id,
|
|
25
18
|
channel: channel,
|
|
26
19
|
recoverable: recoverable,
|
|
27
20
|
join_leave: join_leave,
|
|
28
21
|
recovery: recovery
|
|
29
22
|
)
|
|
23
|
+
command['subscribe']['data'] = data if data
|
|
24
|
+
command
|
|
25
|
+
end
|
|
26
|
+
rescue Async::Cancel
|
|
27
|
+
# Once the frame is being written the server may subscribe, and nothing
|
|
28
|
+
# would leave the channel. Closing the socket has the reader end the
|
|
29
|
+
# connection, as on a loss, so the reconnect is not cancelled along with
|
|
30
|
+
# this task. Stopped before that, there is nothing to leave.
|
|
31
|
+
close_socket if writing
|
|
32
|
+
raise
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def recovery_reply(channel, recovery)
|
|
36
|
+
return unless recovery
|
|
37
|
+
|
|
38
|
+
lambda do |reply|
|
|
39
|
+
publications = parse_recovery_result(channel: channel, recovery: recovery, result: reply)
|
|
40
|
+
dispatch_recovered_publications(channel, publications)
|
|
30
41
|
end
|
|
31
42
|
end
|
|
32
43
|
|
data/lib/volcano/realtime.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative 'realtime/postgres_batch'
|
|
|
16
16
|
require_relative 'realtime/postgres_delivery'
|
|
17
17
|
require_relative 'realtime/broadcast_delivery'
|
|
18
18
|
require_relative 'realtime/channel_callbacks'
|
|
19
|
+
require_relative 'realtime/durable_channel'
|
|
19
20
|
|
|
20
21
|
module Volcano
|
|
21
22
|
# Manages a project's realtime connection and broadcast channels.
|
|
@@ -68,6 +69,9 @@ module Volcano
|
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
CHANNEL_TYPES = %i[broadcast presence postgres].freeze
|
|
72
|
+
# A channel that fetches no rows still takes a batch configuration.
|
|
73
|
+
UNBATCHED = PostgresBatchConfig.new(auto_fetch: false, batch_window_ms: 1, max_batch_size: 1)
|
|
74
|
+
private_constant :UNBATCHED
|
|
71
75
|
|
|
72
76
|
# Owns the public realtime facade and its channel collection.
|
|
73
77
|
module Core
|
|
@@ -152,6 +156,8 @@ module Volcano
|
|
|
152
156
|
|
|
153
157
|
def report_channel_error(error) = protocol_error(public_error(error))
|
|
154
158
|
|
|
159
|
+
def blocking(&) = BlockingCall.call(&)
|
|
160
|
+
|
|
155
161
|
def cached_channel(channel, batch_config)
|
|
156
162
|
channel.__send__(:ensure_fetch_config!, batch_config)
|
|
157
163
|
channel
|
|
@@ -161,6 +167,20 @@ module Volcano
|
|
|
161
167
|
Channel.new(self, -> { protocol }, name, type, batch_config:)
|
|
162
168
|
end
|
|
163
169
|
|
|
170
|
+
# Opens the channel an execution publishes its messages on, or answers
|
|
171
|
+
# nil when this client already follows the execution.
|
|
172
|
+
def durable_channel(execution_id, client_token, follower)
|
|
173
|
+
channel_lock.acquire do
|
|
174
|
+
ensure_open!
|
|
175
|
+
name = "durable:#{execution_id}"
|
|
176
|
+
next if @channels.key?(name)
|
|
177
|
+
|
|
178
|
+
channel = build_channel(name, :durable, UNBATCHED)
|
|
179
|
+
channel.__send__(:follow_durable, client_token && { 'client_token' => client_token }, follower)
|
|
180
|
+
@channels[name] = channel
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
164
184
|
def protocol_lock
|
|
165
185
|
require 'async/semaphore'
|
|
166
186
|
@protocol_lock ||= Async::Semaphore.new(1)
|
|
@@ -186,6 +206,7 @@ module Volcano
|
|
|
186
206
|
include PostgresDelivery
|
|
187
207
|
include BroadcastDelivery
|
|
188
208
|
include ChannelCallbacks
|
|
209
|
+
include DurableChannel
|
|
189
210
|
|
|
190
211
|
attr_reader :name
|
|
191
212
|
|
|
@@ -194,7 +215,7 @@ module Volcano
|
|
|
194
215
|
@protocol_provider = protocol_provider
|
|
195
216
|
@name = name.freeze
|
|
196
217
|
@handler_registered = @subscribed = @subscription_desired = @closed = false
|
|
197
|
-
@lifecycle_lock = nil
|
|
218
|
+
@lifecycle_lock = @subscribe_data = @follower = nil
|
|
198
219
|
initialize_recovery_state
|
|
199
220
|
initialize_callback_dispatch
|
|
200
221
|
initialize_presence(type)
|
|
@@ -230,13 +251,13 @@ module Volcano
|
|
|
230
251
|
def restoration_needed? = subscription_desired? && !@subscribed
|
|
231
252
|
|
|
232
253
|
def restore_subscription(protocol)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
254
|
+
ask_follower_to_restore
|
|
255
|
+
state = resubscribe(protocol)
|
|
256
|
+
return unless state
|
|
257
|
+
|
|
258
|
+
subscribed_protocol, epoch = state
|
|
259
|
+
sync_presence(subscribed_protocol, epoch)
|
|
260
|
+
@follower&.resubscribed
|
|
240
261
|
nil
|
|
241
262
|
end
|
|
242
263
|
|
|
@@ -247,6 +268,7 @@ module Volcano
|
|
|
247
268
|
clear_broadcast_recovery_state
|
|
248
269
|
clear_channel_recovery_state
|
|
249
270
|
mark_removed
|
|
271
|
+
@follower&.close
|
|
250
272
|
end
|
|
251
273
|
nil
|
|
252
274
|
end
|
|
@@ -273,6 +295,18 @@ module Volcano
|
|
|
273
295
|
raise
|
|
274
296
|
end
|
|
275
297
|
|
|
298
|
+
# Outside the lifecycle lock: a follower that refuses may leave the channel.
|
|
299
|
+
def ask_follower_to_restore
|
|
300
|
+
@follower&.restoring if restoration_needed?
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# The refusal is handled outside the lifecycle lock too, for the same reason.
|
|
304
|
+
def resubscribe(protocol)
|
|
305
|
+
with_lifecycle_lock { restoration_needed? ? subscribe_protocol(protocol) : nil }
|
|
306
|
+
rescue ServerError => e
|
|
307
|
+
raise restore_refused(e)
|
|
308
|
+
end
|
|
309
|
+
|
|
276
310
|
def with_lifecycle_lock(&)
|
|
277
311
|
require 'async/semaphore'
|
|
278
312
|
lock = @lifecycle_lock ||= Async::Semaphore.new(1)
|
data/lib/volcano/redaction.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Volcano
|
|
|
25
25
|
return copy_volcano_error(error, redacted_message)
|
|
26
26
|
end
|
|
27
27
|
if defined?(Realtime::ServerError) && error.is_a?(Realtime::ServerError)
|
|
28
|
-
return error.class.new(redacted_message, code: error.code)
|
|
28
|
+
return error.class.new(redacted_message, code: error.code, temporary: error.temporary?)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
error.exception(redacted_message)
|
data/lib/volcano/version.rb
CHANGED
data/lib/volcano.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative 'volcano/session_credentials'
|
|
|
9
9
|
require_relative 'volcano/function_response'
|
|
10
10
|
require_relative 'volcano/durable_models'
|
|
11
11
|
require_relative 'volcano/durable_approval_models'
|
|
12
|
+
require_relative 'volcano/durable_message_models'
|
|
12
13
|
require_relative 'volcano/log_responses'
|
|
13
14
|
require_relative 'volcano/storage_models'
|
|
14
15
|
require_relative 'volcano/storage_upload_models'
|
|
@@ -48,6 +49,13 @@ require_relative 'volcano/functions'
|
|
|
48
49
|
require_relative 'volcano/durable_arguments'
|
|
49
50
|
require_relative 'volcano/durable_approval_responses'
|
|
50
51
|
require_relative 'volcano/durable_approvals'
|
|
52
|
+
require_relative 'volcano/durable_message_arguments'
|
|
53
|
+
require_relative 'volcano/durable_message_responses'
|
|
54
|
+
require_relative 'volcano/durable_message_catch_ups'
|
|
55
|
+
require_relative 'volcano/durable_message_backlog'
|
|
56
|
+
require_relative 'volcano/durable_message_stream'
|
|
57
|
+
require_relative 'volcano/durable_message_session'
|
|
58
|
+
require_relative 'volcano/durable_messages'
|
|
51
59
|
require_relative 'volcano/durable'
|
|
52
60
|
require_relative 'volcano/logs'
|
|
53
61
|
require_relative 'volcano/storage_session_adapter'
|
data/sig/core_facades.rbs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Volcano
|
|
2
2
|
type sdk_json = nil | bool | Integer | Float | String | Time | Array[sdk_json] | Hash[String | Symbol, sdk_json]
|
|
3
3
|
type sdk_payload = Hash[String | Symbol, sdk_json]
|
|
4
|
+
type durable_message_filters = { ?direction: (String | Symbol)?, ?name: String?, ?after: Integer?, ?limit: Integer?, ?client_token: String?, ?project_id: String? }
|
|
4
5
|
type durable_approval_filters = { ?status: String?, ?function: String?, ?execution_id: String?, ?from: (Time | String)?, ?to: (Time | String)?, ?page: Integer?, ?limit: Integer? }
|
|
5
6
|
type database_rows = Array[Hash[String, json_value]]
|
|
6
7
|
|
|
@@ -79,8 +80,22 @@ module Volcano
|
|
|
79
80
|
def deny: (String project_id, String approval_id, ?comment: String?) -> DurableApproval
|
|
80
81
|
end
|
|
81
82
|
|
|
83
|
+
class DurableMessageSubscription
|
|
84
|
+
def unsubscribe: () -> nil
|
|
85
|
+
def inspect: () -> String
|
|
86
|
+
def to_s: () -> String
|
|
87
|
+
def pretty_print: (DurableExecution::_PrettyPrinter printer) -> void
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class DurableMessages
|
|
91
|
+
def send_message: (String function_name, String execution_id, String name, ?sdk_json data, **String? options) -> DurableMessage
|
|
92
|
+
def list: (String function_name, String execution_id, ?durable_message_filters filters) -> DurableMessagePage
|
|
93
|
+
def subscribe: (String function_name, String execution_id, ?client_token: String?, ?project_id: String?) { (DurableMessage) -> void } -> DurableMessageSubscription
|
|
94
|
+
end
|
|
95
|
+
|
|
82
96
|
class Durable
|
|
83
97
|
attr_reader approvals: DurableApprovals
|
|
98
|
+
attr_reader messages: DurableMessages
|
|
84
99
|
|
|
85
100
|
def start: (String function_name, ?sdk_payload payload, ?execution_name: String?) -> DurableExecution
|
|
86
101
|
def get: (String project_id, String function_name, String execution_id) -> DurableExecution
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Volcano
|
|
2
|
+
class DurableMessageSender < Data
|
|
3
|
+
attr_reader type: String
|
|
4
|
+
attr_reader user_id: String?
|
|
5
|
+
|
|
6
|
+
def self.new: (String type, ?String? user_id) -> DurableMessageSender
|
|
7
|
+
| (type: String, ?user_id: String?) -> DurableMessageSender
|
|
8
|
+
def self.[]: (String type, ?String? user_id) -> DurableMessageSender
|
|
9
|
+
| (type: String, ?user_id: String?) -> DurableMessageSender
|
|
10
|
+
def self.members: () -> [:type, :user_id]
|
|
11
|
+
|
|
12
|
+
def initialize: (type: String, ?user_id: String?) -> void
|
|
13
|
+
def members: () -> [:type, :user_id]
|
|
14
|
+
def with: (?type: String, ?user_id: String?) -> DurableMessageSender
|
|
15
|
+
def to_h: () -> { type: String, user_id: String? }
|
|
16
|
+
| [K, V] () { (Symbol, String?) -> [K, V] } -> Hash[K, V]
|
|
17
|
+
def deconstruct: () -> [String, String?]
|
|
18
|
+
def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, String?]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class DurableMessage < Data
|
|
22
|
+
type field = String | Integer | json_value | DurableMessageSender | Time | nil
|
|
23
|
+
|
|
24
|
+
attr_reader id: String
|
|
25
|
+
attr_reader execution_id: String
|
|
26
|
+
attr_reader direction: String
|
|
27
|
+
attr_reader seq: Integer
|
|
28
|
+
attr_reader name: String
|
|
29
|
+
attr_reader data: json_value
|
|
30
|
+
attr_reader sender: DurableMessageSender
|
|
31
|
+
attr_reader status: String
|
|
32
|
+
attr_reader sent_at: Time
|
|
33
|
+
attr_reader delivered_at: Time?
|
|
34
|
+
|
|
35
|
+
def self.new: (String id, String execution_id, String direction, Integer seq, String name, json_value data, DurableMessageSender sender, String status, Time sent_at, ?Time? delivered_at) -> DurableMessage
|
|
36
|
+
| (id: String, execution_id: String, direction: String, seq: Integer, name: String, ?data: json_value, sender: DurableMessageSender, status: String, sent_at: Time, ?delivered_at: Time?) -> DurableMessage
|
|
37
|
+
def self.[]: (String id, String execution_id, String direction, Integer seq, String name, json_value data, DurableMessageSender sender, String status, Time sent_at, ?Time? delivered_at) -> DurableMessage
|
|
38
|
+
| (id: String, execution_id: String, direction: String, seq: Integer, name: String, ?data: json_value, sender: DurableMessageSender, status: String, sent_at: Time, ?delivered_at: Time?) -> DurableMessage
|
|
39
|
+
def self.members: () -> [:id, :execution_id, :direction, :seq, :name, :data, :sender, :status, :sent_at, :delivered_at]
|
|
40
|
+
|
|
41
|
+
private def initialize: (**field attributes) -> void
|
|
42
|
+
def members: () -> [:id, :execution_id, :direction, :seq, :name, :data, :sender, :status, :sent_at, :delivered_at]
|
|
43
|
+
def with: (?id: String, ?execution_id: String, ?direction: String, ?seq: Integer, ?name: String, ?data: json_value, ?sender: DurableMessageSender, ?status: String, ?sent_at: Time, ?delivered_at: Time?) -> DurableMessage
|
|
44
|
+
def to_h: () -> { id: String, execution_id: String, direction: String, seq: Integer, name: String, data: json_value, sender: DurableMessageSender, status: String, sent_at: Time, delivered_at: Time? }
|
|
45
|
+
| [K, V] () { (Symbol, field) -> [K, V] } -> Hash[K, V]
|
|
46
|
+
def deconstruct: () -> [String, String, String, Integer, String, json_value, DurableMessageSender, String, Time, Time?]
|
|
47
|
+
def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, field]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class DurableMessagePage < Data
|
|
51
|
+
attr_reader messages: Array[DurableMessage]
|
|
52
|
+
attr_reader has_more: bool
|
|
53
|
+
|
|
54
|
+
def self.new: (Enumerable[DurableMessage] messages, bool has_more) -> DurableMessagePage
|
|
55
|
+
| (messages: Enumerable[DurableMessage], has_more: bool) -> DurableMessagePage
|
|
56
|
+
def self.[]: (Enumerable[DurableMessage] messages, bool has_more) -> DurableMessagePage
|
|
57
|
+
| (messages: Enumerable[DurableMessage], has_more: bool) -> DurableMessagePage
|
|
58
|
+
def self.members: () -> [:messages, :has_more]
|
|
59
|
+
|
|
60
|
+
def initialize: (messages: Enumerable[DurableMessage], has_more: bool) -> void
|
|
61
|
+
def members: () -> [:messages, :has_more]
|
|
62
|
+
def with: (?messages: Enumerable[DurableMessage], ?has_more: bool) -> DurableMessagePage
|
|
63
|
+
def to_h: () -> { messages: Array[DurableMessage], has_more: bool }
|
|
64
|
+
| [K, V] () { (Symbol, Array[DurableMessage] | bool) -> [K, V] } -> Hash[K, V]
|
|
65
|
+
def deconstruct: () -> [Array[DurableMessage], bool]
|
|
66
|
+
def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, Array[DurableMessage] | bool]
|
|
67
|
+
end
|
|
68
|
+
end
|
data/sig/durable_records.rbs
CHANGED
|
@@ -29,21 +29,29 @@ module Volcano
|
|
|
29
29
|
attr_reader result_expired: bool?
|
|
30
30
|
attr_reader error: DurableExecutionError?
|
|
31
31
|
attr_reader completed_at: Time?
|
|
32
|
+
attr_reader client_token: String?
|
|
32
33
|
|
|
33
|
-
def self.new: (String id, String function_id, String name, String status, String region, Time created_at, ?json_value result, ?bool? result_expired, ?DurableExecutionError? error, ?Time? completed_at) -> DurableExecution
|
|
34
|
-
| (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> DurableExecution
|
|
35
|
-
def self.[]: (String id, String function_id, String name, String status, String region, Time created_at, ?json_value result, ?bool? result_expired, ?DurableExecutionError? error, ?Time? completed_at) -> DurableExecution
|
|
36
|
-
| (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> DurableExecution
|
|
37
|
-
def self.members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at]
|
|
34
|
+
def self.new: (String id, String function_id, String name, String status, String region, Time created_at, ?json_value result, ?bool? result_expired, ?DurableExecutionError? error, ?Time? completed_at, ?String? client_token) -> DurableExecution
|
|
35
|
+
| (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?, ?client_token: String?) -> DurableExecution
|
|
36
|
+
def self.[]: (String id, String function_id, String name, String status, String region, Time created_at, ?json_value result, ?bool? result_expired, ?DurableExecutionError? error, ?Time? completed_at, ?String? client_token) -> DurableExecution
|
|
37
|
+
| (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?, ?client_token: String?) -> DurableExecution
|
|
38
|
+
def self.members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at, :client_token]
|
|
38
39
|
|
|
39
40
|
private def initialize: (**(json_value | Time | DurableExecutionError) attributes) -> void
|
|
40
|
-
def members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at]
|
|
41
|
-
def with: (?id: String, ?function_id: String, ?name: String, ?status: String, ?region: String, ?created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> DurableExecution
|
|
42
|
-
def to_h: () -> { id: String, function_id: String, name: String, status: String, region: String, created_at: Time, result: json_value, result_expired: bool?, error: DurableExecutionError?, completed_at: Time? }
|
|
41
|
+
def members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at, :client_token]
|
|
42
|
+
def with: (?id: String, ?function_id: String, ?name: String, ?status: String, ?region: String, ?created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?, ?client_token: String?) -> DurableExecution
|
|
43
|
+
def to_h: () -> { id: String, function_id: String, name: String, status: String, region: String, created_at: Time, result: json_value, result_expired: bool?, error: DurableExecutionError?, completed_at: Time?, client_token: String? }
|
|
43
44
|
| [K, V] () { (Symbol, String | Time | json_value | bool | DurableExecutionError | nil) -> [K, V] } -> Hash[K, V]
|
|
44
|
-
def deconstruct: () -> [String, String, String, String, String, Time, json_value, bool?, DurableExecutionError?, Time?]
|
|
45
|
+
def deconstruct: () -> [String, String, String, String, String, Time, json_value, bool?, DurableExecutionError?, Time?, String?]
|
|
45
46
|
def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, String | Time | json_value | bool | DurableExecutionError | nil]
|
|
46
47
|
def terminal?: () -> bool
|
|
48
|
+
def inspect: () -> String
|
|
49
|
+
def to_s: () -> String
|
|
50
|
+
def pretty_print: (_PrettyPrinter printer) -> void
|
|
51
|
+
|
|
52
|
+
interface _PrettyPrinter
|
|
53
|
+
def text: (String text) -> void
|
|
54
|
+
end
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
class DurableExecutionPage < Data
|