sisyphus 0.2.3 → 0.2.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
  SHA1:
3
- metadata.gz: 1a96cdf599669e601888f70c06de7ae280b86fc6
4
- data.tar.gz: 601d5544f93601e4b9aac5df9dad122040742300
3
+ metadata.gz: cba21106d39eba3304360aef31ce2a159d7a0213
4
+ data.tar.gz: e04b2f8ed6b63981e40c8f5bd99286ee1251f24e
5
5
  SHA512:
6
- metadata.gz: 731a337d88e4ca319a824c2fdb44f2343808adfd1a15762bfb6a2417666c7aca8182b2859bf495c33f4c48c170091061b36f18fb0088281258288fe677a5d55a
7
- data.tar.gz: e4c2bcbfb26349558d25d705fb69f370cc05e4040f8f46bc4430794b448b1e621bcece6988b68318a2ee6dba128594001cd42f2212bf52ac9ec79125e9704afc
6
+ metadata.gz: 68ec3f435ab8161d7510d849f4131b014b12c84660db5347c5fdc7c0717b1f3259efb979616d2f259f043d02861eab4103a5d971c533867e237055e2e3452c8e
7
+ data.tar.gz: a0e650e2792a69f15ca6b0b6e3c5ad1b3403533d5c5992ce01d70524fe43b545a294dbfb5e35a62fd132da09b51e74d6460f762c048f3f3783801b0f2495278a
data/README.md CHANGED
@@ -32,6 +32,10 @@ Getting started
32
32
  master or workers encounter. The logger should quack like a
33
33
  `Logger` instance from the Ruby stdlib. Exceptions are logged with the
34
34
  `Logger::WARN` level.
35
+ * `:execution_strategy` which can either be `Sisyphus::SimpleExecutionStrategy` or
36
+ `Sisyphus::ForkingExecutionStrategy`. This is the strategy used by
37
+ workers when performing the job. The default is
38
+ `Sisyphus::ForkingExecutionStrategy`.
35
39
  4. You can start workers by doing one of the following things:
36
40
  * Send the `start` message to the master, if the `options` hash was
37
41
  provided. This starts a run loop which monitors workers and
@@ -63,8 +67,6 @@ Things missing
63
67
  Sisyphus is still very much in its infancy, though the ambition isn't to build a [Resque] [resque] clone, but
64
68
  instead build as small a tool with as few features as possible.
65
69
 
66
- [resque]: https://github.com/resque/resque
67
-
68
70
  There are, however, still features that are missing:
69
71
 
70
72
  - Force killing workers
@@ -76,6 +78,7 @@ There are, however, still features that are missing:
76
78
  - Some sort of reaping of worker processes
77
79
  - Documentation
78
80
 
81
+ [resque]: https://github.com/resque/resque
79
82
  [unicorn]: http://unicorn.bogomips.org/
80
83
 
81
84
  Contributing
data/Rakefile CHANGED
@@ -5,8 +5,10 @@ task :sleeper do
5
5
  gem 'sisyphus'
6
6
  require 'sisyphus'
7
7
  require 'sisyphus/sleep'
8
+ require 'logger'
8
9
 
10
+ logger = Logger.new(STDOUT)
9
11
  job = Sisyphus::Sleep.new
10
- master = Sisyphus::Master.new job, workers: 2
12
+ master = Sisyphus::Master.new job, workers: 2, logger: logger
11
13
  master.start
12
14
  end
@@ -1,4 +1,6 @@
1
1
  require "version"
2
+ require 'sisyphus/forking_execution_strategy'
3
+ require 'sisyphus/simple_execution_strategy'
2
4
  require 'sisyphus/job'
3
5
  require 'sisyphus/worker'
4
6
  require 'sisyphus/master'
@@ -0,0 +1,67 @@
1
+ module Sisyphus
2
+ class ForkingExecutionStrategy
3
+
4
+ attr_reader :logger
5
+
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def execute(job, error_handler = ->{})
11
+ if @child_pid = fork
12
+ error_handler.call unless success?
13
+ else
14
+ perform job
15
+ end
16
+ end
17
+
18
+ class ChildProcess
19
+ attr_reader :pid
20
+
21
+ def initialize(pid)
22
+ @pid = pid
23
+ end
24
+
25
+ def success?
26
+ status.success?
27
+ end
28
+
29
+ private
30
+
31
+ def status
32
+ _, status = ::Process.waitpid2 pid
33
+ status
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def success?
40
+ child_process.success?
41
+ end
42
+
43
+ def child_process
44
+ ChildProcess.new(@child_pid)
45
+ end
46
+
47
+ def perform(job)
48
+ self.process_name = "Child of worker #{::Process.ppid}"
49
+ begin
50
+ job.perform
51
+ exit! 0
52
+ rescue ::Exception => e
53
+ logger.warn(process_name) { e }
54
+ exit! 1
55
+ end
56
+ end
57
+
58
+ def process_name
59
+ $0
60
+ end
61
+
62
+ def process_name=(name)
63
+ $0 = name
64
+ end
65
+
66
+ end
67
+ end
@@ -1,4 +1,5 @@
1
1
  require 'timeout'
