chutzen 0.8.0 → 0.8.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/chutzen/command.rb +15 -26
- data/lib/chutzen/demux.rb +28 -18
- data/lib/chutzen/expression.rb +1 -1
- data/lib/chutzen/fail_when.rb +30 -0
- data/lib/chutzen/version.rb +1 -1
- data/lib/chutzen.rb +19 -13
- metadata +7 -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: aa53613b2fb5208ae0f7824b9617cde550535abaa2306e05d862cad1ae147b2b
|
4
|
+
data.tar.gz: 5c4b6dd163b1097a14a49d2b39bb94322c1378089784d8b3d8e589d0523d5404
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3195bbf164332b41112fde4ba6a0d88cfcde0cab7adf278daf64d1197bd30f70529af1630fa3fa7e9cc889f99b10bee3253f8ce98bc6ac42cfc2c9ecbeab5e26
|
7
|
+
data.tar.gz: f871912fa5c9df6eb51124c4a457f9e7e20dd6609e0558ed52505ff8f81f09236c7f8bbb194e1d4319ef3375c221b31025417747b92f375c72a37938911f715d
|
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
|
|
@@ -238,7 +242,3 @@ That is why a `Chutzen::Signal` worker will, by default, only process within 30
|
|
238
242
|
Chutzen::Signal.
|
239
243
|
set(queue: 'chutzen-5-test').
|
240
244
|
perform_async('stop', (Time.now + 10).to_i)
|
241
|
-
|
242
|
-
## New Relic integrations
|
243
|
-
|
244
|
-
Chutzen can send custom metrics and exceptions to New Relic. The New Relic gem is ‘dynamically’ loaded so you have to make sure it's installed for this to work. You also have to provide a valid `confg/newrelic.yml` file relative to the working directory where Chutzen is started.
|
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
|
|
@@ -137,18 +137,17 @@ module Chutzen
|
|
137
137
|
end
|
138
138
|
|
139
139
|
def monitor(stdout, stderr, thread)
|
140
|
-
|
141
|
-
|
142
|
-
Demux.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
Process.kill('KILL', thread.pid)
|
140
|
+
fail_when = FailWhen.new(@fail_when)
|
141
|
+
[
|
142
|
+
Demux.thread(stdout, $stdout, @stdout, select_timeout: fail_when.select_timeout),
|
143
|
+
Demux.thread(stderr, $stderr, @stderr, select_timeout: fail_when.select_timeout)
|
144
|
+
].each(&:join)
|
145
|
+
rescue Chutzen::Demux::Error => e
|
146
|
+
begin
|
147
|
+
Process.kill('KILL', thread.pid)
|
148
|
+
rescue Errno::ESRCH
|
149
|
+
# Means the process doesn't exist so it already stopped.
|
150
|
+
end
|
152
151
|
raise ExecutionFailed.new(e.message, command: self) unless optional?
|
153
152
|
end
|
154
153
|
|
@@ -198,19 +197,9 @@ module Chutzen
|
|
198
197
|
end
|
199
198
|
end
|
200
199
|
|
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}]
|
200
|
+
# Placeholder for plugging an exception handler.
|
201
|
+
def trace_execution
|
202
|
+
yield
|
214
203
|
end
|
215
204
|
end
|
216
205
|
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/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/version.rb
CHANGED
data/lib/chutzen.rb
CHANGED
@@ -3,14 +3,6 @@
|
|
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'
|
@@ -19,6 +11,7 @@ module Chutzen
|
|
19
11
|
autoload :Dictionary, 'chutzen/dictionary'
|
20
12
|
autoload :Expression, 'chutzen/expression'
|
21
13
|
autoload :ExpressionParser, 'chutzen/expression_parser'
|
14
|
+
autoload :FailWhen, 'chutzen/fail_when'
|
22
15
|
autoload :Job, 'chutzen/job'
|
23
16
|
autoload :Notification, 'chutzen/notification'
|
24
17
|
autoload :RuntimeError, 'chutzen/runtime_error'
|
@@ -28,7 +21,6 @@ module Chutzen
|
|
28
21
|
autoload :Template, 'chutzen/template'
|
29
22
|
autoload :TemplateParser, 'chutzen/template_parser'
|
30
23
|
autoload :VERSION, 'chutzen/version'
|
31
|
-
autoload :Watcher, 'chutzen/watcher'
|
32
24
|
autoload :Worker, 'chutzen/worker'
|
33
25
|
|
34
26
|
class << self
|
@@ -38,8 +30,19 @@ module Chutzen
|
|
38
30
|
|
39
31
|
# Enqueue a Sidekiq job that will eventually perform the job in the
|
40
32
|
# description.
|
41
|
-
|
42
|
-
|
33
|
+
#
|
34
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
35
|
+
def self.perform_async(description, queue: nil)
|
36
|
+
async_worker(queue: queue).perform_async(JSON.dump(description))
|
37
|
+
end
|
38
|
+
|
39
|
+
# Configures a worker class for asynchronous processing.
|
40
|
+
#
|
41
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
42
|
+
def self.async_worker(queue: nil)
|
43
|
+
worker = Chutzen::Worker
|
44
|
+
worker = worker.set(queue: queue) if queue
|
45
|
+
worker
|
43
46
|
end
|
44
47
|
|
45
48
|
# Enqueue a Sidekiq job that will eventually process the signal. It's
|
@@ -63,8 +66,11 @@ module Chutzen
|
|
63
66
|
end
|
64
67
|
|
65
68
|
# Dequeue all jobs that match the query. Uses Chutzen's own query format.
|
66
|
-
|
67
|
-
|
69
|
+
#
|
70
|
+
# * +queue+: Override +Chutzen.sidekiq_queue+.
|
71
|
+
def self.dequeue(query, queue: nil)
|
72
|
+
queue ||= sidekiq_queue
|
73
|
+
Sidekiq::Queue.new(queue).each do |job|
|
68
74
|
dictionary = Chutzen::Dictionary.new(JSON.parse(job.args.first))
|
69
75
|
expression = Chutzen::Expression.new(query, dictionary)
|
70
76
|
job.delete if expression.result
|
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.8.
|
4
|
+
version: 0.8.4
|
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: 2025-01-06 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
|
@@ -99,6 +85,7 @@ files:
|
|
99
85
|
- lib/chutzen/expression/lookup_error.rb
|
100
86
|
- lib/chutzen/expression/syntax_error.rb
|
101
87
|
- lib/chutzen/expression_parser.rb
|
88
|
+
- lib/chutzen/fail_when.rb
|
102
89
|
- lib/chutzen/job.rb
|
103
90
|
- lib/chutzen/notification.rb
|
104
91
|
- lib/chutzen/runtime_error.rb
|
@@ -109,7 +96,6 @@ files:
|
|
109
96
|
- lib/chutzen/template/syntax_error.rb
|
110
97
|
- lib/chutzen/template_parser.rb
|
111
98
|
- lib/chutzen/version.rb
|
112
|
-
- lib/chutzen/watcher.rb
|
113
99
|
- lib/chutzen/worker.rb
|
114
100
|
homepage:
|
115
101
|
licenses: []
|
@@ -122,14 +108,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
108
|
requirements:
|
123
109
|
- - ">="
|
124
110
|
- !ruby/object:Gem::Version
|
125
|
-
version: '
|
111
|
+
version: '3.1'
|
126
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
113
|
requirements:
|
128
114
|
- - ">="
|
129
115
|
- !ruby/object:Gem::Version
|
130
116
|
version: '0'
|
131
117
|
requirements: []
|
132
|
-
rubygems_version: 3.
|
118
|
+
rubygems_version: 3.5.22
|
133
119
|
signing_key:
|
134
120
|
specification_version: 4
|
135
121
|
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
|