sigurd 0.0.1
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 +7 -0
- data/.circleci/config.yml +83 -0
- data/.gitignore +39 -0
- data/.gitmodules +0 -0
- data/.rspec +1 -0
- data/.rubocop.yml +312 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +77 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +103 -0
- data/Guardfile +22 -0
- data/LICENSE.md +195 -0
- data/README.md +2 -0
- data/lib/sigurd.rb +7 -0
- data/lib/sigurd/executor.rb +125 -0
- data/lib/sigurd/signal_handler.rb +66 -0
- data/lib/sigurd/version.rb +5 -0
- data/sigurd.gemspec +31 -0
- data/spec/executor_spec.rb +54 -0
- data/spec/signal_handler_spec.rb +18 -0
- data/spec/spec_helper.rb +58 -0
- metadata +194 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sigurd
|
4
|
+
# Class that takes any object with a "start" and "stop" method and catches
|
5
|
+
# signals to ask them to stop nicely.
|
6
|
+
class SignalHandler
|
7
|
+
SIGNALS = %i(INT TERM QUIT).freeze
|
8
|
+
|
9
|
+
# Takes any object that responds to the `start` and `stop` methods.
|
10
|
+
# @param runner[#start, #stop]
|
11
|
+
def initialize(runner)
|
12
|
+
@signal_queue = []
|
13
|
+
@reader, @writer = IO.pipe
|
14
|
+
@runner = runner
|
15
|
+
end
|
16
|
+
|
17
|
+
# Run the runner.
|
18
|
+
def run!
|
19
|
+
setup_signals
|
20
|
+
@runner.start
|
21
|
+
|
22
|
+
loop do
|
23
|
+
case signal_queue.pop
|
24
|
+
when *SIGNALS
|
25
|
+
@runner.stop
|
26
|
+
break
|
27
|
+
else
|
28
|
+
ready = IO.select([reader, writer])
|
29
|
+
|
30
|
+
# drain the self-pipe so it won't be returned again next time
|
31
|
+
reader.read_nonblock(1) if ready[0].include?(reader)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :reader, :writer, :signal_queue, :executor
|
39
|
+
|
40
|
+
# https://stackoverflow.com/questions/29568298/run-code-when-signal-is-sent-but-do-not-trap-the-signal-in-ruby
|
41
|
+
def prepend_handler(signal)
|
42
|
+
previous = Signal.trap(signal) do
|
43
|
+
previous = -> { raise SignalException, signal } unless previous.respond_to?(:call)
|
44
|
+
yield
|
45
|
+
previous.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Trap signals using the self-pipe trick.
|
50
|
+
def setup_signals
|
51
|
+
at_exit { @runner&.stop }
|
52
|
+
SIGNALS.each do |signal|
|
53
|
+
prepend_handler(signal) do
|
54
|
+
unblock(signal)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Save the signal to the queue and continue on.
|
60
|
+
# @param signal [Symbol]
|
61
|
+
def unblock(signal)
|
62
|
+
writer.write_nonblock('.')
|
63
|
+
signal_queue << signal
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/sigurd.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'sigurd/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'sigurd'
|
9
|
+
spec.version = Sigurd::VERSION
|
10
|
+
spec.authors = ['Daniel Orner']
|
11
|
+
spec.email = ['daniel.orner@flipp.com']
|
12
|
+
spec.summary = 'Small gem to manage executing looping processes and signal handling.'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'Apache-2.0'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_runtime_dependency('concurrent-ruby', '~> 1')
|
22
|
+
spec.add_runtime_dependency('exponential-backoff')
|
23
|
+
|
24
|
+
spec.add_development_dependency('guard', '~> 2')
|
25
|
+
spec.add_development_dependency('guard-rspec', '~> 4')
|
26
|
+
spec.add_development_dependency('guard-rubocop', '~> 1')
|
27
|
+
spec.add_development_dependency('rspec', '~> 3')
|
28
|
+
spec.add_development_dependency('rspec_junit_formatter', '~>0.3')
|
29
|
+
spec.add_development_dependency('rubocop', '~> 0.72')
|
30
|
+
spec.add_development_dependency('rubocop-rspec', '~> 1.27')
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'sigurd/executor'
|
5
|
+
|
6
|
+
RSpec.describe Sigurd::Executor do
|
7
|
+
|
8
|
+
let(:executor) { described_class.new(runners) }
|
9
|
+
let(:runners) { (1..2).map { |i| TestRunners::TestRunner.new(i) } }
|
10
|
+
|
11
|
+
it 'starts and stops configured runners' do
|
12
|
+
runners.each do |r|
|
13
|
+
expect(r.started).to be_falsey
|
14
|
+
expect(r.stopped).to be_falsey
|
15
|
+
end
|
16
|
+
executor.start
|
17
|
+
wait_for do
|
18
|
+
runners.each do |r|
|
19
|
+
expect(r.started).to be_truthy
|
20
|
+
expect(r.stopped).to be_falsey
|
21
|
+
end
|
22
|
+
executor.stop
|
23
|
+
runners.each do |r|
|
24
|
+
expect(r.started).to be_truthy
|
25
|
+
expect(r.stopped).to be_truthy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sleeps X seconds' do
|
31
|
+
executor = described_class.new(runners, sleep_seconds: 5)
|
32
|
+
allow(executor).to receive(:handle_crashed_runner).and_call_original
|
33
|
+
expect(executor).to receive(:sleep).with(5).twice
|
34
|
+
runners.each { |r| r.should_error = true }
|
35
|
+
executor.start
|
36
|
+
wait_for do
|
37
|
+
runners.each { |r| expect(r.started).to be_truthy }
|
38
|
+
executor.stop
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'reconnects crashed runners' do
|
43
|
+
allow(executor).to receive(:handle_crashed_runner).and_call_original
|
44
|
+
runners.each { |r| r.should_error = true }
|
45
|
+
executor.start
|
46
|
+
wait_for do
|
47
|
+
expect(executor).to have_received(:handle_crashed_runner).with(runners[0], anything, 0).once
|
48
|
+
expect(executor).to have_received(:handle_crashed_runner).with(runners[1], anything, 0).once
|
49
|
+
runners.each { |r| expect(r.started).to be_truthy }
|
50
|
+
executor.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sigurd/signal_handler'
|
4
|
+
|
5
|
+
RSpec.describe Sigurd::SignalHandler do
|
6
|
+
describe '#run!' do
|
7
|
+
|
8
|
+
it 'starts and stops the runner' do
|
9
|
+
runner = TestRunners::TestRunner.new
|
10
|
+
expect(runner).to receive(:start)
|
11
|
+
expect(runner).to receive(:stop)
|
12
|
+
|
13
|
+
signal_handler = described_class.new(runner)
|
14
|
+
signal_handler.send(:unblock, described_class::SIGNALS.first)
|
15
|
+
signal_handler.run!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
4
|
+
|
5
|
+
# Helpers for Executor/DbProducer
|
6
|
+
module TestRunners
|
7
|
+
# Execute a block until it stops failing. This is helpful for testing threads
|
8
|
+
# where we need to wait for them to continue but don't want to rely on
|
9
|
+
# sleeping for X seconds, which is crazy brittle and slow.
|
10
|
+
def wait_for
|
11
|
+
start_time = Time.now
|
12
|
+
begin
|
13
|
+
yield
|
14
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
15
|
+
raise if Time.now - start_time > 2 # 2 seconds is probably plenty of time! <_<
|
16
|
+
|
17
|
+
sleep(0.1)
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Test runner
|
23
|
+
class TestRunner
|
24
|
+
attr_accessor :id, :started, :stopped, :should_error
|
25
|
+
|
26
|
+
# :nodoc:
|
27
|
+
def initialize(id=nil)
|
28
|
+
@id = id
|
29
|
+
end
|
30
|
+
|
31
|
+
# :nodoc:
|
32
|
+
def start
|
33
|
+
if @should_error
|
34
|
+
@should_error = false
|
35
|
+
raise 'OH NOES'
|
36
|
+
end
|
37
|
+
@started = true
|
38
|
+
end
|
39
|
+
|
40
|
+
# :nodoc:
|
41
|
+
def stop
|
42
|
+
@stopped = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
RSpec.configure do |config|
|
48
|
+
config.include TestRunners
|
49
|
+
config.full_backtrace = true
|
50
|
+
|
51
|
+
# true by default for RSpec 4.0
|
52
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
53
|
+
|
54
|
+
config.mock_with(:rspec) do |mocks|
|
55
|
+
mocks.yield_receiver_to_any_instance_implementation_blocks = true
|
56
|
+
mocks.verify_partial_doubles = true
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sigurd
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Orner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: exponential-backoff
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: guard
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard-rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec_junit_formatter
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.3'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.72'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.72'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop-rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.27'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.27'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- daniel.orner@flipp.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".circleci/config.yml"
|
147
|
+
- ".gitignore"
|
148
|
+
- ".gitmodules"
|
149
|
+
- ".rspec"
|
150
|
+
- ".rubocop.yml"
|
151
|
+
- ".ruby-gemset"
|
152
|
+
- ".ruby-version"
|
153
|
+
- CHANGELOG.md
|
154
|
+
- CODE_OF_CONDUCT.md
|
155
|
+
- Gemfile
|
156
|
+
- Gemfile.lock
|
157
|
+
- Guardfile
|
158
|
+
- LICENSE.md
|
159
|
+
- README.md
|
160
|
+
- lib/sigurd.rb
|
161
|
+
- lib/sigurd/executor.rb
|
162
|
+
- lib/sigurd/signal_handler.rb
|
163
|
+
- lib/sigurd/version.rb
|
164
|
+
- sigurd.gemspec
|
165
|
+
- spec/executor_spec.rb
|
166
|
+
- spec/signal_handler_spec.rb
|
167
|
+
- spec/spec_helper.rb
|
168
|
+
homepage: ''
|
169
|
+
licenses:
|
170
|
+
- Apache-2.0
|
171
|
+
metadata: {}
|
172
|
+
post_install_message:
|
173
|
+
rdoc_options: []
|
174
|
+
require_paths:
|
175
|
+
- lib
|
176
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
requirements: []
|
187
|
+
rubygems_version: 3.1.2
|
188
|
+
signing_key:
|
189
|
+
specification_version: 4
|
190
|
+
summary: Small gem to manage executing looping processes and signal handling.
|
191
|
+
test_files:
|
192
|
+
- spec/executor_spec.rb
|
193
|
+
- spec/signal_handler_spec.rb
|
194
|
+
- spec/spec_helper.rb
|