ori-rb 0.4
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 +7 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/LICENSE +9 -0
- data/README.md +444 -0
- data/Rakefile +17 -0
- data/docs/images/example_boundary.png +0 -0
- data/docs/images/example_boundary_cancellation.png +0 -0
- data/docs/images/example_channel.png +0 -0
- data/docs/images/example_mutex.png +0 -0
- data/docs/images/example_promise.png +0 -0
- data/docs/images/example_semaphore.png +0 -0
- data/docs/images/example_trace.png +0 -0
- data/docs/images/example_trace_tag.png +0 -0
- data/lib/ori/channel.rb +148 -0
- data/lib/ori/lazy.rb +163 -0
- data/lib/ori/mutex.rb +9 -0
- data/lib/ori/out/index.html +146 -0
- data/lib/ori/out/script.js +3 -0
- data/lib/ori/promise.rb +39 -0
- data/lib/ori/reentrant_semaphore.rb +68 -0
- data/lib/ori/scope.rb +620 -0
- data/lib/ori/select.rb +35 -0
- data/lib/ori/selectable.rb +9 -0
- data/lib/ori/semaphore.rb +49 -0
- data/lib/ori/task.rb +78 -0
- data/lib/ori/timeout.rb +16 -0
- data/lib/ori/tracer.rb +335 -0
- data/lib/ori/version.rb +5 -0
- data/lib/ori.rb +68 -0
- data/mise-tasks/test +15 -0
- data/mise.toml +40 -0
- data/sorbet/config +8 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +619 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
- data/sorbet/rbi/gems/erb@5.1.1.rbi +845 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/io-console@0.8.1.rbi +9 -0
- data/sorbet/rbi/gems/json@2.15.1.rbi +2101 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +9 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
- data/sorbet/rbi/gems/minitest@5.26.0.rbi +2234 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/nio4r@2.7.4.rbi +293 -0
- data/sorbet/rbi/gems/parallel@1.27.0.rbi +291 -0
- data/sorbet/rbi/gems/parser@3.3.9.0.rbi +5535 -0
- data/sorbet/rbi/gems/pp@0.6.3.rbi +376 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +477 -0
- data/sorbet/rbi/gems/prism@1.5.2.rbi +42056 -0
- data/sorbet/rbi/gems/psych@5.2.6.rbi +2469 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +160 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
- data/sorbet/rbi/gems/rake@13.3.0.rbi +3036 -0
- data/sorbet/rbi/gems/rbi@0.3.7.rbi +7115 -0
- data/sorbet/rbi/gems/rbs@3.9.5.rbi +6978 -0
- data/sorbet/rbi/gems/rdoc@6.15.0.rbi +12777 -0
- data/sorbet/rbi/gems/regexp_parser@2.11.3.rbi +3845 -0
- data/sorbet/rbi/gems/reline@0.6.2.rbi +9 -0
- data/sorbet/rbi/gems/rexml@3.4.4.rbi +5285 -0
- data/sorbet/rbi/gems/rubocop-ast@1.47.1.rbi +7780 -0
- data/sorbet/rbi/gems/rubocop-shopify@2.17.1.rbi +9 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.11.0.rbi +2506 -0
- data/sorbet/rbi/gems/rubocop@1.81.1.rbi +63489 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
- data/sorbet/rbi/gems/spoom@1.6.3.rbi +6985 -0
- data/sorbet/rbi/gems/stringio@3.1.7.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
- data/sorbet/rbi/gems/thor@1.4.0.rbi +4399 -0
- data/sorbet/rbi/gems/tsort@0.2.0.rbi +393 -0
- data/sorbet/rbi/gems/unicode-display_width@3.2.0.rbi +132 -0
- data/sorbet/rbi/gems/unicode-emoji@4.1.0.rbi +251 -0
- data/sorbet/rbi/gems/vernier@1.8.1-96ce5c739bfe6a18d2f4393f4219a1bf48674b87.rbi +904 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
- data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +1429 -0
- data/sorbet/shims/fiber.rbi +21 -0
- data/sorbet/shims/io.rbi +8 -0
- data/sorbet/shims/random.rbi +9 -0
- data/sorbet/shims/rdoc.rbi +3 -0
- data/sorbet/tapioca/require.rb +7 -0
- metadata +169 -0
data/lib/ori/task.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module Ori
|
4
|
+
class Task
|
5
|
+
include(Ori::Selectable)
|
6
|
+
|
7
|
+
EMPTY = :empty
|
8
|
+
|
9
|
+
attr_reader :fiber
|
10
|
+
|
11
|
+
def initialize(&block)
|
12
|
+
@fiber = Fiber.new(&block)
|
13
|
+
@killed = false
|
14
|
+
@value = EMPTY
|
15
|
+
end
|
16
|
+
|
17
|
+
def alive?
|
18
|
+
@fiber.alive?
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
@value unless @value == EMPTY
|
23
|
+
end
|
24
|
+
|
25
|
+
def raise_error(error)
|
26
|
+
@fiber.raise(error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def killed?
|
30
|
+
@killed
|
31
|
+
end
|
32
|
+
|
33
|
+
def kill
|
34
|
+
@fiber.kill
|
35
|
+
@killed = true
|
36
|
+
@value = EMPTY
|
37
|
+
end
|
38
|
+
|
39
|
+
def id
|
40
|
+
@id ||= @fiber.object_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def resume
|
44
|
+
if @cancellation_error
|
45
|
+
@fiber.kill
|
46
|
+
return @cancellation_error
|
47
|
+
end
|
48
|
+
|
49
|
+
fiber_result = @fiber.resume
|
50
|
+
|
51
|
+
case fiber_result
|
52
|
+
when Ori::Channel, Ori::Promise, Ori::Semaphore, Ori::ReentrantSemaphore
|
53
|
+
fiber_result
|
54
|
+
else
|
55
|
+
return self if @fiber.alive?
|
56
|
+
|
57
|
+
@value = fiber_result
|
58
|
+
end
|
59
|
+
rescue => error
|
60
|
+
@fiber.kill
|
61
|
+
raise error
|
62
|
+
end
|
63
|
+
|
64
|
+
def await
|
65
|
+
Fiber.yield while @fiber.alive?
|
66
|
+
@value
|
67
|
+
end
|
68
|
+
|
69
|
+
def deconstruct
|
70
|
+
[await]
|
71
|
+
end
|
72
|
+
|
73
|
+
def cancel(error)
|
74
|
+
@cancellation_error = error
|
75
|
+
resume
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/ori/timeout.rb
ADDED
data/lib/ori/tracer.rb
ADDED
@@ -0,0 +1,335 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Ori
|
6
|
+
class Tracer
|
7
|
+
TIMELINE_WIDTH = 80
|
8
|
+
|
9
|
+
Event = Struct.new(:fiber_id, :type, :timestamp, :data, :scope_id)
|
10
|
+
ScopeEvent = Struct.new(:scope_id, :type, :timestamp, :data)
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@events = []
|
14
|
+
@scope_events = []
|
15
|
+
@start_time = nil
|
16
|
+
@fiber_names = {}
|
17
|
+
@fiber_ids = Set.new
|
18
|
+
@scope_hierarchy = {}
|
19
|
+
@fiber_scopes = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_scope(scope_id, parent_scope_id = nil, creating_fiber_id = nil, name: nil)
|
23
|
+
if parent_scope_id
|
24
|
+
@scope_hierarchy[parent_scope_id] ||= []
|
25
|
+
@scope_hierarchy[parent_scope_id] << scope_id
|
26
|
+
end
|
27
|
+
|
28
|
+
# Store scope names and use them as group IDs
|
29
|
+
@scope_names ||= {}
|
30
|
+
if name
|
31
|
+
@scope_names[scope_id] = "Scope #{name}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Track which fiber created this scope (if any)
|
35
|
+
@scope_creators ||= {}
|
36
|
+
@scope_creators[scope_id] = creating_fiber_id if creating_fiber_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def register_fiber(fiber_id, scope_id)
|
40
|
+
@fiber_scopes[fiber_id] = scope_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def record(fiber_id, type, data = nil)
|
44
|
+
return unless fiber_id
|
45
|
+
|
46
|
+
@start_time ||= current_time
|
47
|
+
@fiber_ids << fiber_id
|
48
|
+
|
49
|
+
@events << Event.new(
|
50
|
+
fiber_id,
|
51
|
+
type,
|
52
|
+
(current_time - @start_time).round(8),
|
53
|
+
data,
|
54
|
+
@fiber_scopes[fiber_id],
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def record_scope(scope_id, type, data = nil)
|
59
|
+
return unless scope_id
|
60
|
+
|
61
|
+
@start_time ||= current_time
|
62
|
+
|
63
|
+
@scope_events << ScopeEvent.new(
|
64
|
+
scope_id,
|
65
|
+
type,
|
66
|
+
(current_time - @start_time).round(8),
|
67
|
+
data,
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def visualize
|
72
|
+
return "No events recorded." if @events.empty?
|
73
|
+
|
74
|
+
name_width = 42
|
75
|
+
min_spacing = 1
|
76
|
+
duration = [@events.last.timestamp, 0.00000001].max
|
77
|
+
|
78
|
+
output = []
|
79
|
+
output << "Fiber Execution Timeline (#{duration.round(3)}s)"
|
80
|
+
|
81
|
+
# First pass: calculate all positions with minimum spacing
|
82
|
+
positions_by_fiber = {}
|
83
|
+
max_position = T.let(0, T.untyped)
|
84
|
+
|
85
|
+
@fiber_ids.sort.each do |fiber_id|
|
86
|
+
fiber_events = @events.select { |e| e.fiber_id == fiber_id }
|
87
|
+
next if fiber_events.empty?
|
88
|
+
|
89
|
+
# Calculate raw positions based on timestamps
|
90
|
+
positions = []
|
91
|
+
fiber_events.each_with_index do |evt, idx|
|
92
|
+
raw_pos = (evt.timestamp / duration * TIMELINE_WIDTH).floor # Use larger scale initially
|
93
|
+
|
94
|
+
if idx > 0
|
95
|
+
# Ensure minimum spacing from previous event
|
96
|
+
prev_pos = T.unsafe(positions.last) || -1
|
97
|
+
positions << [raw_pos, prev_pos + min_spacing].max
|
98
|
+
else
|
99
|
+
positions << raw_pos
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
positions_by_fiber[fiber_id] = positions
|
104
|
+
max_position = [max_position, T.unsafe(positions.last) || 0].max
|
105
|
+
end
|
106
|
+
|
107
|
+
# Calculate final timeline width based on max position
|
108
|
+
timeline_width = max_position + 1 # Add some padding
|
109
|
+
separator = "=" * (timeline_width + name_width + 3)
|
110
|
+
output << separator
|
111
|
+
|
112
|
+
# Second pass: render the timeline
|
113
|
+
@fiber_ids.sort.each do |fiber_id|
|
114
|
+
fiber_events = @events.select { |e| e.fiber_id == fiber_id }
|
115
|
+
next if fiber_events.empty?
|
116
|
+
|
117
|
+
fiber_name = @fiber_names[fiber_id] || "Fiber #{fiber_id}"
|
118
|
+
line = "#{fiber_name.ljust(name_width)} |"
|
119
|
+
timeline = " " * timeline_width
|
120
|
+
positions = positions_by_fiber[fiber_id]
|
121
|
+
|
122
|
+
# Render events using calculated positions
|
123
|
+
fiber_events.each_with_index do |evt, idx|
|
124
|
+
pos = positions[idx]
|
125
|
+
next_pos = positions[idx + 1]
|
126
|
+
|
127
|
+
# Choose character based on event type
|
128
|
+
char = case evt.type
|
129
|
+
when :opened, :created then "█"
|
130
|
+
when :resuming then "▶"
|
131
|
+
when :waiting_io then "~"
|
132
|
+
when :sleeping then "."
|
133
|
+
when :yielded then "╎"
|
134
|
+
when :closed, :completed then "▒"
|
135
|
+
when :cancelling then "⏹"
|
136
|
+
when :error, :cancelled then "✗"
|
137
|
+
when :awaiting then "↻"
|
138
|
+
else " "
|
139
|
+
end
|
140
|
+
|
141
|
+
timeline[pos] = char
|
142
|
+
|
143
|
+
# Fill the space until the next event if there is one
|
144
|
+
next unless next_pos
|
145
|
+
|
146
|
+
length = next_pos - pos - 1
|
147
|
+
next if length <= 0
|
148
|
+
|
149
|
+
fill_char = case evt.type
|
150
|
+
when :resuming then "═"
|
151
|
+
when :waiting_io then "~"
|
152
|
+
when :sleeping then "."
|
153
|
+
when :yielded then "-"
|
154
|
+
else " "
|
155
|
+
end
|
156
|
+
|
157
|
+
timeline[pos + 1, length] = fill_char * length
|
158
|
+
end
|
159
|
+
|
160
|
+
line << timeline << "|"
|
161
|
+
output << line
|
162
|
+
end
|
163
|
+
|
164
|
+
output << separator
|
165
|
+
output << "Legend: (█ Start) (▒ Finish) (═ Running) (~ IO-Wait) (. Sleeping) (╎ Yield) (✗ Error)"
|
166
|
+
|
167
|
+
output.join("\n")
|
168
|
+
end
|
169
|
+
|
170
|
+
def generate_timeline_data
|
171
|
+
# Get unique scope IDs
|
172
|
+
scope_ids = @fiber_scopes.values.uniq.sort
|
173
|
+
|
174
|
+
# Track nested groups for each parent
|
175
|
+
nested_groups = Hash.new { |h, k| h[k] = [] }
|
176
|
+
|
177
|
+
# First, handle scope hierarchy relationships
|
178
|
+
scope_ids.each do |scope_id|
|
179
|
+
next unless scope_id
|
180
|
+
|
181
|
+
# If scope was created by a fiber, nest it under that fiber
|
182
|
+
if @scope_creators&.[](scope_id)
|
183
|
+
creating_fiber = @scope_creators[scope_id]
|
184
|
+
group_id = "scope_#{scope_id}"
|
185
|
+
nested_groups["fiber_#{creating_fiber}"] << group_id
|
186
|
+
else
|
187
|
+
# Otherwise use normal scope hierarchy
|
188
|
+
parent_id = @scope_hierarchy.find { |_, children| children.include?(scope_id) }&.first
|
189
|
+
group_id = "scope_#{scope_id}"
|
190
|
+
parent_group = if parent_id
|
191
|
+
"scope_#{parent_id}"
|
192
|
+
else
|
193
|
+
"main"
|
194
|
+
end
|
195
|
+
nested_groups[parent_group] << group_id
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Then map remaining fibers to their parent scopes
|
200
|
+
@fiber_ids.sort.each do |fiber_id|
|
201
|
+
next if nested_groups.values.any? { |groups| groups.include?("fiber_#{fiber_id}") }
|
202
|
+
|
203
|
+
scope_id = @fiber_scopes[fiber_id]
|
204
|
+
parent_group = if scope_id
|
205
|
+
"scope_#{scope_id}"
|
206
|
+
else
|
207
|
+
"main"
|
208
|
+
end
|
209
|
+
nested_groups[parent_group] << "fiber_#{fiber_id}"
|
210
|
+
end
|
211
|
+
|
212
|
+
# Generate groups data
|
213
|
+
groups = []
|
214
|
+
|
215
|
+
# Add root scope group
|
216
|
+
groups << {
|
217
|
+
id: "main",
|
218
|
+
content: "Root Scope",
|
219
|
+
value: 1,
|
220
|
+
className: "main-scope",
|
221
|
+
nestedGroups: nested_groups["main"],
|
222
|
+
showNested: true,
|
223
|
+
}
|
224
|
+
|
225
|
+
# Add scope groups
|
226
|
+
scope_ids.each do |scope_id|
|
227
|
+
next unless scope_id
|
228
|
+
|
229
|
+
group_id = "scope_#{scope_id}"
|
230
|
+
title = @scope_names[scope_id] || "Scope #{scope_id}"
|
231
|
+
|
232
|
+
data = {
|
233
|
+
id: group_id,
|
234
|
+
order: scope_id,
|
235
|
+
content: title,
|
236
|
+
value: 2,
|
237
|
+
className: "scope",
|
238
|
+
showNested: false,
|
239
|
+
}
|
240
|
+
|
241
|
+
if nested_groups[group_id].any?
|
242
|
+
data[:nestedGroups] = nested_groups[group_id]
|
243
|
+
end
|
244
|
+
|
245
|
+
groups << data
|
246
|
+
end
|
247
|
+
|
248
|
+
# Add fiber groups (including those that create scopes)
|
249
|
+
@fiber_ids.sort.each do |fiber_id|
|
250
|
+
data = {
|
251
|
+
id: "fiber_#{fiber_id}",
|
252
|
+
content: "Fiber #{fiber_id}",
|
253
|
+
value: 3,
|
254
|
+
className: "fiber",
|
255
|
+
showNested: false,
|
256
|
+
}
|
257
|
+
|
258
|
+
if nested_groups["fiber_#{fiber_id}"].any?
|
259
|
+
data[:nestedGroups] = nested_groups["fiber_#{fiber_id}"]
|
260
|
+
end
|
261
|
+
|
262
|
+
groups << data
|
263
|
+
end
|
264
|
+
|
265
|
+
# Generate dataset from both scope and fiber events
|
266
|
+
dataset = []
|
267
|
+
|
268
|
+
# Add scope lifecycle events
|
269
|
+
@scope_events.each do |event|
|
270
|
+
group_id = if event.scope_id == "main"
|
271
|
+
"main"
|
272
|
+
else
|
273
|
+
"scope_#{event.scope_id}"
|
274
|
+
end
|
275
|
+
|
276
|
+
item = {
|
277
|
+
group: group_id,
|
278
|
+
content: event.type.to_s,
|
279
|
+
start: (event.timestamp * 1_000_000).to_i.to_s,
|
280
|
+
className: event.type.to_s,
|
281
|
+
data: event.data,
|
282
|
+
}
|
283
|
+
|
284
|
+
if event.type == :tag
|
285
|
+
item[:content] = event.data
|
286
|
+
item.delete(:data)
|
287
|
+
end
|
288
|
+
|
289
|
+
dataset << item
|
290
|
+
end
|
291
|
+
|
292
|
+
# Add fiber events
|
293
|
+
@events.each do |event|
|
294
|
+
item = {
|
295
|
+
group: "fiber_#{event.fiber_id}",
|
296
|
+
content: event.type.to_s,
|
297
|
+
start: (event.timestamp * 1_000_000).to_i.to_s,
|
298
|
+
className: event.type.to_s,
|
299
|
+
data: event.data,
|
300
|
+
}
|
301
|
+
|
302
|
+
# Add end time if the event has duration
|
303
|
+
item[:end] = (event.end_timestamp * 1_000_000).to_i.to_s if event.respond_to?(:end_timestamp)
|
304
|
+
|
305
|
+
dataset << item
|
306
|
+
end
|
307
|
+
|
308
|
+
{
|
309
|
+
groups: groups,
|
310
|
+
dataset: dataset,
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
def write_timeline_data(output_path)
|
315
|
+
data = generate_timeline_data
|
316
|
+
|
317
|
+
# Create JavaScript file content
|
318
|
+
js_content = <<~JAVASCRIPT
|
319
|
+
export const groups = #{data[:groups].to_json};
|
320
|
+
|
321
|
+
export const dataset = #{data[:dataset].to_json};
|
322
|
+
JAVASCRIPT
|
323
|
+
|
324
|
+
# Write to file
|
325
|
+
File.write(File.join(output_path, "index.html"), File.read(File.join(__dir__, "out", "index.html")))
|
326
|
+
File.write(File.join(output_path, "script.js"), js_content)
|
327
|
+
end
|
328
|
+
|
329
|
+
private
|
330
|
+
|
331
|
+
def current_time
|
332
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
data/lib/ori/version.rb
ADDED
data/lib/ori.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
5
|
+
loader.setup
|
6
|
+
|
7
|
+
module Ori
|
8
|
+
class CancellationError < StandardError
|
9
|
+
|
10
|
+
#: Scope
|
11
|
+
attr_reader :scope
|
12
|
+
|
13
|
+
#: (Scope scope, ?String? message) -> void
|
14
|
+
def initialize(scope, message = "Scope cancelled")
|
15
|
+
@scope = scope
|
16
|
+
super(message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
#: (?name: String?, ?cancel_after: Numeric?, ?raise_after: Numeric?, ?trace: bool) { (Scope) -> void } -> Scope
|
22
|
+
def sync(name: nil, cancel_after: nil, raise_after: nil, trace: false, &block)
|
23
|
+
deadline = cancel_after || raise_after
|
24
|
+
prev_scheduler = Fiber.current_scheduler
|
25
|
+
|
26
|
+
scope = Scope.new(
|
27
|
+
prev_scheduler.is_a?(Scope) ? prev_scheduler : nil,
|
28
|
+
name,
|
29
|
+
deadline,
|
30
|
+
trace,
|
31
|
+
)
|
32
|
+
|
33
|
+
Fiber.set_scheduler(scope)
|
34
|
+
|
35
|
+
begin
|
36
|
+
if Fiber.current.blocking?
|
37
|
+
scope.fork { block.call(scope) }
|
38
|
+
else
|
39
|
+
yield(scope)
|
40
|
+
end
|
41
|
+
|
42
|
+
scope.await
|
43
|
+
scope
|
44
|
+
rescue CancellationError => error
|
45
|
+
# Re-raise if:
|
46
|
+
# 1. The error is from a different scope, or
|
47
|
+
# 2. This is our error but it's from raise_after
|
48
|
+
raise if error.scope != scope || !raise_after.nil?
|
49
|
+
|
50
|
+
scope # Return the scope even when cancelled
|
51
|
+
ensure
|
52
|
+
Fiber.set_scheduler(prev_scheduler)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# sig do
|
57
|
+
# type_parameters(:U)
|
58
|
+
# .params(resources: T::Array[T.all(T.type_parameter(:U), Ori::Selectable)])
|
59
|
+
# .returns(T.type_parameter(:U))
|
60
|
+
# end
|
61
|
+
#: [U] (Array[U & Selectable] resources) -> U
|
62
|
+
def select(resources)
|
63
|
+
Ori::Select.await(resources)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private_constant(:Scope)
|
68
|
+
end
|
data/mise-tasks/test
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env -S usage bash
|
2
|
+
|
3
|
+
#MISE description="Run tests"
|
4
|
+
#MISE alias="t"
|
5
|
+
#MISE depends=["typecheck"]
|
6
|
+
#USAGE arg "<file>" help="Run specific test file" default=""
|
7
|
+
#USAGE flag "-n --name <name>" help="Run tests with specific name" default=""
|
8
|
+
|
9
|
+
if [ -z "$usage_file" ]; then
|
10
|
+
bin/rake test
|
11
|
+
elif [ -z "$usage_name" ]; then
|
12
|
+
ruby -Itest "$usage_file"
|
13
|
+
else
|
14
|
+
ruby -Itest "$usage_file" --name "$usage_name"
|
15
|
+
fi
|
data/mise.toml
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
[tools]
|
2
|
+
ruby = "3.4.7"
|
3
|
+
usage = "latest"
|
4
|
+
watchexec = "latest"
|
5
|
+
|
6
|
+
[tasks.console]
|
7
|
+
description = "Start a REPL using IRB"
|
8
|
+
alias = "c"
|
9
|
+
run = "bin/console"
|
10
|
+
|
11
|
+
[tasks."bundle:install"]
|
12
|
+
description = "Install gem dependencies"
|
13
|
+
run = "bundle install"
|
14
|
+
|
15
|
+
[tasks.style]
|
16
|
+
description = "Run lint using Rubocop"
|
17
|
+
alias = "l"
|
18
|
+
run = "bin/rubocop"
|
19
|
+
|
20
|
+
[tasks.typecheck]
|
21
|
+
description = "Run typecheck using Sorbet"
|
22
|
+
alias = "tc"
|
23
|
+
run = "srb tc --enable-experimental-rbs-comments"
|
24
|
+
|
25
|
+
[tasks."rbi:update"]
|
26
|
+
description = "Update RBI files using Tapioca"
|
27
|
+
run = """
|
28
|
+
#!/usr/bin/env bash
|
29
|
+
bin/tapioca gem &&
|
30
|
+
bin/spoom srb bump --from false --to true &&
|
31
|
+
bin/spoom srb bump --from true --to strict
|
32
|
+
"""
|
33
|
+
|
34
|
+
[tasks.'gem:build']
|
35
|
+
description = "Build the gem"
|
36
|
+
run = "gem build && mkdir -p pkg && mv ori-rb-* pkg/"
|
37
|
+
|
38
|
+
[tasks.'gem:publish']
|
39
|
+
description = "Publish the gem"
|
40
|
+
run = "gem push pkg/*.gem"
|
data/sorbet/config
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
**/*.rbi linguist-generated=true
|