chutzen 0.8.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.
@@ -0,0 +1,111 @@
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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq'
4
+
5
+ module Chutzen
6
+ # Sidekiq worker to perform a Chutzen job expressed in JSON.
7
+ class Worker
8
+ include Sidekiq::Worker
9
+
10
+ sidekiq_options queue: Chutzen.sidekiq_queue
11
+
12
+ def perform(json)
13
+ Chutzen.perform(JSON.parse(json))
14
+ end
15
+ end
16
+ end
data/lib/chutzen.rb ADDED
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'sidekiq/api'
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
+ # Implements a toolkit to perform batch processing.
15
+ module Chutzen
16
+ autoload :Apply, 'chutzen/apply'
17
+ autoload :Command, 'chutzen/command'
18
+ autoload :Demux, 'chutzen/demux'
19
+ autoload :Dictionary, 'chutzen/dictionary'
20
+ autoload :Expression, 'chutzen/expression'
21
+ autoload :ExpressionParser, 'chutzen/expression_parser'
22
+ autoload :Job, 'chutzen/job'
23
+ autoload :Notification, 'chutzen/notification'
24
+ autoload :RuntimeError, 'chutzen/runtime_error'
25
+ autoload :Shell, 'chutzen/shell'
26
+ autoload :Signal, 'chutzen/signal'
27
+ autoload :StandardError, 'chutzen/standard_error'
28
+ autoload :Template, 'chutzen/template'
29
+ autoload :TemplateParser, 'chutzen/template_parser'
30
+ autoload :VERSION, 'chutzen/version'
31
+ autoload :Watcher, 'chutzen/watcher'
32
+ autoload :Worker, 'chutzen/worker'
33
+
34
+ class << self
35
+ attr_accessor :sidekiq_queue
36
+ end
37
+ self.sidekiq_queue = 'chutzen'
38
+
39
+ # Enqueue a Sidekiq job that will eventually perform the job in the
40
+ # description.
41
+ def self.perform_async(description)
42
+ Chutzen::Worker.perform_async(JSON.dump(description))
43
+ end
44
+
45
+ # Enqueue a Sidekiq job that will eventually process the signal. It's
46
+ # customary for each Chutzen host to have its own control queue next to
47
+ # the regular job queue. Use the name of that queue when you want to
48
+ # target a specific host. Otherwise the signal will be processed by one
49
+ # random host.
50
+ #
51
+ # @example Stop chutzen-1-staging
52
+ # Chutzen.emit_signal('stop', queue: 'chutzen-1-staging')
53
+ def self.emit_signal(signal, queue:, expires_at: nil, expires_in: nil)
54
+ Chutzen::Signal.set(
55
+ queue: queue
56
+ ).perform_async(
57
+ signal,
58
+ expiration_since_epoch(
59
+ expires_at: expires_at,
60
+ expires_in: expires_in
61
+ )
62
+ )
63
+ end
64
+
65
+ # 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|
68
+ dictionary = Chutzen::Dictionary.new(JSON.parse(job.args.first))
69
+ expression = Chutzen::Expression.new(query, dictionary)
70
+ job.delete if expression.result
71
+ end
72
+ end
73
+
74
+ # Immediately perform the description.
75
+ def self.perform(description)
76
+ Chutzen::Job.new(description).perform
77
+ end
78
+
79
+ # Computes the expiration in seconds since epoch. When neither expires_at
80
+ # or expires_in is specified it will default to 30 seconds from now.
81
+ def self.expiration_since_epoch(expires_at: nil, expires_in: nil)
82
+ return expires_at.to_i if expires_at
83
+
84
+ expires_in ||= 30
85
+ (Time.now + expires_in).to_i
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chutzen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Manfred Stienstra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parslet
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sidekiq
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">"
74
+ - !ruby/object:Gem::Version
75
+ version: '13.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">"
81
+ - !ruby/object:Gem::Version
82
+ version: '13.0'
83
+ description: |
84
+ Chutzen is a toolkit to implement batch processing for your Ruby or
85
+ Ruby on Rails application.
86
+ email:
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - README.md
92
+ - lib/chutzen.rb
93
+ - lib/chutzen/apply.rb
94
+ - lib/chutzen/command.rb
95
+ - lib/chutzen/command/execution_failed.rb
96
+ - lib/chutzen/demux.rb
97
+ - lib/chutzen/dictionary.rb
98
+ - lib/chutzen/expression.rb
99
+ - lib/chutzen/expression/lookup_error.rb
100
+ - lib/chutzen/expression/syntax_error.rb
101
+ - lib/chutzen/expression_parser.rb
102
+ - lib/chutzen/job.rb
103
+ - lib/chutzen/notification.rb
104
+ - lib/chutzen/runtime_error.rb
105
+ - lib/chutzen/shell.rb
106
+ - lib/chutzen/signal.rb
107
+ - lib/chutzen/standard_error.rb
108
+ - lib/chutzen/template.rb
109
+ - lib/chutzen/template/syntax_error.rb
110
+ - lib/chutzen/template_parser.rb
111
+ - lib/chutzen/version.rb
112
+ - lib/chutzen/watcher.rb
113
+ - lib/chutzen/worker.rb
114
+ homepage:
115
+ licenses: []
116
+ metadata: {}
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '2.7'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 3.3.7
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: Toolkit to implement batch processing.
136
+ test_files: []