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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e078b40512684bbdd04c341199d45ed740749194fa8a1154cc3760afdd9cefeb
4
- data.tar.gz: 2eca64fa5545fc9c7722ab06fd5456458b31e7347248bb67f30237616c1c24e7
3
+ metadata.gz: aa53613b2fb5208ae0f7824b9617cde550535abaa2306e05d862cad1ae147b2b
4
+ data.tar.gz: 5c4b6dd163b1097a14a49d2b39bb94322c1378089784d8b3d8e589d0523d5404
5
5
  SHA512:
6
- metadata.gz: 2f53fd4ea333c556f30143fc580b8b5c0fb9d2450bf093c29e9c60f97147836c280b6b999ed33c13ff1c807004bb7b132f994fea0881d4ee4f3730300a974cd8
7
- data.tar.gz: 943189ce50907777c0d663e4c755277dabe35fbbeadcded292c53dd657b0a766f55aff5d88dc3cee6b47e94bf932694b0849f88fac1fa02c43e6f905c95548c5
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.
@@ -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/.freeze
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
- watcher = Watcher.new(fail_when: @fail_when, files: [@stdout, @stderr])
141
- wait_until_done(
142
- Demux.new(
143
- stdout, $stdout, @stdout, select_timeout: watcher.select_timeout
144
- ),
145
- Demux.new(
146
- stderr, $stderr, @stderr, select_timeout: watcher.select_timeout
147
- ),
148
- watcher
149
- )
150
- rescue Chutzen::Watcher::Error => e
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
- def trace_execution(&block)
202
- if defined?(::NewRelic)
203
- NewRelic::Agent::MethodTracer.trace_execution_scoped(
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
- BUFFER_SIZE = 1024
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
- def done?
21
- @done
22
- end
14
+ BUFFER_SIZE = 4096
23
15
 
24
- def tick
25
- readable = IO.select([@input], nil, nil, @select_timeout)
26
- return unless readable
16
+ def self.thread(...)
17
+ thread = Thread.new { copy(...) }
18
+ thread.abort_on_exception = true
19
+ thread
20
+ end
27
21
 
28
- buffer = readable.first.first.read_nonblock(BUFFER_SIZE)
29
- @done = buffer.nil?
30
- @output.each { |stream| stream.write(buffer) }
31
- rescue IOError
32
- @done = true
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
@@ -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/.freeze
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Chutzen
4
- VERSION = '0.8.0'
4
+ VERSION = '0.8.4'
5
5
  end
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
- def self.perform_async(description)
42
- Chutzen::Worker.perform_async(JSON.dump(description))
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
- def self.dequeue(query)
67
- Sidekiq::Queue.new(sidekiq_queue).each do |job|
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.0
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: 2023-04-02 00:00:00.000000000 Z
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: '6.0'
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: '6.0'
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: '2.7'
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.3.7
118
+ rubygems_version: 3.5.22
133
119
  signing_key:
134
120
  specification_version: 4
135
121
  summary: Toolkit to implement batch processing.
@@ -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