qs 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/bench/report.txt CHANGED
@@ -5,7 +5,7 @@ Adding jobs
5
5
  Running jobs
6
6
  ....................................................................................................
7
7
 
8
- Adding 10000 Jobs Time: 1.6155s
9
- Running 10000 Jobs Time: 12.4848s
8
+ Adding 10000 Jobs Time: 1.6133s
9
+ Running 10000 Jobs Time: 12.4769s
10
10
 
11
11
  Done running benchmark report
data/lib/qs/client.rb CHANGED
@@ -55,6 +55,10 @@ module Qs
55
55
  self.redis.with{ |c| c.del(redis_key) }
56
56
  end
57
57
 
58
+ def ping
59
+ self.redis.with{ |c| c.ping }
60
+ end
61
+
58
62
  end
59
63
 
60
64
  end
data/lib/qs/daemon.rb CHANGED
@@ -71,7 +71,11 @@ module Qs
71
71
  !!(@work_loop_thread && @work_loop_thread.alive?)
72
72
  end
73
73
 
74
+ # * Ping redis to check that it can communicate with redis before running,
75
+ # this is friendlier than starting and continously erroring because it
76
+ # can't dequeue.
74
77
  def start
78
+ @client.ping
75
79
  @signal.set :start
76
80
  @work_loop_thread ||= Thread.new{ work_loop }
77
81
  end
@@ -160,7 +164,8 @@ module Qs
160
164
 
161
165
  def wait_for_available_worker
162
166
  if !@worker_pool.worker_available? && @signal.start?
163
- @worker_available_io.wait.read
167
+ @worker_available_io.wait
168
+ @worker_available_io.read
164
169
  end
165
170
  end
166
171
 
data/lib/qs/io_pipe.rb CHANGED
@@ -31,9 +31,8 @@ module Qs
31
31
  @writer.write_nonblock(value[0, NUMBER_OF_BYTES])
32
32
  end
33
33
 
34
- def wait
35
- ::IO.select([@reader])
36
- self
34
+ def wait(timeout = nil)
35
+ !!::IO.select([@reader], nil, nil, timeout)
37
36
  end
38
37
 
39
38
  end
data/lib/qs/process.rb CHANGED
@@ -9,6 +9,8 @@ module Qs
9
9
  STOP = 'S'.freeze
10
10
  RESTART = 'R'.freeze
11
11
 
12
+ WAIT_FOR_SIGNALS_TIMEOUT = 15
13
+
12
14
  attr_reader :daemon, :name
13
15
  attr_reader :pid_file, :signal_io, :restart_cmd
14
16
 
@@ -77,9 +79,14 @@ module Qs
77
79
  end
78
80
 
79
81
  def wait_for_signals(signal_io, daemon)
80
- while signal_io.wait do
81
- os_signal = signal_io.read
82
- handle_signal(os_signal, daemon)
82
+ loop do
83
+ ready = signal_io.wait(WAIT_FOR_SIGNALS_TIMEOUT)
84
+ handle_signal(signal_io.read, daemon) if ready
85
+
86
+ if !daemon.running?
87
+ log "Daemon crashed, restarting"
88
+ start_daemon(daemon)
89
+ end
83
90
  end
84
91
  end
85
92
 
data/lib/qs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qs
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -127,6 +127,13 @@ module Qs::Client
127
127
  assert_equal [@queue_redis_key], call.args
128
128
  end
129
129
 
130
+ should "ping redis using `ping`" do
131
+ subject.ping
132
+
133
+ call = @connection_spy.redis_calls.last
134
+ assert_equal :ping, call.command
135
+ end
136
+
130
137
  end
131
138
 
132
139
  class QsClientTests < UnitTests
@@ -218,6 +218,11 @@ module Qs::Daemon
218
218
  @thread.join 0.1
219
219
  end
220
220
 