2
+ require_relative './forking_execution_strategy'
2
3
  require_relative './worker'
3
4
  require_relative './null_logger'
4
5
 
@@ -8,11 +9,12 @@ module Sisyphus
8
9
 
9
10
  HANDLED_SIGNALS = [:INT, :TTIN, :TTOU]
10
11
 
11
- attr_reader :logger
12
+ attr_reader :logger, :job, :number_of_workers
12
13
 
13
14
  def initialize(job, options = {})
14
- @number_of_workers = options.fetch :workers, 0
15
- @logger = options.fetch :logger, NullLogger.new
15
+ self.number_of_workers = options.fetch :workers, 0
16
+ @logger = options.fetch(:logger) { NullLogger.new }
17
+ @execution_strategy = options.fetch(:execution_strategy) { ForkingExecutionStrategy }
16
18
  @workers = []
17
19
  @job = job
18
20
 
@@ -24,60 +26,77 @@ module Sisyphus
24
26
 
25
27
  def start
26
28
  trap_signals
27
- @number_of_workers.times do
28
- start_worker
29
+ number_of_workers.times do
30
+ spawn_worker
29
31
  sleep rand(1000).fdiv(1000)
30
32
  end
31
33
  puts "Sisyphus::Master started with PID: #{Process.pid}"
32
34
  watch_for_output
33
35
  end
34
36
 
35
- def start_worker
37
+ def spawn_worker
36
38
  reader, writer = IO.pipe
37
39
  if wpid = fork
38
40
  writer.close
39
- @workers << { pid: wpid, reader: reader }
41
+ workers << { pid: wpid, reader: reader }
40
42
  else
41
43
  reader.close
42
44
  self.process_name = "Worker #{Process.pid}"
43
- begin
44
- worker = Worker.new(@job, writer, logger)
45
- worker.setup
46
- worker.start
47
- rescue Exception => e
48
- writer.write Worker::UNCAUGHT_ERROR
49
- logger.warn(process_name) { e }
50
- exit! 0
51
- end
45
+ worker = create_worker(writer)
46
+ start_worker worker
52
47
  end
53
48
  end
54
49
 
50
+ def start_worker(worker)
51
+ worker.setup
52
+ worker.start
53
+ rescue Exception => e
54
+ worker.error_handler.call
55
+ logger.warn(process_name) { e }
56
+ exit! 0
57
+ end
58
+
55
59
  def stop_worker(wpid)
56
- if @workers.find { |w| w.fetch(:pid) == wpid }
60
+ if workers.find { |w| w.fetch(:pid) == wpid }
57
61
  Process.kill 'INT', wpid rescue Errno::ESRCH # Ignore if the process is already gone
58
62
  end
59
63
  end
60
64
 
61
65
  def stop_all
62
- @workers.each do |worker|
66
+ workers.each do |worker|
63
67
  stop_worker worker.fetch(:pid)
64
68
  end
65
- Timeout.timeout(30) do
66
- watch_for_shutdown while worker_count > 0
69
+ begin
70
+ Timeout.timeout(30) do
71
+ watch_for_shutdown while worker_count > 0
72
+ end
73
+ rescue e
74
+ p "Timeout reached:", e
67
75
  end
68
76
  end
69
77
 
70
78
  def worker_count
71
- @workers.length
79
+ workers.length
72
80
  end
73
81
 
74
82
  private
75
83
 
84
+ attr_reader :workers
85
+ attr_writer :number_of_workers
86
+
87
+ def create_worker(writer)
88
+ Worker.new(job, writer, execution_strategy)
89
+ end
90
+
91
+ def execution_strategy
92
+ @execution_strategy.new logger
93
+ end
94
+
76
95
  def watch_for_shutdown
77
96
  wpid, _ = Process.wait2
78
97
  worker = @workers.find { |w| w.fetch(:pid) == wpid }
79
98
  worker.fetch(:reader).close
80
- @workers.delete worker
99
+ workers.delete worker
81
100
  wpid
82
101
  rescue Errno::ECHILD
83
102
  end
@@ -107,26 +126,26 @@ module Sisyphus
107
126
 
108
127
  def process_output(pipes)
