qs 0.2.0 → 0.3.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.
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