221
+ should "ping redis" do
222
+ call = @client_spy.calls.first
223
+ assert_equal :ping, call.command
224
+ end
225
+
221
226
  should "return the thread that is running the daemon" do
222
227
  assert_instance_of Thread, @thread
223
228
  assert_true @thread.alive?
@@ -228,8 +233,8 @@ module Qs::Daemon
228
233
  end
229
234
 
230
235
  should "clear the signals list in redis" do
231
- call = @client_spy.calls.first
232
- assert_equal :clear, call.command
236
+ call = @client_spy.calls.find{ |c| c.command == :clear }
237
+ assert_not_nil call
233
238
  assert_equal [subject.signals_redis_key], call.args
234
239
  end
235
240
 
@@ -710,6 +715,10 @@ module Qs::Daemon
710
715
  @calls << Call.new(:clear, args)
711
716
  end
712
717
 
718
+ def ping
719
+ @calls << Call.new(:ping)
720
+ end
721
+
713
722
  Call = Struct.new(:command, :args)
714
723
  end
715
724
 
@@ -11,8 +11,7 @@ class Qs::ErrorHandler
11
11
  setup do
12
12
  @exception = Factory.exception
13
13
  @daemon_data = Qs::DaemonData.new
14
- @queue_name = Factory.string
15
- @queue_redis_key = Qs::Queue::RedisKey.new(@queue_name)
14
+ @queue_redis_key = Qs::Queue::RedisKey.new(Factory.string)
16
15
  @context_hash = {
17
16
  :daemon_data => @daemon_data,
18
17
  :queue_redis_key => @queue_redis_key,
@@ -27,20 +26,20 @@ class Qs::ErrorHandler
27
26
 
28
27
  end
29
28
 
30
- class InitTests < UnitTests
29
+ class InitSetupTests < UnitTests
31
30
  desc "when init"
32
31
  setup do
33
- @call_count = 0
34
- @first_called_at = nil
35
- @second_called_at = nil
36
- @args_passed_to_error_proc = nil
37
- first_error_proc = proc do |*args|
38
- @args_passed_to_error_proc = args
39
- @first_called_at = (@call_count += 1)
40
- end
41
- second_error_proc = proc{ @second_called_at = (@call_count += 1) }
42
- Assert.stub(@daemon_data, :error_procs){ [first_error_proc, second_error_proc] }
32
+ # always make sure there are multiple error procs or tests can be false
33
+ # positives
34
+ @error_proc_spies = (1..(Factory.integer(3) + 1)).map{ ErrorProcSpy.new }
35
+ Assert.stub(@daemon_data, :error_procs){ @error_proc_spies }
36
+ end
37
+
38
+ end
43
39
 
40
+ class InitTests < InitSetupTests
41
+ desc "when init"
42
+ setup do
44
43
  @handler = @handler_class.new(@exception, @context_hash)
45
44
  end
46
45
  subject{ @handler }
@@ -55,7 +54,7 @@ class Qs::ErrorHandler
55
54
  end
56
55
 
57
56
  should "know its error procs" do
58
- assert_equal @daemon_data.error_procs.reverse, subject.error_procs
57
+ assert_equal @error_proc_spies.reverse, subject.error_procs
59
58
  end
60
59
 
61
60
  end
@@ -66,66 +65,36 @@ class Qs::ErrorHandler
66
65
  @handler.run
67
66
  end
68
67
 
69
- should "pass its exception and context to the error procs" do
70
- assert_not_nil @args_passed_to_error_proc
71
- assert_includes subject.exception, @args_passed_to_error_proc
72
- assert_includes subject.context, @args_passed_to_error_proc
73
- end
74
-
75
- should "call each of its error procs" do
76
- assert_equal 1, @second_called_at
77
- assert_equal 2, @first_called_at
68
+ should "call each of its procs" do
69
+ subject.error_procs.each_with_index do |spy, index|
70
+ assert_true spy.called
71
+ assert_equal subject.exception, spy.exception
72
+ assert_equal subject.context, spy.context
73
+ end
78
74
  end
79
75
 
80
76
  end
81
77
 
82
- class RunAndErrorProcThrowsExceptionTests < UnitTests
83
- desc "run with an error proc that throws an exception"
78
+ class RunWithErrorProcExceptionsTests < InitSetupTests
79
+ desc "and run with error procs that throw exceptions"
84
80
  setup do
85
- @proc_exception = Factory.exception
86
- error_proc = proc{ raise @proc_exception }
87
- Assert.stub(@daemon_data, :error_procs){ [error_proc] }
81
+ @proc_exceptions = @error_proc_spies.reverse.map do |spy|
82
+ exception = Factory.exception(RuntimeError, @error_proc_spies.index(spy).to_s)
83
+ spy.raise_exception = exception
84
+ exception
85
+ end
88
86
 
89
87
  @handler = @handler_class.new(@exception, @context_hash).tap(&:run)
90
88
  end
91
89
  subject{ @handler }
92
90
 
93
- should "set its exception to the exception thrown by the error proc" do
94
- assert_equal @proc_exception, subject.exception
91
+ should "pass the previously raised exception to the next proc" do
92
+ exp = [@exception] + @proc_exceptions[0..-2]
93
+ assert_equal exp, subject.error_procs.map(&:exception)
95
94
  end
96
95
 
97
- end
98
-
99
- class RunWithMultipleErrorProcsThatThrowExceptionsTests < UnitTests
100
- desc "run with multiple error procs that throw an exception"
101
- setup do
102
- @first_caught_exception = nil
103
- @second_caught_exception = nil
104
- @third_caught_exception = nil
105
-
106
- @third_proc_exception = Factory.exception
107
- third_proc = proc do |exception, context|
108
- @third_caught_exception = exception
109
- raise @third_proc_exception
110
- end
111
-
112
- @second_proc_exception = Factory.exception
113
- second_proc = proc do |exception, context|
114
- @second_caught_exception = exception
115
- raise @second_proc_exception
116
- end
117
-
118
- first_proc = proc{ |exception, context| @first_caught_exception = exception }
119
-
120
- Assert.stub(@daemon_data, :error_procs){ [first_proc, second_proc, third_proc] }
121
- @handler = @handler_class.new(@exception, @context_hash).tap(&:run)
122
- end
123
- subject{ @handler }
124
-
125
- should "call each proc, passing the previously raised exception to the next" do
126
- assert_equal @exception, @third_caught_exception
127
- assert_equal @third_proc_exception, @second_caught_exception
128
- assert_equal @second_proc_exception, @first_caught_exception
96
+ should "set its exception to the last exception thrown by the procs" do
97
+ assert_equal @proc_exceptions.last, subject.exception
129
98
  end
130
99
 
131
100
  end
@@ -160,4 +129,21 @@ class Qs::ErrorHandler
160
129
 
161
130
  end
162
131
 
132
+ class ErrorProcSpy
133
+ attr_reader :called, :exception, :context
134
+ attr_accessor :raise_exception
135
+
136
+ def initialize
137
+ @called = false
138
+ end
139
+
140
+ def call(exception, context)
141
+ @called = true
142
+ @exception = exception
143
+ @context = context
144
+
145
+ raise self.raise_exception if self.raise_exception
146
+ end
147
+ end
148
+
163
149
  end
@@ -8,6 +8,14 @@ class Qs::IOPipe
8
8
  class UnitTests < Assert::Context
9
9
  desc "Qs::IOPipe"
10
10
  setup do
11
+ # mimic how IO.select responds
12
+ @io_select_response = Factory.boolean ? [[NULL], [], []] : nil
13
+ @io_select_called_with = nil
14
+ Assert.stub(IO, :select) do |*args|
15
+ @io_select_called_with = args
16
+ @io_select_response
17
+ end
18
+
11
19
  @io_pipe = Qs::IOPipe.new
12
20
  end
13
21
  subject{ @io_pipe }
@@ -60,15 +68,15 @@ class Qs::IOPipe
60
68
  should "be able to wait until there is something to read" do
61
69
  subject.setup
62
70
 
63
- result = nil
64
- thread = Thread.new{ result = subject.wait }
65
- thread.join(0.1)
66
- assert_equal 'sleep', thread.status
71
+ result = subject.wait
72
+ exp = [[subject.reader], nil, nil, nil]
73
+ assert_equal exp, @io_select_called_with
74
+ assert_equal !!@io_select_response, result
67
75
 
68
- subject.write(Factory.string)
69
- thread.join
70
- assert_false thread.status
71
- assert_equal subject, result
76
+ timeout = Factory.integer
77
+ subject.wait(timeout)
78
+ exp = [[subject.reader], nil, nil, timeout]
79
+ assert_equal exp, @io_select_called_with
72
80
  end
73
81
 
74
82
  end
@@ -13,6 +13,10 @@ class Qs::Process
13
13
  end
14
14
  subject{ @process_class }
15
15
 
16
+ should "know its wait for signals timeout" do
17
+ assert_equal 15, WAIT_FOR_SIGNALS_TIMEOUT
18
+ end
19
+
16
20
  end
17
21
 
18
22
  class InitTests < UnitTests
@@ -117,9 +121,20 @@ class Qs::Process
117
121
  class RunTests < RunSetupTests
118
122
  desc "and run"
119
123
  setup do
124
+ @wait_timeout = nil
125
+ Assert.stub(@process.signal_io, :wait) do |timeout|
126
+ @wait_timeout = timeout
127
+ sleep 0.1
128
+ false
129
+ end
130
+
120
131
  @thread = Thread.new{ @process.run }
121
132
  @thread.join(0.1)
122
133
  end
134
+ teardown do
135
+ # manually unstub or the process thread will hang forever
136
+ Assert.unstub(@process.signal_io, :wait)
137
+ end
123
138
 
124
139
  should "not daemonize the process" do
125
140
  assert_false @daemonize_called
@@ -143,6 +158,7 @@ class Qs::Process
143
158
  end
144
159
 
145
160
  should "sleep its thread waiting for signals" do
161
+ assert_equal WAIT_FOR_SIGNALS_TIMEOUT, @wait_timeout
146
162
  assert_equal 'sleep', @thread.status
147
163
  end
148
164
 
@@ -262,6 +278,30 @@ class Qs::Process
262
278
 
263
279
  end
264
280
 
281
+ class RunWithDaemonCrashTests < RunSetupTests
282
+ desc "and run with the daemon crashing"
283
+ setup do
284
+ # lower the time it sleeps so it loops and restarts the daemon quicker
285
+ Assert.stub(@process.signal_io, :wait) do |timeout|
286
+ sleep 0.1
287
+ false
288
+ end
289
+
290
+ @thread = Thread.new{ @process.run }
291
+ @daemon_spy.start_called = false
292
+ @thread.join(0.1)
293
+ end
294
+ teardown do
295
+ # manually unstub or the process thread will hang forever
296
+ Assert.unstub(@process.signal_io, :wait)
297
+ end
298
+
299
+ should "re-start its daemon" do
300
+ assert_true @daemon_spy.start_called
301
+ end
302
+
303
+ end
304
+
265
305
  class RunWithInvalidSignalTests < RunSetupTests
266
306
  desc "and run with unsupported signals"
267
307
  setup do
@@ -402,6 +442,10 @@ class Qs::Process
402
442
  @halt_args = args
403
443
  @halt_called = true
404
444
  end
445
+
446
+ def running?
447
+ !!@start_called
448
+ end
405
449
  end
406
450
 
407
451
  class RestartCmdSpy
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qs
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 3
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Kelly Redding
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2015-05-01 00:00:00 Z
19
+ date: 2015-05-05 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  requirement: &id001 !ruby/object:Gem::Requirement