109
128
  pipes.each do |pipe|
110
- restart_worker worker_pid(pipe) unless stopping?
129
+ respawn_worker worker_pid(pipe) unless stopping?
111
130
  end
112
131
  end
113
132
 
114
- def restart_worker(wpid)
115
- start_worker
133
+ def respawn_worker(wpid)
134
+ spawn_worker
116
135
  stop_worker wpid
117
136
  watch_for_shutdown
118
137
  end
119
138
 
120
139
  def worker_pipes
121
140
  if worker_count > 0
122
- @workers.map { |w| w.fetch(:reader) }
141
+ workers.map { |w| w.fetch(:reader) }
123
142
  else
124
143
  []
125
144
  end
126
145
  end
127
146
 
128
147
  def worker_pid(reader)
129
- if worker = @workers.find { |w| w.fetch(:reader).fileno == reader.fileno }
148
+ if worker = workers.find { |w| w.fetch(:reader).fileno == reader.fileno }
130
149
  worker.fetch(:pid)
131
150
  else
132
151
  raise 'Unknown worker pipe'
@@ -171,14 +190,14 @@ module Sisyphus
171
190
  end
172
191
 
173
192
  def handle_ttin
174
- @number_of_workers += 1
175
- start_worker
193
+ self.number_of_workers += 1
194
+ spawn_worker
176
195
  end
177
196
 
178
197
  def handle_ttou
179
- if @number_of_workers > 0
180
- @number_of_workers -= 1
181
- stop_worker(@workers.first.fetch(:pid))
198
+ if number_of_workers > 0
199
+ self.number_of_workers -= 1
200
+ stop_worker(workers.first.fetch(:pid))
182
201
  end
183
202
  end
184
203
 
