taski 0.8.2 → 0.9.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/CHANGELOG.md +47 -0
- data/README.md +65 -50
- data/docs/GUIDE.md +41 -56
- data/examples/README.md +10 -29
- data/examples/clean_demo.rb +25 -65
- data/examples/large_tree_demo.rb +356 -0
- data/examples/message_demo.rb +0 -1
- data/examples/progress_demo.rb +13 -24
- data/examples/reexecution_demo.rb +8 -44
- data/lib/taski/execution/execution_facade.rb +150 -0
- data/lib/taski/execution/executor.rb +156 -357
- data/lib/taski/execution/registry.rb +15 -19
- data/lib/taski/execution/scheduler.rb +161 -140
- data/lib/taski/execution/task_observer.rb +41 -0
- data/lib/taski/execution/task_output_router.rb +41 -58
- data/lib/taski/execution/task_wrapper.rb +123 -219
- data/lib/taski/execution/worker_pool.rb +238 -64
- data/lib/taski/logging.rb +105 -0
- data/lib/taski/progress/layout/base.rb +600 -0
- data/lib/taski/progress/layout/filters.rb +126 -0
- data/lib/taski/progress/layout/log.rb +27 -0
- data/lib/taski/progress/layout/simple.rb +166 -0
- data/lib/taski/progress/layout/tags.rb +76 -0
- data/lib/taski/progress/layout/theme_drop.rb +84 -0
- data/lib/taski/progress/layout/tree.rb +300 -0
- data/lib/taski/progress/theme/base.rb +224 -0
- data/lib/taski/progress/theme/compact.rb +58 -0
- data/lib/taski/progress/theme/default.rb +25 -0
- data/lib/taski/progress/theme/detail.rb +48 -0
- data/lib/taski/progress/theme/plain.rb +40 -0
- data/lib/taski/static_analysis/analyzer.rb +5 -17
- data/lib/taski/static_analysis/dependency_graph.rb +19 -1
- data/lib/taski/static_analysis/visitor.rb +1 -39
- data/lib/taski/task.rb +44 -58
- data/lib/taski/test_helper/errors.rb +1 -1
- data/lib/taski/test_helper.rb +21 -35
- data/lib/taski/version.rb +1 -1
- data/lib/taski.rb +60 -61
- data/sig/taski.rbs +194 -203
- metadata +31 -8
- data/examples/section_demo.rb +0 -195
- data/lib/taski/execution/base_progress_display.rb +0 -364
- data/lib/taski/execution/execution_context.rb +0 -390
- data/lib/taski/execution/plain_progress_display.rb +0 -76
- data/lib/taski/execution/simple_progress_display.rb +0 -206
- data/lib/taski/execution/tree_progress_display.rb +0 -643
- data/lib/taski/section.rb +0 -74
|
@@ -22,10 +22,10 @@ module Taski
|
|
|
22
22
|
READ_BUFFER_SIZE = 4096
|
|
23
23
|
MAX_RECENT_LINES = 30 # Maximum number of recent lines to keep per task
|
|
24
24
|
|
|
25
|
-
def initialize(original_stdout,
|
|
25
|
+
def initialize(original_stdout, execution_facade = nil)
|
|
26
26
|
super()
|
|
27
27
|
@original = original_stdout
|
|
28
|
-
@
|
|
28
|
+
@execution_facade = execution_facade
|
|
29
29
|
@pipes = {} # task_class => TaskOutputPipe
|
|
30
30
|
@thread_map = {} # Thread => task_class
|
|
31
31
|
@recent_lines = {} # task_class => Array<String>
|
|
@@ -50,27 +50,22 @@ module Taski
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
# Stop the background polling thread
|
|
54
53
|
def stop_polling
|
|
55
54
|
synchronize { @polling = false }
|
|
56
55
|
@poll_thread&.join(0.5)
|
|
57
56
|
@poll_thread = nil
|
|
58
57
|
end
|
|
59
58
|
|
|
60
|
-
# Start capturing output for the current thread
|
|
61
|
-
# Creates a new pipe for the task and registers the thread mapping
|
|
62
|
-
# @param task_class [Class] The task class being executed
|
|
63
59
|
def start_capture(task_class)
|
|
64
60
|
synchronize do
|
|
65
61
|
pipe = TaskOutputPipe.new(task_class)
|
|
66
62
|
@pipes[task_class] = pipe
|
|
67
63
|
@thread_map[Thread.current] = task_class
|
|
68
|
-
|
|
64
|
+
Taski::Logging.debug(Taski::Logging::Events::OUTPUT_ROUTER_START_CAPTURE, task: task_class.name)
|
|
69
65
|
end
|
|
70
66
|
end
|
|
71
67
|
|
|
72
|
-
#
|
|
73
|
-
# Closes the write end of the pipe and drains remaining data
|
|
68
|
+
# Closes the write end and drains remaining data.
|
|
74
69
|
def stop_capture
|
|
75
70
|
task_class = nil
|
|
76
71
|
pipe = nil
|
|
@@ -78,41 +73,20 @@ module Taski
|
|
|
78
73
|
synchronize do
|
|
79
74
|
task_class = @thread_map.delete(Thread.current)
|
|
80
75
|
unless task_class
|
|
81
|
-
|
|
76
|
+
Taski::Logging.debug(Taski::Logging::Events::OUTPUT_ROUTER_STOP_CAPTURE_UNREGISTERED)
|
|
82
77
|
return
|
|
83
78
|
end
|
|
84
79
|
|
|
85
80
|
pipe = @pipes[task_class]
|
|
86
81
|
pipe&.close_write
|
|
87
|
-
|
|
82
|
+
Taski::Logging.debug(Taski::Logging::Events::OUTPUT_ROUTER_STOP_CAPTURE, task: task_class.name)
|
|
88
83
|
end
|
|
89
84
|
|
|
90
85
|
# Drain any remaining data from the pipe after closing write end
|
|
91
86
|
drain_pipe(pipe) if pipe
|
|
92
87
|
end
|
|
93
88
|
|
|
94
|
-
#
|
|
95
|
-
# Called after close_write to ensure all output is captured
|
|
96
|
-
def drain_pipe(pipe)
|
|
97
|
-
return if pipe.read_closed?
|
|
98
|
-
|
|
99
|
-
loop do
|
|
100
|
-
data = pipe.read_io.read_nonblock(READ_BUFFER_SIZE)
|
|
101
|
-
debug_log("drain_pipe read #{data.bytesize} bytes for #{pipe.task_class}")
|
|
102
|
-
store_output_lines(pipe.task_class, data)
|
|
103
|
-
rescue IO::WaitReadable
|
|
104
|
-
# Check if there's more data with a very short timeout
|
|
105
|
-
ready, = IO.select([pipe.read_io], nil, nil, 0.001)
|
|
106
|
-
break unless ready
|
|
107
|
-
rescue IOError
|
|
108
|
-
# All data has been read (EOFError) or pipe was closed by another thread
|
|
109
|
-
synchronize { pipe.close_read }
|
|
110
|
-
break
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Poll all open pipes for available data
|
|
115
|
-
# Should be called periodically from the display thread
|
|
89
|
+
# Called periodically from the display thread.
|
|
116
90
|
def poll
|
|
117
91
|
readable_pipes = synchronize do
|
|
118
92
|
@pipes.values.reject { |p| p.read_closed? }.map(&:read_io)
|
|
@@ -129,25 +103,21 @@ module Taski
|
|
|
129
103
|
|
|
130
104
|
read_from_pipe(pipe)
|
|
131
105
|
end
|
|
132
|
-
rescue IOError
|
|
106
|
+
rescue IOError, Errno::EBADF
|
|
133
107
|
# Pipe was closed by another thread (drain_pipe), ignore
|
|
134
108
|
end
|
|
135
109
|
|
|
136
|
-
# Get the last output line for a task
|
|
137
|
-
# @param task_class [Class] The task class
|
|
138
|
-
# @return [String, nil] The last output line
|
|
139
110
|
def last_line_for(task_class)
|
|
140
111
|
synchronize { @recent_lines[task_class]&.last }
|
|
141
112
|
end
|
|
142
113
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
114
|
+
def read(task_class, limit: nil)
|
|
115
|
+
synchronize do
|
|
116
|
+
lines = (@recent_lines[task_class] || []).dup
|
|
117
|
+
limit ? lines.last(limit) : lines
|
|
118
|
+
end
|
|
148
119
|
end
|
|
149
120
|
|
|
150
|
-
# Close all pipes and clean up
|
|
151
121
|
def close_all
|
|
152
122
|
synchronize do
|
|
153
123
|
@pipes.each_value(&:close)
|
|
@@ -156,8 +126,6 @@ module Taski
|
|
|
156
126
|
end
|
|
157
127
|
end
|
|
158
128
|
|
|
159
|
-
# Check if there are any active (not fully closed) pipes
|
|
160
|
-
# @return [Boolean] true if there are active pipes
|
|
161
129
|
def active?
|
|
162
130
|
synchronize do
|
|
163
131
|
@pipes.values.any? { |p| !p.read_closed? }
|
|
@@ -219,9 +187,7 @@ module Taski
|
|
|
219
187
|
@original.winsize
|
|
220
188
|
end
|
|
221
189
|
|
|
222
|
-
#
|
|
223
|
-
# Used by Task#system to redirect subprocess output directly to the pipe
|
|
224
|
-
# @return [IO, nil] The write IO or nil if not capturing
|
|
190
|
+
# Used by Task#system to redirect subprocess output to the pipe.
|
|
225
191
|
def current_write_io
|
|
226
192
|
synchronize do
|
|
227
193
|
task_class = @thread_map[Thread.current]
|
|
@@ -232,7 +198,6 @@ module Taski
|
|
|
232
198
|
end
|
|
233
199
|
end
|
|
234
200
|
|
|
235
|
-
# Delegate unknown methods to original stdout
|
|
236
201
|
def method_missing(method, ...)
|
|
237
202
|
@original.send(method, ...)
|
|
238
203
|
end
|
|
@@ -243,6 +208,24 @@ module Taski
|
|
|
243
208
|
|
|
244
209
|
private
|
|
245
210
|
|
|
211
|
+
def drain_pipe(pipe)
|
|
212
|
+
return if pipe.read_closed?
|
|
213
|
+
|
|
214
|
+
loop do
|
|
215
|
+
data = pipe.read_io.read_nonblock(READ_BUFFER_SIZE)
|
|
216
|
+
Taski::Logging.debug(Taski::Logging::Events::OUTPUT_ROUTER_DRAIN_PIPE, task: pipe.task_class.name, bytes: data.bytesize)
|
|
217
|
+
store_output_lines(pipe.task_class, data)
|
|
218
|
+
rescue IO::WaitReadable
|
|
219
|
+
# Check if there's more data with a very short timeout
|
|
220
|
+
ready, = IO.select([pipe.read_io], nil, nil, 0.001)
|
|
221
|
+
break unless ready
|
|
222
|
+
rescue IOError, Errno::EBADF
|
|
223
|
+
# All data has been read (EOFError) or pipe was closed by another thread
|
|
224
|
+
synchronize { pipe.close_read }
|
|
225
|
+
break
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
246
229
|
def current_thread_pipe
|
|
247
230
|
synchronize do
|
|
248
231
|
task_class = @thread_map[Thread.current]
|
|
@@ -256,7 +239,7 @@ module Taski
|
|
|
256
239
|
store_output_lines(pipe.task_class, data)
|
|
257
240
|
rescue IO::WaitReadable
|
|
258
241
|
# No data available yet
|
|
259
|
-
rescue IOError
|
|
242
|
+
rescue IOError, Errno::EBADF
|
|
260
243
|
# Pipe closed by writer (EOFError) or by another thread, close read end
|
|
261
244
|
synchronize { pipe.close_read }
|
|
262
245
|
end
|
|
@@ -269,20 +252,20 @@ module Taski
|
|
|
269
252
|
@recent_lines[task_class] ||= []
|
|
270
253
|
lines.each do |line|
|
|
271
254
|
stripped = line.chomp
|
|
272
|
-
|
|
255
|
+
next if stripped.strip.empty?
|
|
256
|
+
@recent_lines[task_class] << stripped
|
|
257
|
+
Taski::Logging.debug(
|
|
258
|
+
Taski::Logging::Events::TASK_OUTPUT,
|
|
259
|
+
task: task_class.name,
|
|
260
|
+
line: stripped
|
|
261
|
+
)
|
|
273
262
|
end
|
|
274
|
-
# Keep only the last MAX_RECENT_LINES
|
|
275
263
|
if @recent_lines[task_class].size > MAX_RECENT_LINES
|
|
276
264
|
@recent_lines[task_class] = @recent_lines[task_class].last(MAX_RECENT_LINES)
|
|
277
265
|
end
|
|
278
|
-
|
|
266
|
+
Taski::Logging.debug(Taski::Logging::Events::OUTPUT_ROUTER_STORE_LINES, task: task_class.name, line_count: @recent_lines[task_class].size)
|
|
279
267
|
end
|
|
280
268
|
end
|
|
281
|
-
|
|
282
|
-
def debug_log(message)
|
|
283
|
-
return unless ENV["TASKI_DEBUG"]
|
|
284
|
-
warn "[TaskOutputRouter] #{message}"
|
|
285
|
-
end
|
|
286
269
|
end
|
|
287
270
|
end
|
|
288
271
|
end
|