daytona 0.200.0 → 0.200.1
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/daytona/common/event_subscription_manager.rb +110 -60
- data/lib/daytona/sdk/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 714770a5cd03bbfb27825fd9c018333d0377db70c0ce32b7ed484ff62946b2da
|
|
4
|
+
data.tar.gz: 1ea5e87028fffadb41659ceb7d76e6ec635ce2801e27f53bf1319d02b66ea6a7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f8bec4d5a367c8c1f2cc48bab297903dd2b75f03460c0c72728842a639da4dd00c781ec5668f64aef5375af1955f3e1cb6221577dfa4d3fd237c97fbb6056e2
|
|
7
|
+
data.tar.gz: 19a3b2b8bafb2c5a9fc14777411de38abe6a6ac17b1c0d132af9334142ab915ef3aa325aa8184e1ac999df7e0c867a47e8d3691975b09be3ca6a98a80584b8b8
|
|
@@ -6,14 +6,29 @@
|
|
|
6
6
|
require 'securerandom'
|
|
7
7
|
|
|
8
8
|
module Daytona
|
|
9
|
+
# Tracks dispatcher subscriptions with TTL auto-expiry.
|
|
10
|
+
#
|
|
11
|
+
# All subscriptions share ONE lazily started expiry worker thread instead of a
|
|
12
|
+
# dedicated sleeping thread per subscription, so listing many sandboxes costs
|
|
13
|
+
# one thread total, not one thread each
|
|
14
|
+
# (https://github.com/daytona/clients/issues/108). #refresh only bumps the
|
|
15
|
+
# subscription deadline — it never creates or kills threads.
|
|
9
16
|
class EventSubscriptionManager
|
|
10
17
|
SUBSCRIPTION_TTL = 300
|
|
11
18
|
private_constant :SUBSCRIPTION_TTL
|
|
12
19
|
|
|
13
|
-
def initialize(dispatcher = nil)
|
|
20
|
+
def initialize(dispatcher = nil, ttl_seconds: SUBSCRIPTION_TTL)
|
|
14
21
|
@dispatcher = dispatcher
|
|
22
|
+
@ttl = ttl_seconds
|
|
15
23
|
@subscriptions = {}
|
|
24
|
+
# Deadline-ordered [expires_at, sub_id] entries. Entries are lazily
|
|
25
|
+
# invalidated: a popped entry is discarded when its subscription is gone,
|
|
26
|
+
# or re-queued at the current deadline when it was refreshed meanwhile.
|
|
27
|
+
# Invariant: every live subscription has at least one queue entry.
|
|
28
|
+
@expiry_queue = []
|
|
16
29
|
@mutex = Mutex.new
|
|
30
|
+
@cond = ConditionVariable.new
|
|
31
|
+
@worker = nil
|
|
17
32
|
@closed = false
|
|
18
33
|
end
|
|
19
34
|
|
|
@@ -24,7 +39,6 @@ module Daytona
|
|
|
24
39
|
|
|
25
40
|
unsubscribe = @dispatcher.subscribe(resource_id, events:, &handler)
|
|
26
41
|
sub_id = SecureRandom.hex(16)
|
|
27
|
-
timer_to_stop = nil
|
|
28
42
|
rollback_unsubscribe = nil
|
|
29
43
|
|
|
30
44
|
@mutex.synchronize do
|
|
@@ -34,8 +48,11 @@ module Daytona
|
|
|
34
48
|
next
|
|
35
49
|
end
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
expires_at = monotonic_now + @ttl
|
|
52
|
+
@subscriptions[sub_id] = { unsubscribe:, expires_at: }
|
|
53
|
+
push_entry_locked(expires_at, sub_id)
|
|
54
|
+
ensure_worker_locked
|
|
55
|
+
@cond.signal
|
|
39
56
|
end
|
|
40
57
|
|
|
41
58
|
if rollback_unsubscribe
|
|
@@ -43,44 +60,36 @@ module Daytona
|
|
|
43
60
|
return nil
|
|
44
61
|
end
|
|
45
62
|
|
|
46
|
-
stop_thread(timer_to_stop)
|
|
47
63
|
sub_id
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
def refresh(sub_id)
|
|
51
67
|
@mutex.synchronize do
|
|
52
|
-
# Reject after shutdown to prevent use-after-close
|
|
53
68
|
return false if @closed
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
timer_to_stop = nil
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
return false unless
|
|
70
|
+
subscription = @subscriptions[sub_id]
|
|
71
|
+
return false unless subscription
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
# No queue entry or worker wake-up needed: when the old deadline pops,
|
|
74
|
+
# the worker sees the newer expires_at and re-queues the subscription.
|
|
75
|
+
subscription[:expires_at] = monotonic_now + @ttl
|
|
76
|
+
true
|
|
62
77
|
end
|
|
63
|
-
|
|
64
|
-
stop_thread(timer_to_stop)
|
|
65
|
-
true
|
|
66
78
|
end
|
|
67
79
|
|
|
68
80
|
def unsubscribe(sub_id)
|
|
69
|
-
subscription =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
subscription = @mutex.synchronize do
|
|
82
|
+
removed = @subscriptions.delete(sub_id)
|
|
83
|
+
# The stale queue entry is discarded when the worker pops it — except
|
|
84
|
+
# when it was the last subscription: drop the leftovers and wake the
|
|
85
|
+
# worker so it exits now instead of idling until the old deadline.
|
|
86
|
+
if removed && @subscriptions.empty?
|
|
87
|
+
@expiry_queue.clear
|
|
88
|
+
@cond.signal
|
|
77
89
|
end
|
|
90
|
+
removed
|
|
78
91
|
end
|
|
79
|
-
|
|
80
|
-
return unless subscription
|
|
81
|
-
|
|
82
|
-
stop_thread(timer)
|
|
83
|
-
subscription[:unsubscribe].call
|
|
92
|
+
subscription&.dig(:unsubscribe)&.call
|
|
84
93
|
end
|
|
85
94
|
|
|
86
95
|
def shutdown
|
|
@@ -90,55 +99,96 @@ module Daytona
|
|
|
90
99
|
@closed = true
|
|
91
100
|
subscriptions = @subscriptions.values
|
|
92
101
|
@subscriptions = {}
|
|
102
|
+
@expiry_queue.clear
|
|
103
|
+
@cond.broadcast
|
|
93
104
|
end
|
|
94
105
|
|
|
95
|
-
subscriptions.each
|
|
96
|
-
stop_thread(subscription[:timer])
|
|
97
|
-
subscription[:unsubscribe].call
|
|
98
|
-
end
|
|
106
|
+
subscriptions.each { |subscription| subscription[:unsubscribe].call }
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
private
|
|
102
110
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
def monotonic_now
|
|
112
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
113
|
+
end
|
|
106
114
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
# Caller must hold @mutex.
|
|
116
|
+
def push_entry_locked(expires_at, sub_id)
|
|
117
|
+
index = @expiry_queue.bsearch_index { |entry| entry[0] > expires_at } || @expiry_queue.length
|
|
118
|
+
@expiry_queue.insert(index, [expires_at, sub_id])
|
|
119
|
+
end
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
# Caller must hold @mutex. The alive? check is a safety net: a worker that
|
|
122
|
+
# crashed for any reason leaves a dead-but-truthy reference, and a plain nil
|
|
123
|
+
# check would then block replacements forever, silently disabling expiry.
|
|
124
|
+
def ensure_worker_locked
|
|
125
|
+
return if @worker&.alive?
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
@worker = Thread.new { expiry_loop }
|
|
128
|
+
@worker.name = 'daytona-subscription-expiry'
|
|
129
|
+
@worker.abort_on_exception = false
|
|
130
|
+
end
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
def expiry_loop
|
|
133
|
+
loop do
|
|
134
|
+
expired = collect_expired
|
|
135
|
+
return if expired.nil?
|
|
136
|
+
|
|
137
|
+
expired.each do |subscription|
|
|
138
|
+
subscription[:unsubscribe].call
|
|
139
|
+
rescue StandardError
|
|
140
|
+
# One failing dispatcher unsubscribe must not kill the shared worker
|
|
141
|
+
# and silently stop expiry for every other subscription.
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
119
144
|
end
|
|
120
|
-
timer.abort_on_exception = false
|
|
121
|
-
subscription[:timer] = timer
|
|
122
|
-
previous_timer
|
|
123
145
|
end
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
# Blocks until at least one subscription expires. Returns nil when the
|
|
148
|
+
# worker should exit (shutdown, or no live subscriptions remain).
|
|
149
|
+
def collect_expired
|
|
150
|
+
@mutex.synchronize do
|
|
151
|
+
expired = []
|
|
152
|
+
|
|
153
|
+
while expired.empty?
|
|
154
|
+
return nil if @closed
|
|
155
|
+
|
|
156
|
+
if @expiry_queue.empty?
|
|
157
|
+
# No live subscriptions remain (see queue invariant above):
|
|
158
|
+
# exit and let the next subscribe start a fresh worker.
|
|
159
|
+
@worker = nil
|
|
160
|
+
return nil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
now = monotonic_now
|
|
164
|
+
deadline = @expiry_queue.first[0]
|
|
165
|
+
if deadline > now
|
|
166
|
+
@cond.wait(@mutex, deadline - now)
|
|
167
|
+
next
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
drain_due_entries_locked(now, expired)
|
|
171
|
+
end
|
|
128
172
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
timer
|
|
173
|
+
expired
|
|
174
|
+
end
|
|
132
175
|
end
|
|
133
176
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
# Caller must hold @mutex.
|
|
178
|
+
def drain_due_entries_locked(now, expired)
|
|
179
|
+
while !@expiry_queue.empty? && @expiry_queue.first[0] <= now
|
|
180
|
+
_, sub_id = @expiry_queue.shift
|
|
181
|
+
subscription = @subscriptions[sub_id]
|
|
182
|
+
next unless subscription
|
|
137
183
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
if subscription[:expires_at] > now
|
|
185
|
+
push_entry_locked(subscription[:expires_at], sub_id)
|
|
186
|
+
next
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
@subscriptions.delete(sub_id)
|
|
190
|
+
expired << subscription
|
|
191
|
+
end
|
|
142
192
|
end
|
|
143
193
|
end
|
|
144
194
|
end
|
data/lib/daytona/sdk/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: daytona
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.200.
|
|
4
|
+
version: 0.200.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daytona Platforms Inc.
|
|
@@ -85,42 +85,42 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - '='
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.200.
|
|
88
|
+
version: 0.200.1
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - '='
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: 0.200.
|
|
95
|
+
version: 0.200.1
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: daytona_api_client
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
100
|
- - '='
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: 0.200.
|
|
102
|
+
version: 0.200.1
|
|
103
103
|
type: :runtime
|
|
104
104
|
prerelease: false
|
|
105
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
107
|
- - '='
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 0.200.
|
|
109
|
+
version: 0.200.1
|
|
110
110
|
- !ruby/object:Gem::Dependency
|
|
111
111
|
name: daytona_toolbox_api_client
|
|
112
112
|
requirement: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
114
|
- - '='
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.200.
|
|
116
|
+
version: 0.200.1
|
|
117
117
|
type: :runtime
|
|
118
118
|
prerelease: false
|
|
119
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
121
|
- - '='
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 0.200.
|
|
123
|
+
version: 0.200.1
|
|
124
124
|
- !ruby/object:Gem::Dependency
|
|
125
125
|
name: dotenv
|
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|