@@ -0,0 +1,24 @@
1
+ module Sisyphus
2
+ class SimpleExecutionStrategy
3
+
4
+ attr_reader :logger
5
+
6
+ def initialize(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def execute(job, error_handler = ->{})
11
+ job.perform
12
+ rescue Exception => e
13
+ logger.warn(process_name) { e }
14
+ error_handler.call
15
+ end
16
+
17
+ private
18
+
19
+ def process_name
20
+ $0
21
+ end
22
+
23
+ end
24
+ end
@@ -3,7 +3,7 @@ require_relative './job'
3
3
  module Sisyphus
4
4
  class Sleep < Job
5
5
  def perform
6
- sleep 2
6
+ sleep 40
7
7
  puts "Goodmorning from #{Process.pid}"
8
8
  raise "Hest" if rand(10) % 2 == 0
9
9
  end
@@ -2,16 +2,16 @@ module Sisyphus
2
2
  class Worker
3
3
  UNCAUGHT_ERROR = '.'
4
4
 
5
- attr_reader :logger
5
+ attr_reader :execution_strategy, :job, :output
6
6
 
7
- def initialize(job, output, logger)
7
+ def initialize(job, output, execution_strategy)
8
8
  @job = job
9
9
  @output = output
10
- @logger = logger
10
+ @execution_strategy = execution_strategy
11
11
  end
12
12
 
13
13
  def setup
14
- @job.setup if @job.respond_to? :setup
14
+ job.setup if job.respond_to? :setup
15
15
  end
16
16
 
17
17
  def start
@@ -25,28 +25,22 @@ module Sisyphus
25
25
  exit! 0
26
26
  end
27
27
 
28
- private
29
-
30
28
  def perform_job
31
- if child = fork
32
- _, status = Process.waitpid2 child
29
+ execution_strategy.execute job, error_handler
30
+ end
31
+
32
+ def error_handler
33
+ -> {
33
34
  begin
34
- @output.write UNCAUGHT_ERROR unless status.success? || stopped?
35
+ output.write UNCAUGHT_ERROR unless stopped?
35
36
  rescue Errno::EAGAIN, Errno::EINTR
36
37
  # Ignore
37
38
  end
38
- else
39
- self.process_name = "Child of worker #{Process.ppid}"
40
- begin
41
- @job.perform
42
- exit! 0
43
- rescue Exception => e
44
- logger.warn(process_name) { e }
45
- exit! 1
46
- end
47
- end
39
+ }
48
40
  end
49
41
 
42
+ private
43
+
50
44
  def trap_signals
51
45
  Signal.trap('INT') do
52
46
  stop
@@ -61,12 +55,5 @@ module Sisyphus
61
55
  @stopped
62
56
  end
63
57
 
64
- def process_name=(name)
65
- $0 = name
66
- end
67
-
68
- def process_name
69
- $0
70
- end
71
58
  end
72
59
  end
@@ -1,3 +1,3 @@
1
1
  module Sisyphus
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -0,0 +1,122 @@
1
+ require_relative '../../lib/sisyphus/forking_execution_strategy'
2
+
3
+ module Sisyphus
4
+ describe ForkingExecutionStrategy do
5
+
6
+ let(:logger) { double :logger }
7
+ let(:job) { double :job }
8
+ let(:error_handler) { double :error_handler }
9
+ let(:strategy) { ForkingExecutionStrategy.new logger }
10
+ let(:child_pid) { 1 }
11
+
12
+ it 'forks on execution' do
13
+ allow(strategy).to receive(:success?) { true }
14
+ expect(strategy).to receive(:fork) { child_pid }
15
+ strategy.execute job
16
+ end
17
+
18
+ context 'in the parent process' do
19
+
20
+ let(:child_process) { double :child_process }
21
+
22
+ before :each do
23
+ allow(strategy).to receive(:fork) { child_pid }
24
+ end
25
+
26
+ it 'calls error_handler if execution is unsuccessful' do
27
+ allow(strategy).to receive(:success?) { false }
28
+ expect(error_handler).to receive(:call)
29
+ strategy.execute job, error_handler
30
+ end
31
+
32
+ it 'does not call error_handler if execution is successful' do
33
+ allow(strategy).to receive(:success?) { true }
34
+ expect(error_handler).not_to receive(:call)
35
+ strategy.execute job, error_handler
36
+ end
37
+
38
+ it 'instantiates a child_process' do
39
+ allow(child_process).to receive(:success?) { true }
40
+ expect(ForkingExecutionStrategy::ChildProcess).to receive(:new).with(child_pid) { child_process }
41
+ strategy.execute job, error_handler
42
+ end
43
+
44
+ it 'checks if the child process is successful' do
45
+ expect(child_process).to receive(:success?) { true }
46
+ allow(strategy).to receive(:child_process) { child_process }
47
+ strategy.execute job, error_handler
48
+ end
49
+
50
+ end
51
+
52
+ context 'in the execution process' do
53
+
54
+ let(:ppid) { 2 }
55
+
56
+ before :each do
57
+ allow(strategy).to receive(:fork) { nil }
58
+ allow(job).to receive(:perform)
59
+ allow(strategy).to receive(:exit!)
60
+ end
61
+
62
+ it 'updates the process name' do
63
+ allow(Process).to receive(:ppid) { ppid }
64
+ expect(strategy).to receive(:process_name=).with("Child of worker #{ppid}")
65
+ strategy.execute job
66
+ end
67
+
68
+ it 'performs the job' do
69
+ expect(job).to receive(:perform)
70
+ strategy.execute job
71
+ end
72
+
73
+ it 'exits with a 0 status if job is performed without failing' do
74
+ expect(strategy).to receive(:exit!).with(0)
75
+ strategy.execute job
76
+ end
77
+
78
+ it 'logs the exception if job is performed and it fails' do
79
+ process_name = "foobarbaz"
80
+ exception_message = "foo"
81
+ allow(strategy).to receive(:process_name) { process_name }
82
+ allow(job).to receive(:perform) { raise Exception, exception_message }
83
+ expect(logger).to receive(:warn).with(process_name)
84
+ strategy.execute job
85
+ end
86
+
87
+ it 'exits with a 1 status if job is performed and it fails' do
88
+ process_name = "foobarbaz"
89
+ allow(strategy).to receive(:process_name) { process_name }
90
+ allow(job).to receive(:perform) { raise "foo" }
91
+ allow(logger).to receive(:warn)
92
+ expect(strategy).to receive(:exit!).with(1)
93
+ strategy.execute job
94
+ end
95
+
96
+ end
97
+
98
+ describe ForkingExecutionStrategy::ChildProcess do
99
+
100
+ let(:pid) { 1 }
101
+ let(:status) { double :status }
102
+ let(:child_process) { ForkingExecutionStrategy::ChildProcess.new pid }
103
+
104
+ it 'waits for the process to finish' do
105
+ expect(Process).to receive(:waitpid2).with pid do
106
+ allow(status).to receive(:success?) { true }
107
+ [pid, status]
108
+ end
109
+ child_process.success?
110
+ end
111
+
112
+ it 'asks status about its success' do
113
+ allow(Process).to receive(:waitpid2).with pid do
114
+ expect(status).to receive(:success?) { true }
115
+ [pid, status]
116
+ end
117
+ child_process.success?
118
+ end
119
+ end
120
+
121
+ end
122
+ end
@@ -4,123 +4,128 @@ module Sisyphus
4
4
  describe Master do
5
5
  subject(:master) { Master.new job }
6
6
 
7
- before(:each) { master.stub :puts }
7
+ before(:each) {
8
+ allow(master).to receive(:puts)
9
+ allow(master).to receive(:sleep)
10
+ }
8
11
 
9
12
  let(:job) { double(:job) }
10
13
  let(:pipes) { [double(:reader_pipe), double(:writer_pipe)] }
11
14
 
12
- describe 'when receiving the start_worker message' do
15
+ describe 'when receiving the spawn_worker message' do
13
16
  it 'forks' do
14
- master.should_receive(:fork) { 666 }
15
- master.start_worker
17
+ expect(master).to receive(:fork) { 666 }
18
+ master.spawn_worker
16
19
  end
17
20
 
18
21
  describe 'in the worker process' do
19
22
  let(:worker) { double :worker }
20
23
 
21
24
  before :each do
22
- master.stub(:fork) { nil }
23
- IO.stub(:pipe) { pipes }
24
- pipes.first.stub(:close)
25
- Process.stub(:pid) { 666 }
26
- master.stub :exit!
27
- Worker.stub(:new) { worker }
28
- worker.stub(:setup)
29
- worker.stub(:start)
25
+ allow(master).to receive(:fork) { nil }
26
+ allow(IO).to receive(:pipe) { pipes }
27
+ allow(pipes.first).to receive(:close)
28
+ allow(Process).to receive(:pid) { 666 }
29
+ allow(master).to receive(:exit!)
30
+ allow(Worker).to receive(:new) { worker }
31
+ allow(worker).to receive(:setup)
32
+ allow(worker).to receive(:start)
30
33
  end
31
34
 
32
35
  it 'should setup the worker' do
33
- worker.should_receive :setup
34
- master.start_worker
36
+ expect(worker).to receive(:setup)
37
+ master.spawn_worker
35
38
  end
36
39
 
37
40
  it 'should rename the process' do
38
- master.should_receive(:process_name=).with("Worker #{666}")
39
- master.start_worker
41
+ expect(master).to receive(:process_name=).with("Worker #{666}")
42
+ master.spawn_worker
40
43
  end
41
44
 
42
45
  it 'starts a worker after forking' do
43
- worker.should_receive :start
44
- master.start_worker
46
+ expect(worker).to receive(:start)
47
+ master.spawn_worker
45
48
  end
46
49
 
47
50
  it 'gives the writer pipe to the worker' do
48
- Worker.should_receive(:new).with(job, pipes.last, master.logger) { worker }
49
- master.start_worker
51
+ execution_strategy = double :execution_strategy
52
+ allow(master).to receive(:execution_strategy) { execution_strategy }
53
+ expect(Worker).to receive(:new).with(job, pipes.last, execution_strategy) { worker }
54
+ master.spawn_worker
50
55
  end
51
56
 
52
57
  it 'closes the reader pipe' do
53
- pipes.first.should_receive :close
54
- master.start_worker
58
+ expect(pipes.first).to receive(:close)
59
+ master.spawn_worker
55
60
  end
56
61
 
57
62
  describe 'when an exception is raised' do
58
63
  let(:logger) { double(:logger) }
59
64
 
60
65
  it 'should log the exception' do
61
- master.stub(:logger).and_return logger
62
- pipes.last.stub(:write)
63
- worker.stub(:setup).and_raise :raised_by_spec
64
- logger.should_receive :warn
65
- master.start_worker
66
+ allow(worker).to receive(:error_handler) { ->{} }
67
+ allow(master).to receive(:logger) { logger }
68
+ allow(worker).to receive(:setup) { raise :raised_by_spec }
69
+ expect(logger).to receive(:warn)
70
+ master.spawn_worker
66
71
  end
67
72
 
68
73
  it 'should write to the writer pipe' do
69
- master.stub(:logger).and_return logger
70
- worker.stub(:setup).and_raise :raised_by_spec
71
- logger.stub :warn
72
- pipes.last.should_receive(:write).with Worker::UNCAUGHT_ERROR
73
- master.start_worker
74
+ allow(master).to receive(:logger) { logger }
75
+ allow(worker).to receive(:setup) { raise :raised_by_spec }
76
+ allow(logger).to receive(:warn)
77
+ expect(worker).to receive(:error_handler) { ->{} }
78
+ master.spawn_worker
74
79
  end
75
80
  end
76
81
  end
77
82
 
78
83
  describe 'in the master process' do
79
84
  before :each do
80
- master.stub(:fork) { 666 }
81
- IO.stub(:pipe) { pipes }
82
- pipes.last.stub(:close)
85
+ allow(master).to receive(:fork) { 666 }
86
+ allow(IO).to receive(:pipe) { pipes }
87
+ allow(pipes.last).to receive(:close)
83
88
  end
84
89
 
85
90
  it 'increases worker_count' do
86
- master.start_worker
87
- master.worker_count.should eq(1)
91
+ master.spawn_worker
92
+ expect(master.worker_count).to eq(1)
88
93
  end
89
94
 
90
95
  it 'should open a pipe' do
91
- IO.should_receive(:pipe) { pipes }
92
- master.start_worker
96
+ expect(IO).to receive(:pipe) { pipes }
97
+ master.spawn_worker
93
98
  end
94
99
 
95
100
  it 'should close the writer pipe' do
96
- pipes.last.should_receive :close
97
- master.start_worker
101
+ expect(pipes.last).to receive(:close)
102
+ master.spawn_worker
98
103
  end
99
104
  end
100
105
  end
101
106
 
102
107
  describe 'when it has running workers' do
103
108
  before :each do
104
- pipes.each { |p| p.stub :close }
105
- IO.stub(:pipe) { pipes }
106
- master.stub(:fork) { 666 }
107
- master.start_worker
108
- Process.stub(:kill).with('INT', 666)
109
- Process.stub(:waitpid2).with(666)
109
+ pipes.each { |p| allow(p).to receive(:close) }
110
+ allow(IO).to receive(:pipe) { pipes }
111
+ allow(master).to receive(:fork) { 666 }
112
+ master.spawn_worker
113
+ allow(Process).to receive(:kill).with('INT', 666)
114
+ allow(Process).to receive(:waitpid2).with(666)
110
115
  end
111
116
 
112
117
  describe 'and it receives stop_worker message' do
113
118
  it 'kills a child with the INT signal' do
114
- Process.should_receive(:kill).with('INT', 666)
119
+ expect(Process).to receive(:kill).with('INT', 666)
115
120
  master.stop_worker(666)
116
121
  end
117
122
  end
118
123
 
119
124
  it 'stops all workers when receiving stop_all' do
120
- Process.stub(:kill).with('INT', 666)
121
- Process.stub(:wait2) { 666 }
125
+ allow(Process).to receive(:kill).with('INT', 666)
126
+ allow(Process).to receive(:wait2) { 666 }
122
127
 
123
- master.should_receive(:stop_worker).with(666).exactly(master.worker_count).times.and_call_original
128
+ expect(master).to receive(:stop_worker).with(666).exactly(master.worker_count).times.and_call_original
124
129
 
125
130
  master.stop_all
126
131
  end
@@ -135,7 +140,7 @@ module Sisyphus
135
140
 
136
141
  describe 'and it receives stop_all' do
137
142
  it 'does nothing' do
138
- master.should_not_receive(:stop_worker)
143
+ expect(master).not_to receive(:stop_worker)
139
144
  master.stop_all
140
145
  end
141
146
  end
@@ -143,47 +148,47 @@ module Sisyphus
143
148
 
144
149
  it 'starts the specified number of workers when started' do
145
150
  master = Master.new nil, workers: 3
146
- master.stub :puts
147
- master.stub :watch_for_output
148
- master.should_receive(:start_worker).exactly(3).times
151
+ allow(master).to receive(:puts)
152
+ allow(master).to receive(:watch_for_output)
153
+ expect(master).to receive(:spawn_worker).exactly(3).times
149
154
  master.start
150
155
  end
151
156
 
152
157
  describe 'when number of workers is zero' do
153
158
  let(:master) { Master.new nil, workers: 0 }
154
159
 
155
- before(:each) { master.stub :puts }
160
+ before(:each) { allow(master).to receive(:puts) }
156
161
 
157
162
  it 'should not start workers' do
158
- master.stub :watch_for_output
159
- master.should_not_receive :start_worker
163
+ allow(master).to receive(:watch_for_output)
164
+ expect(master).not_to receive(:spawn_worker)
160
165
  master.start
161
166
  end
162
167
  end
163
168
 
164
169
  it 'attaches a signal handler when started' do
165
- Signal.should_receive(:trap).with(:INT)
166
- Signal.should_receive(:trap).with(:TTIN)
167
- Signal.should_receive(:trap).with(:TTOU)
168
- master.stub :start_worker
169
- master.stub :watch_for_output
170
+ expect(Signal).to receive(:trap).with(:TTIN)
171
+ expect(Signal).to receive(:trap).with(:INT)
172
+ expect(Signal).to receive(:trap).with(:TTOU)
173
+ allow(master).to receive(:spawn_worker)
174
+ allow(master).to receive(:watch_for_output)
170
175
  master.start
171
176
  end
172
177
 
173
178
  it 'should watch for output' do
174
- master.stub :start_worker
175
- master.should_receive :watch_for_output
179
+ allow(master).to receive(:spawn_worker)
180
+ expect(master).to receive(:watch_for_output)
176
181
  master.start
177
182
  end
178
183
 
179
184
  it 'can resolve a wpid from a reader pipe' do
180
- IO.stub(:pipe) { pipes }
181
- pipes.each { |p| p.stub(:close) }
182
- pipes.first.stub(:fileno) { 213 }
183
- master.stub(:fork) { 666 }
184
- master.start_worker
185
+ allow(IO).to receive(:pipe) { pipes }
186
+ pipes.each { |p| allow(p).to receive(:close) }
187
+ allow(pipes.first).to receive(:fileno) { 213 }
188
+ allow(master).to receive(:fork) { 666 }
189
+ master.spawn_worker
185
190
 
186
- master.send(:worker_pid, pipes.first).should eq(666)
191
+ expect(master.send(:worker_pid, pipes.first)).to eq(666)
187
192
  end
188
193
 
189
194
  it 'raises if it can\'t resolve a wpid from a reader pipe' do
@@ -0,0 +1,32 @@
1
+ require_relative '../../lib/sisyphus/simple_execution_strategy'
2
+
3
+ module Sisyphus
4
+ describe SimpleExecutionStrategy do
5
+
6
+ let(:logger) { double :logger }
7
+ let(:job) { double :job }
8
+ let(:strategy) { SimpleExecutionStrategy.new(logger) }
9
+
10
+ it 'should perform the job when executed' do
11
+ expect(job).to receive(:perform)
12
+ strategy.execute job
13
+ end
14
+
15
+ it 'should log exceptions if the job fails' do
16
+ error_message = "This is a horrible failure.. The Universe is probably ending!"
17
+ process_name = "uber awesome process name"
18
+ allow(job).to receive(:perform) { fail Exception, error_message }
19
+ allow(strategy).to receive(:process_name) { process_name }
20
+ expect(logger).to receive(:warn).with(process_name)
21
+ strategy.execute job
22
+ end
23
+
24
+ it 'should call the error_handler if the job fails' do
25
+ allow(job).to receive(:perform) { fail "foo" }
26
+ allow(logger).to receive(:warn)
27
+ error_handler = double :error_handler
28
+ expect(error_handler).to receive(:call)
29
+ strategy.execute job, error_handler
30
+ end
31
+ end
32
+ end
@@ -4,8 +4,8 @@ module Sisyphus
4
4
  describe Worker do
5
5
  let(:job) { double :job }
6
6
  let(:output) { double :pipe }
7
- let(:logger) { double :logger }
8
- let(:worker) { Worker.new job, output, logger }
7
+ let(:execution_strategy) { double :execution_strategy }
8
+ let(:worker) { Worker.new job, output, execution_strategy }
9
9
 
10
10
  it 'traps signals when started' do
11
11
  worker.stub :exit!
@@ -20,107 +20,49 @@ module Sisyphus
20
20
  worker.start
21
21
  end
22
22
 
23
- context 'when job does not respond to :setup' do
24
- it 'does not call job.setup' do
25
- job.stub(:respond_to?).with(:setup) { false }
26
- job.should_not_receive :setup
27
- Worker.new job, output, logger
28
- end
23
+ it 'uses execution_strategy to perform the job' do
24
+ expect(execution_strategy).to receive(:execute).with job, an_instance_of(Proc)
25
+ worker.perform_job
29
26
  end
30
27
 
31
- context 'when job responds to :setup' do
32
- it 'sets up the job' do
33
- job.stub(:respond_to?).with(:setup) { true }
34
- job.should_receive :setup
35
- worker.setup
36
- end
28
+ it 'passes the error_handler to the execution strategy' do
29
+ allow(execution_strategy).to receive(:execute).with job, an_instance_of(Proc)
30
+ expect(worker).to receive(:error_handler) { ->{} }
31
+ worker.perform_job
37
32
  end
38
33
 
39
- context 'in the child process' do
40
- before :each do
41
- worker.stub(:fork) { nil }
42
- end
43
-
44
- it 'should perform the job' do
45
- job.should_receive :perform
46
- worker.stub :exit!
47
- worker.send :perform_job
48
- end
34
+ context 'the error_handler' do
49
35
 
50
- it 'should exit after having performed the job' do
51
- job.stub :perform
52
- worker.should_receive(:exit!).with 0
53
- worker.send :perform_job
36
+ it 'writes the UNCAUGHT_ERROR to output' do
37
+ expect(output).to receive(:write).with Worker::UNCAUGHT_ERROR
38
+ worker.error_handler.call
54
39
  end
55
40
 
56
- it 'should change process name' do
57
- job.stub :perform
58
- worker.stub :exit!
59
- Process.stub(:ppid) { 666 }
60
- worker.should_receive(:process_name=).with "Child of worker 666"
61
- worker.send :perform_job
41
+ it 'does not write UNCAUGHT_ERROR to output if the worker is stopped' do
42
+ allow(worker).to receive(:stopped?) { true }
43
+ expect(output).to_not receive(:write)
44
+ worker.error_handler.call
45
+ expect(output).to_not receive(:write)
46
+ worker.error_handler.call
62
47
  end
63
48
 
64
- context 'when job.perform raises an error' do
65
- it 'should exit with a non-zero status' do
66
- logger.stub :warn
67
- job.stub(:perform).and_raise Exception.new "should be rescued!"
68
- worker.should_receive(:exit!).with(1)
69
- worker.send :perform_job
70
- end
71
-
72
- it 'should log the raised error' do
73
- worker.stub(:exit!).with(1)
74
- job.stub(:perform).and_raise Exception.new "should be rescued!"
75
- logger.should_receive :warn
76
- worker.send :perform_job
77
- end
78
- end
79
49
  end
80
50
 
81
- context 'in the worker process' do
82
- let(:status) { double(:status) }
83
-
84
- before :each do
85
- worker.stub(:fork) { 666 }
86
- end
87
-
88
- it 'spawns a process and waits for it to finish' do
89
- worker.should_receive(:fork) { 666 }
90
- status.stub(:success?) { true }
91
- Process.should_receive(:waitpid2).with(666) { [666, status] }
92
- worker.send :perform_job
93
- end
94
-
95
- context 'when exit status from the child is non-zero' do
96
- before :each do
97
- status.stub(:success?) { false }
98
- Process.stub(:waitpid2) { [666, status] }
99
- end
100
-
101
- it 'writes an error message to the output' do
102
- output.should_receive(:write).with Worker::UNCAUGHT_ERROR
103
- worker.send :perform_job
104
- end
105
-
106
- it "doesn't write error byte to output when it has been stopped" do
107
- worker.stub(:stopped?) { true }
108
- output.should_not_receive :write
109
- worker.send :perform_job
110
- end
51
+ context 'when job does not respond to :setup' do
52
+ it 'does not call job.setup' do
53
+ job.stub(:respond_to?).with(:setup) { false }
54
+ job.should_not_receive :setup
55
+ worker.setup
111
56
  end
57
+ end
112
58
 
113
- context 'when exit status from child is zero' do
114
- before :each do
115
- status.stub(:success?) { true }
116
- Process.stub(:waitpid2) { [666, status] }
117
- end
118
-
119
- it "doesn't write error byte to output" do
120
- output.should_not_receive :write
121
- worker.send :perform_job
122
- end
59
+ context 'when job responds to :setup' do
60
+ it 'sets up the job' do
61
+ job.stub(:respond_to?).with(:setup) { true }
62
+ job.should_receive :setup
63
+ worker.setup
123
64
  end
124
65
  end
66
+
125
67
  end
126
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sisyphus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rasmus Bang Grouleff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-04 00:00:00.000000000 Z
11
+ date: 2014-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,14 +66,18 @@ files:
66
66
  - README.md
67
67
  - Rakefile
68
68
  - lib/sisyphus.rb
69
+ - lib/sisyphus/forking_execution_strategy.rb
69
70
  - lib/sisyphus/job.rb
70
71
  - lib/sisyphus/master.rb
71
72
  - lib/sisyphus/null_logger.rb
73
+ - lib/sisyphus/simple_execution_strategy.rb
72
74
  - lib/sisyphus/sleep.rb
73
75
  - lib/sisyphus/worker.rb
74
76
  - lib/version.rb
75
77
  - sisyphus.gemspec
78
+ - spec/sisyphus/forking_execution_strategy_spec.rb
76
79
  - spec/sisyphus/master_spec.rb
80
+ - spec/sisyphus/simple_execution_strategy_spec.rb
77
81
  - spec/sisyphus/worker_spec.rb
78
82
  homepage: https://github.com/rbgrouleff/sisyphus
79
83
  licenses:
@@ -100,5 +104,7 @@ signing_key:
100
104
  specification_version: 4
101
105
  summary: A tiny library for spawning worker processes
102
106
  test_files:
107
+ - spec/sisyphus/forking_execution_strategy_spec.rb
103
108
  - spec/sisyphus/master_spec.rb
109
+ - spec/sisyphus/simple_execution_strategy_spec.rb
104
110
  - spec/sisyphus/worker_spec.rb