chutzen 0.8.0 → 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/README.md +40 -4
- data/lib/chutzen/command.rb +31 -27
- data/lib/chutzen/demux.rb +28 -18
- data/lib/chutzen/dictionary.rb +2 -2
- data/lib/chutzen/exit_status.rb +16 -0
- data/lib/chutzen/expression.rb +1 -1
- data/lib/chutzen/fail_when.rb +30 -0
- data/lib/chutzen/mock.rb +17 -0
- data/lib/chutzen/version.rb +1 -1
- data/lib/chutzen.rb +31 -14
- metadata +9 -21
- data/lib/chutzen/watcher.rb +0 -111
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5150871b332694272fd6cd676916cc8b710c13e7f4f47cdfd717b0b5517a398
|
|
4
|
+
data.tar.gz: b47c1877522ef2082e3d507a03c735a817906cd751da307aaa94bf81ae678972
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 56cbd867e92a2f6bd9e0cc3ce366e84099f7f5f82b1a46f3126c578692c1af764ccecb96156e93fa893975e6e3aad46d34f3c729166da53939faa9fe9525c5ec
|
|
7
|
+
data.tar.gz: a597dfd4348eddf963d3e815fdb6734a6f545a9c01180524cfff41593fd0e18cdea5f1de0bf5170fc9d3e854c793e81181bff526611e3d523c19066450b0e239
|
data/README.md
CHANGED
|
@@ -139,6 +139,10 @@ When *execute* contain a string it will be passed to be executed verbatim. An Ar
|
|
|
139
139
|
The *fail_when* section can contain any of the following options:
|
|
140
140
|
|
|
141
141
|
* read_timeout: Check if the stdout or stderr grows and kill the command if this doesn't happen for the specified number of seconds.
|
|
142
|
+
* ~~runtime: Check the runtime of the command and kill it when it runs for more than the specified number of seconds.~~
|
|
143
|
+
* select_timeout: Read and runtime checking use select on the process output. The timeout is normally computed as `[read_timeout, runtime].compact.min / 2.0` but you can force a different value if necessary.
|
|
144
|
+
|
|
145
|
+
> The runtime option is currently accepted, but not enforced. This may return in the future.
|
|
142
146
|
|
|
143
147
|
### Merge description
|
|
144
148
|
|
|
@@ -229,7 +233,7 @@ Now we can schedule the `Chutzen::Signal` worker through the `emit_signal` conve
|
|
|
229
233
|
|
|
230
234
|
Downside of using a job is that you can enqueue multiple signals which may keep stopping Chutzen.
|
|
231
235
|
|
|
232
|
-
20.times { Chutzen.emit_signal('stop', queue: 'chutzen-13') }
|
|
236
|
+
20.times { Chutzen.emit_signal('stop', queue: 'chutzen-13') }
|
|
233
237
|
|
|
234
238
|
That is why a `Chutzen::Signal` worker will, by default, only process within 30 seconds after is was enqueued. The expiration time can be expressed in a number of ways if you don't like the default.
|
|
235
239
|
|
|
@@ -239,6 +243,38 @@ That is why a `Chutzen::Signal` worker will, by default, only process within 30
|
|
|
239
243
|
set(queue: 'chutzen-5-test').
|
|
240
244
|
perform_async('stop', (Time.now + 10).to_i)
|
|
241
245
|
|
|
242
|
-
##
|
|
243
|
-
|
|
244
|
-
Chutzen can
|
|
246
|
+
## Mocking
|
|
247
|
+
|
|
248
|
+
Chutzen descriptions can get relatively complex and you may want to use a mock to test logic. You can do this by mocking commands:
|
|
249
|
+
|
|
250
|
+
Chutzen.mocked(
|
|
251
|
+
'ls' => lamda do
|
|
252
|
+
# Generally you only access stdout and stderr from mocks and return
|
|
253
|
+
# the exit status code.
|
|
254
|
+
stdout.puts("app test")
|
|
255
|
+
0
|
|
256
|
+
end,
|
|
257
|
+
'unknown' => lambda do
|
|
258
|
+
# The mock uses the basepath of the executed path to look up the
|
|
259
|
+
# Proc. So /path/to/unknown becomes unknown. Use the 127 exit status
|
|
260
|
+
# to simulate a missing binary.
|
|
261
|
+
127
|
|
262
|
+
end,
|
|
263
|
+
# The value returned from the Proc is cast to an integer, so this is
|
|
264
|
+
# zero.
|
|
265
|
+
'zero' => -> {},
|
|
266
|
+
# You have full access to the Chutzen::Command internals, but these
|
|
267
|
+
# mocks may break when internals change.
|
|
268
|
+
'hack' => lambda do
|
|
269
|
+
@dictionary['width'] = '201'
|
|
270
|
+
0
|
|
271
|
+
end,
|
|
272
|
+
) do
|
|
273
|
+
# Any command executed in the same process/thread will now use the mocked
|
|
274
|
+
# commands instead of using Open3 to execute it.
|
|
275
|
+
Command.new(
|
|
276
|
+
{ "execute" => "ls" },
|
|
277
|
+
dictionary:,
|
|
278
|
+
work_path:
|
|
279
|
+
).perform
|
|
280
|
+
end
|
data/lib/chutzen/command.rb
CHANGED
|
@@ -6,7 +6,7 @@ require 'stringio'
|
|
|
6
6
|
module Chutzen
|
|
7
7
|
# Holds a description for a command and executes it.
|
|
8
8
|
class Command
|
|
9
|
-
ALLOW_EVAL = /\A[\d<>=\s]+\z
|
|
9
|
+
ALLOW_EVAL = /\A[\d<>=\s]+\z/
|
|
10
10
|
|
|
11
11
|
autoload :ExecutionFailed, 'chutzen/command/execution_failed'
|
|
12
12
|
|
|
@@ -58,11 +58,19 @@ module Chutzen
|
|
|
58
58
|
def perform
|
|
59
59
|
return if skip?
|
|
60
60
|
|
|
61
|
-
result = @execute ?
|
|
61
|
+
result = @execute ? perform_command_or_mock : self
|
|
62
62
|
merge_remember_into_dictionary
|
|
63
63
|
result
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
def perform_command_or_mock
|
|
67
|
+
if Chutzen.mock
|
|
68
|
+
perform_mock
|
|
69
|
+
else
|
|
70
|
+
perform_command
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
66
74
|
def perform_command
|
|
67
75
|
trace_execution do
|
|
68
76
|
# We use the STOP signal to instruct Sidekiq to stop reading from the queue and the TERM
|
|
@@ -87,6 +95,13 @@ module Chutzen
|
|
|
87
95
|
end
|
|
88
96
|
end
|
|
89
97
|
|
|
98
|
+
def perform_mock
|
|
99
|
+
@exit_status = ExitStatus.new(Chutzen.mock.perform(self).to_i)
|
|
100
|
+
verify_exit_status
|
|
101
|
+
merge_output_into_dictionary
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
90
105
|
# Returns the result expressed by the result section but with its variables
|
|
91
106
|
# instantiated.
|
|
92
107
|
def result
|
|
@@ -137,18 +152,17 @@ module Chutzen
|
|
|
137
152
|
end
|
|
138
153
|
|
|
139
154
|
def monitor(stdout, stderr, thread)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Demux.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Process.kill('KILL', thread.pid)
|
|
155
|
+
fail_when = FailWhen.new(@fail_when)
|
|
156
|
+
[
|
|
157
|
+
Demux.thread(stdout, $stdout, @stdout, select_timeout: fail_when.select_timeout),
|
|
158
|
+
Demux.thread(stderr, $stderr, @stderr, select_timeout: fail_when.select_timeout)
|
|
159
|
+
].each(&:join)
|
|
160
|
+
rescue Chutzen::Demux::Error => e
|
|
161
|
+
begin
|
|
162
|
+
Process.kill('KILL', thread.pid)
|
|
163
|
+
rescue Errno::ESRCH
|
|
164
|
+
# Means the process doesn't exist so it already stopped.
|
|
165
|
+
end
|
|
152
166
|
raise ExecutionFailed.new(e.message, command: self) unless optional?
|
|
153
167
|
end
|
|
154
168
|
|
|
@@ -198,19 +212,9 @@ module Chutzen
|
|
|
198
212
|
end
|
|
199
213
|
end
|
|
200
214
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
transaction_scope,
|
|
205
|
-
&block
|
|
206
|
-
)
|
|
207
|
-
else
|
|
208
|
-
yield
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def transaction_scope
|
|
213
|
-
%W[Custom/Command/#{name}]
|
|
215
|
+
# Placeholder for plugging an exception handler.
|
|
216
|
+
def trace_execution
|
|
217
|
+
yield
|
|
214
218
|
end
|
|
215
219
|
end
|
|
216
220
|
end
|
data/lib/chutzen/demux.rb
CHANGED
|
@@ -8,28 +8,38 @@ module Chutzen
|
|
|
8
8
|
# demux.tick
|
|
9
9
|
# end
|
|
10
10
|
class Demux
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def initialize(input, *output, select_timeout: nil)
|
|
14
|
-
@input = input
|
|
15
|
-
@output = output
|
|
16
|
-
@select_timeout = select_timeout
|
|
17
|
-
@done = false
|
|
11
|
+
class Error < StandardError
|
|
18
12
|
end
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
@done
|
|
22
|
-
end
|
|
14
|
+
BUFFER_SIZE = 4096
|
|
23
15
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
def self.thread(...)
|
|
17
|
+
thread = Thread.new { copy(...) }
|
|
18
|
+
thread.abort_on_exception = true
|
|
19
|
+
thread
|
|
20
|
+
end
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
# rubocop:disable Metrics/MethodLength
|
|
23
|
+
def self.copy(input, *output, select_timeout: nil)
|
|
24
|
+
done = false
|
|
25
|
+
until done
|
|
26
|
+
result = input.read_nonblock(BUFFER_SIZE, exception: false)
|
|
27
|
+
case result
|
|
28
|
+
when String
|
|
29
|
+
output.each do |stream|
|
|
30
|
+
stream.write(result)
|
|
31
|
+
stream.flush
|
|
32
|
+
end
|
|
33
|
+
when :wait_readable
|
|
34
|
+
IO.select([input], nil, nil, select_timeout)
|
|
35
|
+
when nil
|
|
36
|
+
# End of file.
|
|
37
|
+
else
|
|
38
|
+
raise Error, "Demuxer encountered unsupported read_nonblock return value `#{result}'"
|
|
39
|
+
end
|
|
40
|
+
done = input.eof?
|
|
41
|
+
end
|
|
33
42
|
end
|
|
43
|
+
# rubocop:enable Metrics/MethodLength
|
|
34
44
|
end
|
|
35
45
|
end
|
data/lib/chutzen/dictionary.rb
CHANGED
|
@@ -82,7 +82,7 @@ module Chutzen
|
|
|
82
82
|
else
|
|
83
83
|
data.each do |name, value|
|
|
84
84
|
path = search(value, key)
|
|
85
|
-
return
|
|
85
|
+
return path << name if path
|
|
86
86
|
end
|
|
87
87
|
nil
|
|
88
88
|
end
|
|
@@ -91,7 +91,7 @@ module Chutzen
|
|
|
91
91
|
def self.search_array(data, key)
|
|
92
92
|
data.each.with_index do |item, index|
|
|
93
93
|
path = search(item, key)
|
|
94
|
-
return
|
|
94
|
+
return path << index if path
|
|
95
95
|
end
|
|
96
96
|
nil
|
|
97
97
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'parslet'
|
|
4
|
+
|
|
5
|
+
module Chutzen
|
|
6
|
+
# Used to mock an exit status normally provided by executing something through Open3.
|
|
7
|
+
ExitStatus = Data.define(:status) do
|
|
8
|
+
def to_i
|
|
9
|
+
status.to_i
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def success?
|
|
13
|
+
status.zero?
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/chutzen/expression.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Chutzen
|
|
|
9
9
|
autoload :SyntaxError, 'chutzen/expression/syntax_error'
|
|
10
10
|
autoload :LookupError, 'chutzen/expression/lookup_error'
|
|
11
11
|
|
|
12
|
-
INTEGER_RE = /\A\d+\z
|
|
12
|
+
INTEGER_RE = /\A\d+\z/
|
|
13
13
|
OPERATIONS = {
|
|
14
14
|
'=' => ->(left, right) { left == right },
|
|
15
15
|
'&' => ->(left, right) { left & right },
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chutzen
|
|
4
|
+
# Value class to contain details about when to fail a command.
|
|
5
|
+
#
|
|
6
|
+
# FailWhen.new(
|
|
7
|
+
# {
|
|
8
|
+
# 'read_timeout' => 2,
|
|
9
|
+
# 'runtime' => 60
|
|
10
|
+
# }
|
|
11
|
+
# )
|
|
12
|
+
class FailWhen
|
|
13
|
+
attr_reader :read_timeout, :runtime
|
|
14
|
+
|
|
15
|
+
def initialize(fail_when = nil)
|
|
16
|
+
fail_when&.each do |name, value|
|
|
17
|
+
instance_variable_set("@#{name}", value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def select_timeout
|
|
22
|
+
return @select_timeout if defined?(@select_timeout)
|
|
23
|
+
|
|
24
|
+
minimal_timeout = [read_timeout, runtime].compact.min
|
|
25
|
+
return unless minimal_timeout
|
|
26
|
+
|
|
27
|
+
minimal_timeout / 2.0
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/chutzen/mock.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chutzen
|
|
4
|
+
# Keeps mocked commands and executes Command instances against them.
|
|
5
|
+
class Mock
|
|
6
|
+
attr_reader :mocks
|
|
7
|
+
|
|
8
|
+
def initialize(mocks)
|
|
9
|
+
@mocks = mocks
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def perform(command)
|
|
13
|
+
name = File.basename(command.to_s.split(' ', 2).first.to_s)
|
|
14
|
+
command.instance_exec(&mocks.fetch(name))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/chutzen/version.rb
CHANGED
data/lib/chutzen.rb
CHANGED
|
@@ -3,23 +3,18 @@
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'sidekiq/api'
|
|
5
5
|
|
|
6
|
-
# Disabled to allow optional loading of the New Relic gem.
|
|
7
|
-
# rubocop:disable Lint/SuppressedException
|
|
8
|
-
begin
|
|
9
|
-
require 'newrelic_rpm'
|
|
10
|
-
rescue LoadError
|
|
11
|
-
end
|
|
12
|
-
# rubocop:enable Lint/SuppressedException
|
|
13
|
-
|
|
14
6
|
# Implements a toolkit to perform batch processing.
|
|
15
7
|
module Chutzen
|
|
16
8
|
autoload :Apply, 'chutzen/apply'
|
|
17
9
|
autoload :Command, 'chutzen/command'
|
|
18
10
|
autoload :Demux, 'chutzen/demux'
|
|
19
11
|
autoload :Dictionary, 'chutzen/dictionary'
|
|
12
|
+
autoload :ExitStatus, 'chutzen/exit_status'
|
|
20
13
|
autoload :Expression, 'chutzen/expression'
|
|
21
14
|
autoload :ExpressionParser, 'chutzen/expression_parser'
|
|
15
|
+
autoload :FailWhen, 'chutzen/fail_when'
|
|
22
16
|
autoload :Job, 'chutzen/job'
|
|
17
|
+
autoload :Mock, 'chutzen/mock'
|
|
23
18
|
autoload :Notification, 'chutzen/notification'
|
|
24
19
|
autoload :RuntimeError, 'chutzen/runtime_error'
|
|
25
20
|
autoload :Shell, 'chutzen/shell'
|
|
@@ -28,18 +23,29 @@ module Chutzen
|
|
|
28
23
|
autoload :Template, 'chutzen/template'
|
|
29
24
|
autoload :TemplateParser, 'chutzen/template_parser'
|
|
30
25
|
autoload :VERSION, 'chutzen/version'
|
|
31
|
-
autoload :Watcher, 'chutzen/watcher'
|
|
32
26
|
autoload :Worker, 'chutzen/worker'
|
|
33
27
|
|
|
34
28
|
class << self
|
|
35
|
-
attr_accessor :sidekiq_queue
|
|
29
|
+
attr_accessor :sidekiq_queue, :mock
|
|
36
30
|
end
|
|
37
31
|
self.sidekiq_queue = 'chutzen'
|
|
32
|
+
self.mock = nil
|
|
38
33
|
|
|
39
34
|
# Enqueue a Sidekiq job that will eventually perform the job in the
|
|
40
35
|
# description.
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
#
|
|
37
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
|
38
|
+
def self.perform_async(description, queue: nil)
|
|
39
|
+
async_worker(queue: queue).perform_async(JSON.dump(description))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Configures a worker class for asynchronous processing.
|
|
43
|
+
#
|
|
44
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
|
45
|
+
def self.async_worker(queue: nil)
|
|
46
|
+
worker = Chutzen::Worker
|
|
47
|
+
worker = worker.set(queue: queue) if queue
|
|
48
|
+
worker
|
|
43
49
|
end
|
|
44
50
|
|
|
45
51
|
# Enqueue a Sidekiq job that will eventually process the signal. It's
|
|
@@ -63,8 +69,11 @@ module Chutzen
|
|
|
63
69
|
end
|
|
64
70
|
|
|
65
71
|
# Dequeue all jobs that match the query. Uses Chutzen's own query format.
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
#
|
|
73
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
|
74
|
+
def self.dequeue(query, queue: nil)
|
|
75
|
+
queue ||= sidekiq_queue
|
|
76
|
+
Sidekiq::Queue.new(queue).each do |job|
|
|
68
77
|
dictionary = Chutzen::Dictionary.new(JSON.parse(job.args.first))
|
|
69
78
|
expression = Chutzen::Expression.new(query, dictionary)
|
|
70
79
|
job.delete if expression.result
|
|
@@ -84,4 +93,12 @@ module Chutzen
|
|
|
84
93
|
expires_in ||= 30
|
|
85
94
|
(Time.now + expires_in).to_i
|
|
86
95
|
end
|
|
96
|
+
|
|
97
|
+
# Sets the global mock instance for the duration of the block.
|
|
98
|
+
def self.mocked(**)
|
|
99
|
+
self.mock = Chutzen::Mock.new(**)
|
|
100
|
+
yield
|
|
101
|
+
ensure
|
|
102
|
+
self.mock = nil
|
|
103
|
+
end
|
|
87
104
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chutzen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Manfred Stienstra
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: parslet
|
|
@@ -30,28 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '7.0'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: newrelic_rpm
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '6.10'
|
|
48
|
-
type: :runtime
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '6.10'
|
|
40
|
+
version: '7.0'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
42
|
name: minitest
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -95,11 +81,14 @@ files:
|
|
|
95
81
|
- lib/chutzen/command/execution_failed.rb
|
|
96
82
|
- lib/chutzen/demux.rb
|
|
97
83
|
- lib/chutzen/dictionary.rb
|
|
84
|
+
- lib/chutzen/exit_status.rb
|
|
98
85
|
- lib/chutzen/expression.rb
|
|
99
86
|
- lib/chutzen/expression/lookup_error.rb
|
|
100
87
|
- lib/chutzen/expression/syntax_error.rb
|
|
101
88
|
- lib/chutzen/expression_parser.rb
|
|
89
|
+
- lib/chutzen/fail_when.rb
|
|
102
90
|
- lib/chutzen/job.rb
|
|
91
|
+
- lib/chutzen/mock.rb
|
|
103
92
|
- lib/chutzen/notification.rb
|
|
104
93
|
- lib/chutzen/runtime_error.rb
|
|
105
94
|
- lib/chutzen/shell.rb
|
|
@@ -109,7 +98,6 @@ files:
|
|
|
109
98
|
- lib/chutzen/template/syntax_error.rb
|
|
110
99
|
- lib/chutzen/template_parser.rb
|
|
111
100
|
- lib/chutzen/version.rb
|
|
112
|
-
- lib/chutzen/watcher.rb
|
|
113
101
|
- lib/chutzen/worker.rb
|
|
114
102
|
homepage:
|
|
115
103
|
licenses: []
|
|
@@ -122,14 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
122
110
|
requirements:
|
|
123
111
|
- - ">="
|
|
124
112
|
- !ruby/object:Gem::Version
|
|
125
|
-
version: '2
|
|
113
|
+
version: '3.2'
|
|
126
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
115
|
requirements:
|
|
128
116
|
- - ">="
|
|
129
117
|
- !ruby/object:Gem::Version
|
|
130
118
|
version: '0'
|
|
131
119
|
requirements: []
|
|
132
|
-
rubygems_version: 3.
|
|
120
|
+
rubygems_version: 3.5.22
|
|
133
121
|
signing_key:
|
|
134
122
|
specification_version: 4
|
|
135
123
|
summary: Toolkit to implement batch processing.
|
data/lib/chutzen/watcher.rb
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Chutzen
|
|
4
|
-
# Watches the current process to make sure it's still running as intended.
|
|
5
|
-
#
|
|
6
|
-
# You can set a watcher to make sure a list of files increases in size at
|
|
7
|
-
# least every ‘read_timeout’ seconds.
|
|
8
|
-
#
|
|
9
|
-
# watcher = Watcher.new(
|
|
10
|
-
# fail_when: {
|
|
11
|
-
# 'read_timeout' => 2,
|
|
12
|
-
# 'runtime' => 60
|
|
13
|
-
# },
|
|
14
|
-
# files: [file]
|
|
15
|
-
# )
|
|
16
|
-
# until watcher.done?
|
|
17
|
-
# watcher.tick
|
|
18
|
-
# end
|
|
19
|
-
class Watcher
|
|
20
|
-
class Error < StandardError
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
attr_reader :read_timeout, :runtime
|
|
24
|
-
|
|
25
|
-
def initialize(fail_when: nil, files: nil)
|
|
26
|
-
fail_when&.each do |name, value|
|
|
27
|
-
instance_variable_set("@#{name}", value)
|
|
28
|
-
end
|
|
29
|
-
@started_at = now
|
|
30
|
-
@files = files
|
|
31
|
-
reset
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def select_timeout
|
|
35
|
-
return @select_timeout if defined?(@select_timeout)
|
|
36
|
-
|
|
37
|
-
minimal_timeout = [read_timeout, runtime].compact.min
|
|
38
|
-
return unless minimal_timeout
|
|
39
|
-
|
|
40
|
-
minimal_timeout / 2.0
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def done?
|
|
44
|
-
# Because demuxers and the watcher share an interface we need to the
|
|
45
|
-
# watcher to explain the thread doesn't have to wait for it.
|
|
46
|
-
true
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Raises an exception when any of the failure conditions is reached.
|
|
50
|
-
def tick
|
|
51
|
-
compute_last_read
|
|
52
|
-
verify_last_read
|
|
53
|
-
verify_runtime
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def total_bytes_read
|
|
59
|
-
return unless @files
|
|
60
|
-
|
|
61
|
-
@files.inject(0) { |total, file| total + file.size }
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def last_read_ago
|
|
65
|
-
now - @last_read_at
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def compute_last_read
|
|
69
|
-
return unless read_timeout && @files
|
|
70
|
-
|
|
71
|
-
bytes_read = total_bytes_read
|
|
72
|
-
@last_read_at = now if bytes_read != @bytes_read
|
|
73
|
-
@bytes_read = bytes_read
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def verify_last_read
|
|
77
|
-
return unless read_timeout && @files
|
|
78
|
-
return if last_read_ago < read_timeout
|
|
79
|
-
|
|
80
|
-
raise(
|
|
81
|
-
Error,
|
|
82
|
-
"Command did not write to its output for more than #{read_timeout} " \
|
|
83
|
-
'seconds.'
|
|
84
|
-
)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def ran
|
|
88
|
-
now - @started_at
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def verify_runtime
|
|
92
|
-
return unless runtime
|
|
93
|
-
return if ran < runtime
|
|
94
|
-
|
|
95
|
-
raise(
|
|
96
|
-
Error,
|
|
97
|
-
"Command did not finish within the alotted runtime #{runtime} " \
|
|
98
|
-
'seconds.'
|
|
99
|
-
)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def reset
|
|
103
|
-
@last_read_at = now
|
|
104
|
-
@bytes_read = 0
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def now
|
|
108
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|