prosody 0.5.0-aarch64-linux → 0.6.0-aarch64-linux
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/.cargo/config.toml +5 -0
- data/.config/rail.toml +26 -0
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.taplo.toml +1 -1
- data/AGENTS.md +29 -15
- data/CHANGELOG.md +14 -0
- data/CLAUDE.md +29 -15
- data/CONFIGURATION.md +19 -16
- data/README.md +100 -23
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/lib/prosody/3.2/prosody.so +2 -2
- data/lib/prosody/3.3/prosody.so +2 -2
- data/lib/prosody/3.4/prosody.so +2 -2
- data/lib/prosody/client.rb +32 -0
- data/lib/prosody/configuration.rb +20 -12
- data/lib/prosody/demand.rb +27 -0
- data/lib/prosody/native_stubs/client.rb +157 -0
- data/lib/prosody/native_stubs/context.rb +178 -0
- data/lib/prosody/native_stubs/message.rb +133 -0
- data/lib/prosody/native_stubs.rb +11 -956
- data/lib/prosody/state/deque.rb +253 -0
- data/lib/prosody/state/map.rb +313 -0
- data/lib/prosody/state/set.rb +121 -0
- data/lib/prosody/state/value.rb +58 -0
- data/lib/prosody/state.rb +157 -677
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +2 -1
- data/sig/configuration.rbs +19 -10
- data/sig/prosody.rbs +37 -4
- data/sig/published.rbs +102 -0
- data/sig/state.rbs +109 -122
- data/typecheck/payload_types.rb +12 -0
- data/typecheck/payload_types.rbs +1 -0
- metadata +19 -8
data/examples/keyed_state.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "prosody"
|
|
4
4
|
require "logger"
|
|
5
5
|
|
|
6
|
-
# Keyed-state example: per-key value/map/deque collections that survive across
|
|
6
|
+
# Keyed-state example: per-key value/map/set/deque collections that survive across
|
|
7
7
|
# events. Definitions are declared once and reused for both registration (on the
|
|
8
8
|
# client) and binding (inside the handler). Every state op yields the fiber,
|
|
9
9
|
# never the thread.
|
|
@@ -12,6 +12,7 @@ require "logger"
|
|
|
12
12
|
# keyed_state.rbs for payload and state types checked by Steep.
|
|
13
13
|
CART = Prosody.value("cart", ttl: 30 * 24 * 3600) # ValueState
|
|
14
14
|
TOTALS = Prosody.map("totals") # keys are always String
|
|
15
|
+
SEEN = Prosody.set("seen", ttl: 7 * 24 * 3600) # String members, no payload
|
|
15
16
|
BACKLOG = Prosody.message_deque("backlog", capacity: 100) # bounded window of messages
|
|
16
17
|
|
|
17
18
|
class KeyedStateHandler < Prosody::EventHandler
|
|
@@ -19,6 +20,7 @@ class KeyedStateHandler < Prosody::EventHandler
|
|
|
19
20
|
puts "Excise #{message.key}"
|
|
20
21
|
context.state(CART).clear
|
|
21
22
|
context.state(TOTALS).clear
|
|
23
|
+
context.state(SEEN).clear
|
|
22
24
|
context.state(BACKLOG).clear
|
|
23
25
|
nil
|
|
24
26
|
end
|
|
@@ -29,6 +31,10 @@ class KeyedStateHandler < Prosody::EventHandler
|
|
|
29
31
|
|
|
30
32
|
def on_message(context, message)
|
|
31
33
|
payload = message.payload
|
|
34
|
+
seen = context.state(SEEN)
|
|
35
|
+
return if seen.include?(payload["order_id"]) # skip an order seen before
|
|
36
|
+
seen << payload["order_id"]
|
|
37
|
+
|
|
32
38
|
cart = context.state(CART) # bound for this attempt only
|
|
33
39
|
current = cart.get || {"items" => []} # Hash, or nil when absent
|
|
34
40
|
cart.set(current.merge("items" => current["items"] + [payload["order_id"]]))
|
|
@@ -37,6 +43,8 @@ class KeyedStateHandler < Prosody::EventHandler
|
|
|
37
43
|
totals.set(message.key, payload["total"])
|
|
38
44
|
# Steep infers key as String and total as Integer from TOTALS's RBS type.
|
|
39
45
|
totals.each_pair { |key, total| @logger.info(format_total(key, total)) }
|
|
46
|
+
# Query keywords narrow a traversal: here, the first ten keys after this one.
|
|
47
|
+
totals.each_key(after: message.key, limit: 10) { |key| @logger.info("next key: #{key}") }
|
|
40
48
|
|
|
41
49
|
backlog = context.state(BACKLOG)
|
|
42
50
|
backlog.push(message) # stores the full Prosody::Message
|
|
@@ -63,7 +71,7 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
63
71
|
mock: true,
|
|
64
72
|
group_id: "keyed-state-example",
|
|
65
73
|
subscribed_topics: "orders",
|
|
66
|
-
state_collections: [CART, TOTALS, BACKLOG]
|
|
74
|
+
state_collections: [CART, TOTALS, SEEN, BACKLOG]
|
|
67
75
|
)
|
|
68
76
|
client.subscribe(KeyedStateHandler.new(logger: Logger.new($stdout)))
|
|
69
77
|
client.shutdown
|
data/examples/keyed_state.rbs
CHANGED
|
@@ -3,6 +3,7 @@ type keyed_state_order_event = { "order_id" => String, "total" => Integer }
|
|
|
3
3
|
|
|
4
4
|
CART: Prosody::_ValueDefinition[keyed_state_cart]
|
|
5
5
|
TOTALS: Prosody::_MapDefinition[Integer]
|
|
6
|
+
SEEN: Prosody::_SetDefinition
|
|
6
7
|
BACKLOG: Prosody::_MessageDequeDefinition[keyed_state_order_event]
|
|
7
8
|
|
|
8
9
|
class KeyedStateHandler < Prosody::EventHandler[keyed_state_order_event]
|
data/lib/prosody/3.2/prosody.so
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/prosody/3.2/prosody.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 37148304 bytes
|
|
4
|
+
sha256: 59593e6c5f8d787d1ef88b0da37e4ea9e89362f2dee9a71f68c2b331a066e5d7
|
data/lib/prosody/3.3/prosody.so
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/prosody/3.3/prosody.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 37160880 bytes
|
|
4
|
+
sha256: b80346fb441d4f421b17cfa6cb1d12fdf0aa5f9cd34a81ae86537dd8ad0e1b92
|
data/lib/prosody/3.4/prosody.so
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: data/lib/prosody/3.4/prosody.so
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 37160744 bytes
|
|
4
|
+
sha256: a37eb1bfbf9826d6bc07d86a05211a7c7580f8bba9bb19413883badc0c11482c
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosody
|
|
4
|
+
# Ruby-side lifecycle helpers for the native {Prosody::Client}.
|
|
5
|
+
class Client
|
|
6
|
+
# Creates a client, yields it, and shuts it down when the block exits.
|
|
7
|
+
# The client shuts down also when the block raises. Repeated shutdown
|
|
8
|
+
# calls wait for the same operation, so the block can also call
|
|
9
|
+
# {#shutdown}.
|
|
10
|
+
#
|
|
11
|
+
# @param config [Hash, Configuration] Client configuration
|
|
12
|
+
# @yieldparam client [Client] the new client
|
|
13
|
+
# @return [Object] the value of the block
|
|
14
|
+
# @raise [ArgumentError] if no block is given or the configuration is invalid
|
|
15
|
+
# @raise [RuntimeError] if client initialization or shutdown fails
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# Prosody::Client.open(bootstrap_servers: "localhost:9092") do |client|
|
|
19
|
+
# client.send_message("my-topic", "key", {"hello" => "world"})
|
|
20
|
+
# end
|
|
21
|
+
def self.open(config)
|
|
22
|
+
raise ArgumentError, "Prosody::Client.open requires a block" unless block_given?
|
|
23
|
+
|
|
24
|
+
client = new(config)
|
|
25
|
+
begin
|
|
26
|
+
yield client
|
|
27
|
+
ensure
|
|
28
|
+
client.shutdown
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -124,14 +124,18 @@ module Prosody
|
|
|
124
124
|
# Interval between offset commit operations (in seconds).
|
|
125
125
|
config_param :commit_interval, converter: ->(v) { duration_converter(v) }
|
|
126
126
|
|
|
127
|
+
# Interval between librdkafka statistics reports (in seconds).
|
|
128
|
+
# Env: PROSODY_STATISTICS_INTERVAL. Default: 5 seconds.
|
|
129
|
+
config_param :statistics_interval, converter: ->(v) { duration_converter(v) }
|
|
130
|
+
|
|
127
131
|
# Base delay for retry operations (in seconds).
|
|
128
132
|
config_param :retry_base, converter: ->(v) { duration_converter(v) }
|
|
129
133
|
|
|
130
134
|
# Maximum delay between retries (in seconds).
|
|
131
135
|
config_param :max_retry_delay, converter: ->(v) { duration_converter(v) }
|
|
132
136
|
|
|
133
|
-
#
|
|
134
|
-
# Must be at least 1. Default: 8192.
|
|
137
|
+
# Capacity of the producer idempotence cache and of the consumer
|
|
138
|
+
# deduplication cache. Must be at least 1. Default: 8192.
|
|
135
139
|
config_param :idempotence_cache_size, converter: ->(v) { Integer(v) }
|
|
136
140
|
|
|
137
141
|
# Version string for cache-busting deduplication hashes. Changing this
|
|
@@ -303,7 +307,7 @@ module Prosody
|
|
|
303
307
|
|
|
304
308
|
# Keyed-state collections to register before subscribe.
|
|
305
309
|
#
|
|
306
|
-
# Accepts an array of StateDefinition objects (from Prosody.value/map/deque
|
|
310
|
+
# Accepts an array of StateDefinition objects (from Prosody.value/map/set/deque
|
|
307
311
|
# and their message_* siblings) or already-serialized registration hashes.
|
|
308
312
|
# Duplicate names within the set are rejected by the native layer.
|
|
309
313
|
config_param :state_collections,
|
|
@@ -312,13 +316,15 @@ module Prosody
|
|
|
312
316
|
list.map { |d| d.respond_to?(:to_state_config) ? d.to_state_config : d }
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
# Subsystem under which published JSON collections are advertised.
|
|
319
|
+
# Subsystem under which published JSON and set collections are advertised.
|
|
316
320
|
# Uses PROSODY_SUBSYSTEM when omitted. Published collections require it.
|
|
317
321
|
config_param :subsystem, converter: lambda(&:to_s)
|
|
318
322
|
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
323
|
+
# Directory for the local keyed-state caches. Each consumer opens its
|
|
324
|
+
# cache in a new subdirectory and removes it when the consumer stops, so
|
|
325
|
+
# clients can share the directory. Falls back to the
|
|
326
|
+
# PROSODY_STATE_CACHE_DIR environment variable, then to
|
|
327
|
+
# <temp>/prosody/keyed-state. Must not be an empty string.
|
|
322
328
|
config_param :state_cache_dir, converter: lambda(&:to_s)
|
|
323
329
|
|
|
324
330
|
# Capacity of the owning keyed-state cache, such as "64 MiB". Uses
|
|
@@ -326,6 +332,13 @@ module Prosody
|
|
|
326
332
|
# selects its default.
|
|
327
333
|
config_param :state_owned_cache_size, converter: lambda(&:to_s)
|
|
328
334
|
|
|
335
|
+
# Bytes of in-memory writes the local keyed-state cache holds for each
|
|
336
|
+
# assigned partition before it flushes them to disk, such as "16 MiB".
|
|
337
|
+
# Memory use scales with the number of assigned partitions. Uses
|
|
338
|
+
# PROSODY_STATE_MEMTABLE_SIZE when omitted. Otherwise, the engine uses
|
|
339
|
+
# its default of 64 MiB.
|
|
340
|
+
config_param :state_memtable_size, converter: lambda(&:to_s)
|
|
341
|
+
|
|
329
342
|
# Capacity of the published-state read-through cache, such as "1 MiB".
|
|
330
343
|
# Uses PROSODY_STATE_READ_CACHE_SIZE when omitted. It then uses the owned
|
|
331
344
|
# cache size when set, or 1 MiB when both sizes are unset.
|
|
@@ -336,11 +349,6 @@ module Prosody
|
|
|
336
349
|
config_param :state_read_cache,
|
|
337
350
|
converter: ->(v) { (v == true || v == false) ? v : Float(v) }
|
|
338
351
|
|
|
339
|
-
# Delay in whole seconds between staging a provisional cell and the
|
|
340
|
-
# keyed-state recovery sweep. Every registered TTL must strictly exceed this.
|
|
341
|
-
# Must be a whole number of seconds >= 1 (validated natively).
|
|
342
|
-
config_param :state_recovery_delay, converter: ->(v) { duration_converter(v) }
|
|
343
|
-
|
|
344
352
|
# Operation mode of the client.
|
|
345
353
|
#
|
|
346
354
|
# Valid values:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosody
|
|
4
|
+
# The demand a handler call serves, from +context.demand+.
|
|
5
|
+
#
|
|
6
|
+
# +kind+ is +:normal+ for a normal delivery and +:failure+ for a retry after
|
|
7
|
+
# a failure. +retries+ is the number of retries: 0 for a normal delivery and
|
|
8
|
+
# 1 on the first retry. After Prosody defers an event, the count starts
|
|
9
|
+
# again at 1. The count is an estimate. Keep an exact attempt count in
|
|
10
|
+
# keyed state if the handler needs one.
|
|
11
|
+
#
|
|
12
|
+
# @example Alert only on a retry
|
|
13
|
+
# def on_message(context, message)
|
|
14
|
+
# alert(message.key) if context.demand.failure?
|
|
15
|
+
# end
|
|
16
|
+
Demand = Data.define(:kind, :retries) do
|
|
17
|
+
# Whether this is a normal delivery.
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def normal? = kind == :normal
|
|
21
|
+
|
|
22
|
+
# Whether this is a retry after a failure.
|
|
23
|
+
#
|
|
24
|
+
# @return [Boolean]
|
|
25
|
+
def failure? = kind == :failure
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Native stubs for {Prosody::Client}: sending and subscribing.
|
|
4
|
+
|
|
5
|
+
module Prosody
|
|
6
|
+
# Main client for interacting with the Prosody messaging system.
|
|
7
|
+
# Provides methods for sending messages and subscribing to Kafka topics.
|
|
8
|
+
#
|
|
9
|
+
# @see ext/prosody/src/client/mod.rs for implementation
|
|
10
|
+
class Client
|
|
11
|
+
# Creates a new Prosody client with the given configuration.
|
|
12
|
+
#
|
|
13
|
+
# @param config [Hash, Configuration] Client configuration
|
|
14
|
+
# @return [Client] A new client instance
|
|
15
|
+
# @raise [ArgumentError] If the configuration is invalid
|
|
16
|
+
# @raise [RuntimeError] If client initialization fails
|
|
17
|
+
#
|
|
18
|
+
# @example Creating a client with a Configuration object
|
|
19
|
+
# config = Prosody::Configuration.new do |c|
|
|
20
|
+
# c.bootstrap_servers = "localhost:9092"
|
|
21
|
+
# c.group_id = "my-consumer-group"
|
|
22
|
+
# end
|
|
23
|
+
# client = Prosody::Client.new(config)
|
|
24
|
+
#
|
|
25
|
+
# @example Creating a client with a hash
|
|
26
|
+
# client = Prosody::Client.new(
|
|
27
|
+
# bootstrap_servers: "localhost:9092",
|
|
28
|
+
# group_id: "my-consumer-group"
|
|
29
|
+
# )
|
|
30
|
+
def self.new(config)
|
|
31
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the current state of the consumer.
|
|
35
|
+
#
|
|
36
|
+
# The consumer can be in one of four states:
|
|
37
|
+
# - `:shut_down` - The client is shut down
|
|
38
|
+
# - `:unconfigured` - The consumer has not been configured yet
|
|
39
|
+
# - `:configured` - The consumer is configured but not running
|
|
40
|
+
# - `:running` - The consumer is actively consuming messages
|
|
41
|
+
#
|
|
42
|
+
# @return [Symbol] The current consumer state
|
|
43
|
+
def consumer_state
|
|
44
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the number of Kafka partitions currently assigned to this consumer.
|
|
48
|
+
#
|
|
49
|
+
# This method can be used to monitor the consumer's workload and ensure
|
|
50
|
+
# proper load balancing across multiple consumer instances.
|
|
51
|
+
#
|
|
52
|
+
# @return [Integer] The number of assigned partitions
|
|
53
|
+
def assigned_partition_count
|
|
54
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Checks if the consumer is stalled.
|
|
58
|
+
#
|
|
59
|
+
# A stalled consumer is one that has stopped processing messages due to
|
|
60
|
+
# errors or reaching processing limits. This can be used to detect unhealthy
|
|
61
|
+
# consumers that need attention.
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean] true if the consumer is stalled, false otherwise
|
|
64
|
+
def stalled?
|
|
65
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Sends a message to the specified Kafka topic.
|
|
69
|
+
#
|
|
70
|
+
# @param topic [String] The destination topic name
|
|
71
|
+
# @param key [String] The message key for partitioning
|
|
72
|
+
# @param payload [Prosody::json_value] The JSON-compatible message payload
|
|
73
|
+
# @return [void]
|
|
74
|
+
# @raise [RuntimeError] If the message cannot be sent
|
|
75
|
+
#
|
|
76
|
+
# @example Sending a simple message
|
|
77
|
+
# client.send_message("my-topic", "user-123", {
|
|
78
|
+
# "event" => "login", "timestamp" => Time.now.to_i
|
|
79
|
+
# })
|
|
80
|
+
def send_message(topic, key, payload)
|
|
81
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Sends an excise record to the specified Kafka topic.
|
|
85
|
+
#
|
|
86
|
+
# @param topic [String] The destination topic name
|
|
87
|
+
# @param key [String] The message key for partitioning
|
|
88
|
+
# @return [void]
|
|
89
|
+
# @raise [RuntimeError] If the excise record cannot be sent
|
|
90
|
+
def excise(topic, key)
|
|
91
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Subscribes to Kafka topics using the provided handler.
|
|
95
|
+
# The handler must implement `on_message`, `on_excise`, and `on_timer`.
|
|
96
|
+
#
|
|
97
|
+
# @param handler [EventHandler] A handler object that processes messages
|
|
98
|
+
# @return [void]
|
|
99
|
+
# @raise [ArgumentError] If a required handler method is missing
|
|
100
|
+
# @raise [RuntimeError] If subscription fails
|
|
101
|
+
#
|
|
102
|
+
# @example Subscribing with a handler
|
|
103
|
+
# class MyHandler < Prosody::EventHandler
|
|
104
|
+
# def on_message(context, message)
|
|
105
|
+
# puts "Received message: #{message.payload}"
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# def on_excise(_context, message)
|
|
109
|
+
# puts "Excised key: #{message.key}"
|
|
110
|
+
# end
|
|
111
|
+
#
|
|
112
|
+
# def on_timer(_context, _timer)
|
|
113
|
+
# end
|
|
114
|
+
# end
|
|
115
|
+
#
|
|
116
|
+
# client.subscribe(MyHandler.new)
|
|
117
|
+
def subscribe(handler)
|
|
118
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Unsubscribes from all topics, stopping message processing.
|
|
122
|
+
#
|
|
123
|
+
# This method gracefully shuts down the consumer, completing any in-flight
|
|
124
|
+
# messages before stopping.
|
|
125
|
+
#
|
|
126
|
+
# @return [void]
|
|
127
|
+
# @raise [RuntimeError] If unsubscription fails
|
|
128
|
+
#
|
|
129
|
+
def unsubscribe
|
|
130
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Shuts down the consumer and all client services.
|
|
134
|
+
#
|
|
135
|
+
# @return [void]
|
|
136
|
+
# @raise [RuntimeError] If shutdown fails
|
|
137
|
+
#
|
|
138
|
+
# @example Shutting down a client
|
|
139
|
+
# client.shutdown
|
|
140
|
+
def shutdown
|
|
141
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Returns the configured source system identifier.
|
|
145
|
+
#
|
|
146
|
+
# The source system is used to identify the originating service or
|
|
147
|
+
# component in produced messages, enabling loop detection.
|
|
148
|
+
#
|
|
149
|
+
# @return [String] The source system identifier
|
|
150
|
+
#
|
|
151
|
+
# @example Getting the source system
|
|
152
|
+
# puts client.source_system # => "my-service"
|
|
153
|
+
def source_system
|
|
154
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Native stubs for {Prosody::Context}: event metadata, cancellation, and timer
|
|
4
|
+
# scheduling.
|
|
5
|
+
|
|
6
|
+
module Prosody
|
|
7
|
+
# Represents the context of a Kafka message, providing metadata and control
|
|
8
|
+
# capabilities for message handling.
|
|
9
|
+
#
|
|
10
|
+
# Instances of this class are created by the native code and passed to your
|
|
11
|
+
# EventHandler's #on_message method.
|
|
12
|
+
#
|
|
13
|
+
# @see ext/prosody/src/handler/context/mod.rs for implementation
|
|
14
|
+
class Context
|
|
15
|
+
# @private
|
|
16
|
+
def initialize
|
|
17
|
+
raise NotImplementedError, "This class is implemented natively in Rust"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Checks if cancellation has been requested.
|
|
21
|
+
#
|
|
22
|
+
# This method can be called within message handlers to detect when the
|
|
23
|
+
# handler should exit. Cancellation includes message-level cancellation
|
|
24
|
+
# (e.g., handler timeout) and partition shutdown. During shutdown,
|
|
25
|
+
# cancellation is delayed until near the end of the shutdown timeout to
|
|
26
|
+
# allow in-flight work to complete.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean] true if cancellation has been requested, false otherwise
|
|
29
|
+
#
|
|
30
|
+
# @example Checking for cancellation in a loop
|
|
31
|
+
# def on_message(context, message)
|
|
32
|
+
# items = message.payload["items"]
|
|
33
|
+
# items.each do |item|
|
|
34
|
+
# return if context.should_cancel?
|
|
35
|
+
# process_item(item)
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
def should_cancel?
|
|
39
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the demand this handler call serves.
|
|
43
|
+
#
|
|
44
|
+
# A normal delivery has kind +:normal+ and 0 retries. A retry after a
|
|
45
|
+
# failure has kind +:failure+ and a retry count that is 1 on the first
|
|
46
|
+
# retry. The count is an estimate; see {Prosody::Demand}.
|
|
47
|
+
#
|
|
48
|
+
# @return [Prosody::Demand]
|
|
49
|
+
#
|
|
50
|
+
# @example Reading the retry count
|
|
51
|
+
# def on_message(context, message)
|
|
52
|
+
# logger.warn("retry #{context.demand.retries}") if context.demand.failure?
|
|
53
|
+
# end
|
|
54
|
+
def demand
|
|
55
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Blocks until cancellation is signaled.
|
|
59
|
+
#
|
|
60
|
+
# Cancellation includes message-level cancellation (e.g., handler timeout)
|
|
61
|
+
# and partition shutdown. During shutdown, cancellation is delayed until near
|
|
62
|
+
# the end of the shutdown timeout to allow in-flight work to complete.
|
|
63
|
+
# This method is useful for long-running handlers that need to wait for
|
|
64
|
+
# external events while remaining responsive to cancellation.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
67
|
+
#
|
|
68
|
+
# @example Waiting for cancellation
|
|
69
|
+
# def on_message(context, message)
|
|
70
|
+
# # Do some work, then wait for cancellation
|
|
71
|
+
# context.on_cancel
|
|
72
|
+
# end
|
|
73
|
+
def on_cancel
|
|
74
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Schedules a timer to fire at the specified time.
|
|
78
|
+
#
|
|
79
|
+
# Timers allow you to delay execution or implement timeout behavior within
|
|
80
|
+
# your message handlers. When a timer fires, your handler's #on_timer method
|
|
81
|
+
# will be called with the timer object.
|
|
82
|
+
#
|
|
83
|
+
# @param time [Time] When the timer should fire
|
|
84
|
+
# @return [void]
|
|
85
|
+
# @raise [ArgumentError] If the time is invalid or outside the supported range (1970-2106)
|
|
86
|
+
# @raise [RuntimeError] If timer scheduling fails
|
|
87
|
+
#
|
|
88
|
+
# @example Scheduling a delayed action
|
|
89
|
+
# def on_message(context, message)
|
|
90
|
+
# # Schedule a timer to fire in 30 seconds
|
|
91
|
+
# context.schedule(Time.now + 30)
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# def on_timer(context, timer)
|
|
95
|
+
# puts "Timer fired for key: #{timer.key}"
|
|
96
|
+
# end
|
|
97
|
+
def schedule(time)
|
|
98
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Clears all scheduled timers and schedules a new one at the specified time.
|
|
102
|
+
#
|
|
103
|
+
# This is equivalent to calling clear_scheduled followed by schedule, but
|
|
104
|
+
# performed atomically.
|
|
105
|
+
#
|
|
106
|
+
# @param time [Time] When the new timer should fire
|
|
107
|
+
# @return [void]
|
|
108
|
+
# @raise [ArgumentError] If the time is invalid or outside the supported range
|
|
109
|
+
# @raise [RuntimeError] If timer operations fail
|
|
110
|
+
#
|
|
111
|
+
# @example Replacing all timers with a new one
|
|
112
|
+
# def on_message(context, message)
|
|
113
|
+
# # Clear any existing timers and schedule a new one
|
|
114
|
+
# context.clear_and_schedule(Time.now + 60)
|
|
115
|
+
# end
|
|
116
|
+
def clear_and_schedule(time)
|
|
117
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Unschedules a timer that was scheduled for the specified time.
|
|
121
|
+
#
|
|
122
|
+
# If multiple timers were scheduled for the same time, this will remove one
|
|
123
|
+
# of them. If no timer exists for the specified time, this method does nothing.
|
|
124
|
+
#
|
|
125
|
+
# @param time [Time] The time for which to unschedule the timer
|
|
126
|
+
# @return [void]
|
|
127
|
+
# @raise [ArgumentError] If the time is invalid
|
|
128
|
+
# @raise [RuntimeError] If timer unscheduling fails
|
|
129
|
+
#
|
|
130
|
+
# @example Canceling a specific timer
|
|
131
|
+
# def on_message(context, message)
|
|
132
|
+
# timer_time = Time.now + 30
|
|
133
|
+
# context.schedule(timer_time)
|
|
134
|
+
#
|
|
135
|
+
# # Later, cancel that specific timer
|
|
136
|
+
# context.unschedule(timer_time)
|
|
137
|
+
# end
|
|
138
|
+
def unschedule(time)
|
|
139
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Clears all scheduled timers.
|
|
143
|
+
#
|
|
144
|
+
# After calling this method, no timers will be scheduled to fire for this
|
|
145
|
+
# message context.
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
148
|
+
# @raise [RuntimeError] If clearing timers fails
|
|
149
|
+
#
|
|
150
|
+
# @example Canceling all timers
|
|
151
|
+
# def on_message(context, message)
|
|
152
|
+
# context.clear_scheduled
|
|
153
|
+
# end
|
|
154
|
+
def clear_scheduled
|
|
155
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Returns all currently scheduled timer times.
|
|
159
|
+
#
|
|
160
|
+
# The returned array contains Time objects representing when each scheduled
|
|
161
|
+
# timer will fire. The array may be empty if no timers are scheduled.
|
|
162
|
+
#
|
|
163
|
+
# @return [Array<Time>] Array of scheduled timer times
|
|
164
|
+
# @raise [RuntimeError] If retrieving scheduled times fails
|
|
165
|
+
#
|
|
166
|
+
# @example Checking scheduled timers
|
|
167
|
+
# def on_message(context, message)
|
|
168
|
+
# scheduled_times = context.scheduled
|
|
169
|
+
# puts "#{scheduled_times.length} timers scheduled"
|
|
170
|
+
# scheduled_times.each do |time|
|
|
171
|
+
# puts "Timer will fire at: #{time}"
|
|
172
|
+
# end
|
|
173
|
+
# end
|
|
174
|
+
def scheduled
|
|
175
|
+
raise NotImplementedError, "This method is implemented natively in Rust"
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|