job_dispatch 0.0.2 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e00ac3ea419f26f9e3d8ae4a52a73c267f306ce7
4
- data.tar.gz: 2f2941010e8775f2e6ec0759952438fd899f17a5
3
+ metadata.gz: 401fd02367fe4acc36558989fe00883365afcd87
4
+ data.tar.gz: f45f4e6595a88462a394deb27a103d9a9d274ae5
5
5
  SHA512:
6
- metadata.gz: 03c7b5606808c97b37c0c3903ec9058db29f8f96741a5b4b42a2220e459017249b9b48cb6b701c8fbd75392e0ba212bee8dc38ff29257512fd119e39b3739671
7
- data.tar.gz: 414171d3052dcfdc29f5c8c30f3216aeb755d4bdf96c205452016241c1ac493bd859c374d29c24d1287709092bc16e0d04cda87b020a13b9f4228bf9f8bf13a7
6
+ metadata.gz: d07c1dc6f62db463ec888a8e3130893bfb37541854756477200ecc77d79ae8e0e615d4773925b18f71798fccb59d3bb308c1e8ec7e1696958b9b694dd5e7c161
7
+ data.tar.gz: f3a9e4646d3f4e0e1aea6b5b7691758f3b29057215bbc104875239375a6efb0a99f1ea035d86e7e6ed6dc913d87dffcf8731b42c8e8874a52400aadcdccb1348
data/.gitignore CHANGED
@@ -16,4 +16,6 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .idea
19
+ *.orig
20
+ *.swp
19
21
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # job_dispatch change log
2
2
 
3
+ ## Version 0.1.0
4
+
5
+ * Added 'num_tcp_connections' to status command response
6
+ * Fixed leaking sockets from job-worker command.
7
+
3
8
  ## Version 0.0.2
4
9
 
5
10
  * Broker sends an idle command to a worker immediately upon connect. This helps recover from a case where a worker
data/bin/job-worker CHANGED
@@ -28,8 +28,6 @@ JobDispatch.load_config_from_yml('config/job_dispatch.yml', ENV["RAILS_ENV"])
28
28
 
29
29
  require File.expand_path('config/environment.rb', ROOT_DIR)
30
30
 
31
- JobDispatch.logger = Rails.logger
32
-
33
31
  endpoint = JobDispatch.config.broker[:connect]
34
32
  if endpoint.nil? || endpoint.empty?
35
33
  $stderr.puts "No Job Dispatch broker connect address has been specified."
@@ -11,7 +11,7 @@ module JobDispatch
11
11
  class Broker
12
12
 
13
13
  WORKER_IDLE_TIME = 10.123
14
- POLL_TIME = 5.123
14
+ POLL_TIME = 31
15
15
  STOP_SIGNALS = %w[INT TERM KILL]
16
16
 
17
17
  IdleWorker = Struct.new :worker_id, :idle_since, :queue, :worker_name, :idle_count
@@ -32,6 +32,7 @@ module JobDispatch
32
32
  attr :job_subscribers # Key: job_id, value: list of Socket Identities waiting for job completion notifications.
33
33
  attr :pub_socket
34
34
  attr_accessor :reply_exceptions
35
+ attr :queues_ready
35
36
 
36
37
  def initialize(worker_bind_address, wakeup_bind_address, publish_bind_address=nil)
37
38
  @worker_bind_address = worker_bind_address
@@ -48,6 +49,7 @@ module JobDispatch
48
49
  @jobs_in_progress_workers = {} #key: job_id, value: worker_id
49
50
  @worker_names = {} # Key: Symbol socket identity, value: String claimed name of worker
50
51
  @job_subscribers = {} # Key: job_id, value: list of Socket Identities waiting for job completion notifications.
52
+ @queues_ready = {} # Key: Symbol queue name, value: bool ready?
51
53
  @status = "OK"
52
54
  @reply_exceptions = true
53
55
 
@@ -127,7 +129,7 @@ module JobDispatch
127
129
  # TODO: calculate the amount of time to sleep to wake up such that a scheduled event happens as close
128
130
  # as possible to the time it was supposed to happen. This could additionally mean that the POLL_TIME
129
131
  # could be arbitrarily large. As any communication with the broker will wake it immediately.
130
- poll_time = POLL_TIME
132
+ poll_time = JobDispatch.config.broker_options.try(:[], :poll_time) || POLL_TIME
131
133
  poller.poll(poll_time)
132
134
 
133
135
  if @wake_socket && poller.readables.include?(@wake_socket)
@@ -136,6 +138,7 @@ module JobDispatch
136
138
 
137
139
  if poller.readables.include?(socket.socket)
138
140
  command = read_command
141
+ JobDispatch.logger.debug("JobDispatch::Broker received command: #{command.command}(#{command.parameters.inspect})")
139
142
  reply = process_command(command)
140
143
  send_command(reply) if reply
141
144
  end
@@ -217,6 +220,12 @@ module JobDispatch
217
220
  when "enqueue"
218
221
  reply.parameters = create_job(command)
219
222
 
223
+ when "last"
224
+ reply.parameters = last_job(command)
225
+
226
+ when "fetch"
227
+ reply.parameters = fetch_job(command)
228
+
220
229
  when "quit"
221
230
  process_quit
222
231
  reply.parameters = {:status => 'bye'}
@@ -233,6 +242,7 @@ module JobDispatch
233
242
  if reply_exceptions
234
243
  # all others reply over socket.
235
244
  JobDispatch.logger.error("JobDispatch::Broker #{e}")
245
+ JobDispatch.logger.error e.backtrace.join("\n")
236
246
  reply.parameters = {:status => 'error', :message => e.to_s}
237
247
  else
238
248
  # used during testing to raise errors so that Rspec can catch them as a test failure.
@@ -294,6 +304,7 @@ module JobDispatch
294
304
  idle_worker = IdleWorker.new(command.worker_id, Time.now, queue, command.worker_name, idle_count)
295
305
  workers_waiting_for_jobs[command.worker_id] = idle_worker
296
306
  queues[queue] << command.worker_id
307
+ queues_ready[queue] = true
297
308
  if command.worker_name # this is only sent on initial requests.
298
309
  worker_names[command.worker_id] = command.worker_name
299
310
  end
@@ -323,9 +334,9 @@ module JobDispatch
323
334
 
324
335
  def dispatch_jobs_to_workers
325
336
  # dequeue jobs from database for each queue
326
- @queues.each_pair do |queue, worker_ids|
337
+ queues.each_pair do |queue, worker_ids|
327
338
  # we only need to check the database if there are available workers in that queue
328
- if worker_ids.count > 0
339
+ if worker_ids.count > 0 && queues_ready[queue]
329
340
  worker_id = worker_ids.first
330
341
 
331
342
  job = begin
@@ -344,8 +355,10 @@ module JobDispatch
344
355
  job.expire_execution_at = Time.now + (job.timeout || Job::DEFAULT_EXECUTION_TIMEOUT)
345
356
  job.status = JobDispatch::Job::IN_PROGRESS
346
357
  job.save
347
-
348
358
  publish_job_status(job)
359
+ else
360
+ # no job. mark the queue as not ready so we don't repeatedly check for jobs in an empty queue.
361
+ queues_ready[queue] = false
349
362
  end
350
363
  end
351
364
  end
@@ -425,19 +438,24 @@ module JobDispatch
425
438
 
426
439
 
427
440
  def json_for_job(job)
428
- hash = if job.respond_to? :as_job_queue_item
429
- job.as_job_queue_item
430
- else
431
- job.as_json
432
- end.with_indifferent_access
433
- hash[:id] = hash[:id].to_s
434
- hash
441
+ if job
442
+ hash = if job.respond_to? :as_job_queue_item
443
+ job.as_job_queue_item
444
+ else
445
+ job.as_json
446
+ end.with_indifferent_access
447
+ hash[:id] = hash[:id].to_s
448
+ hash
449
+ end
435
450
  end
436
451
 
437
452
 
438
453
  def status_response
454
+ num_tcp_connections = `lsof -p #{Process.pid}`.split.select { |l| l=~ /TCP/ }.count
455
+
439
456
  response = {
440
457
  :status => status,
458
+ :num_tcp_connections => num_tcp_connections,
441
459
  :queues => {}
442
460
  }
443
461
 
@@ -477,14 +495,15 @@ module JobDispatch
477
495
  # @return [Hash] result to be sent to client.
478
496
  def touch_job(command)
479
497
  job_id = command.parameters[:job_id]
480
- timeout = command.parameters[:timeout] || Job::DEFAULT_EXECUTION_TIMEOUT
481
498
  job = @jobs_in_progress[job_id]
482
499
  if job
500
+ timeout = command.parameters[:timeout] || job.timeout || Job::DEFAULT_EXECUTION_TIMEOUT
483
501
  job.expire_execution_at = Time.now + timeout
484
502
  JobDispatch.logger.info("JobDispatch::Broker#touch timeout on job #{job_id} to #{job.expire_execution_at}")
485
503
  job.save
486
504
  {status: "success"}
487
505
  else
506
+ JobDispatch.logger.info("JobDispatch::Broker#touch job #{job_id} not in progress.")
488
507
  {status: "error", message: "the specified job does not appear to be in progress"}
489
508
  end
490
509
  end
@@ -494,10 +513,42 @@ module JobDispatch
494
513
  raise MissingParameterError, "Missing 'job' from command" unless command.parameters[:job].present?
495
514
 
496
515
  job_attrs = command.parameters[:job]
516
+ job_attrs[:queue] ||= :default
497
517
  job = job_source.create!(job_attrs)
518
+ queues_ready[job_attrs[:queue].to_sym] = true
498
519
  {status: 'success', job_id: job.id.to_s}
499
520
  rescue StandardError => e
500
521
  JobDispatch.logger.error "JobDispatch::Broker#create_job error: #{e}"
522
+ JobDispatch.logger.error e.backtrace.join("\n")
523
+ {status: 'error', message: e.to_s}
524
+ end
525
+ end
526
+
527
+ def last_job(command)
528
+ begin
529
+ queue = command.parameters[:queue] || 'default'
530
+ job = job_source.where(:queue => queue).last
531
+ if job
532
+ {status: 'success', job: json_for_job(job)}
533
+ else
534
+ {status: 'error', message: 'no last job'}
535
+ end
536
+ rescue StandardError => e
537
+ JobDispatch.logger.error e.to_s
538
+ JobDispatch.logger.error e.backtrace.join("\n")
539
+ {status: 'error', message: e.to_s}
540
+ end
541
+ end
542
+
543
+ def fetch_job(command)
544
+ begin
545
+ raise "Missing parameter 'job_id'" unless command.parameters[:job_id]
546
+ job = job_source.find(command.parameters[:job_id])
547
+ raise "Job not found" unless job
548
+ {status: 'success', job: json_for_job(job)}
549
+ rescue StandardError => e
550
+ JobDispatch.logger.error e.to_s
551
+ JobDispatch.logger.error e.backtrace.join("\n")
501
552
  {status: 'error', message: e.to_s}
502
553
  end
503
554
  end
@@ -34,14 +34,46 @@ module JobDispatch
34
34
  SynchronousProxy.new(self, target, options)
35
35
  end
36
36
 
37
- def enqueue(job_attrs)
37
+ # Enqueue a job to be processed describe by the passed job attributes.
38
+ #
39
+ # Required attributes:
40
+ # target: The target object that will execute the job. typically a class.
41
+ # method: the message to be sent to the target.
42
+ # Optional:
43
+ # parameters: an array of parameters to be passed to the method.
44
+ # timeout: number of seconds after which the job is considered timed out and failed.
45
+ def enqueue(job_attrs={})
38
46
  send_request('enqueue', {job: job_attrs})
39
47
  end
40
48
 
49
+ # send a message to the dispatcher requesting to be notified when the job completes (or fails).
41
50
  def notify(job_id)
42
51
  send_request('notify', {job_id: job_id})
43
52
  end
44
53
 
54
+ # as the dispatcher what was the last job enqueued on the given queue (or default)
55
+ def last(queue=nil)
56
+ job_or_raise send_request('last', {queue: queue||'default'})
57
+ end
58
+
59
+ # fetch the complete details for hte last job
60
+ def fetch(job_id)
61
+ job_or_raise send_request('fetch', {job_id: job_id})
62
+ end
63
+
64
+ private
65
+
66
+ def job_or_raise(response)
67
+ if response.is_a?(Hash) && response[:status] == 'success'
68
+ response[:job]
69
+ else
70
+ p response
71
+ raise ClientError, response[:message]
72
+ end
73
+ end
74
+ end
75
+
76
+ class ClientError < StandardError
45
77
  end
46
78
  end
47
79
 
@@ -1,3 +1,3 @@
1
1
  module JobDispatch
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -102,6 +102,7 @@ module JobDispatch
102
102
  job_id = Thread.current["JobDispatch::Worker.job_id"]
103
103
  if sock && job_id
104
104
  sock.send_touch(job_id, timeout)
105
+ JobDispatch.logger.debug { "touching job #{job_id}"}
105
106
  end
106
107
  end
107
108
 
@@ -36,6 +36,7 @@ module JobDispatch
36
36
  backtrace: ex.backtrace,
37
37
  }
38
38
  @status = :error
39
+ JobDispatch.logger.debug ex
39
40
  ensure
40
41
  Thread.current["JobDispatch::Worker.job_id"] = nil
41
42
  JobDispatch.logger.info "Worker completed job #{job_id}: #{target}.#{method}, status: #{@status}"
@@ -8,11 +8,14 @@ module JobDispatch
8
8
  class Socket
9
9
 
10
10
  attr :socket
11
+ attr :touch_socket
11
12
  attr :item_class
12
13
 
13
14
  def initialize(connect_address, item_klass)
14
15
  @socket = JobDispatch.context.socket(ZMQ::REQ)
15
16
  @socket.connect(connect_address)
17
+ @touch_socket = JobDispatch.context.socket(ZMQ::DEALER)
18
+ @touch_socket.connect(connect_address)
16
19
  @item_class = item_klass
17
20
  end
18
21
 
@@ -29,7 +32,14 @@ module JobDispatch
29
32
  end
30
33
 
31
34
  def close
32
- @socket.close
35
+ if @socket
36
+ @socket.close rescue nil
37
+ @socket = nil
38
+ end
39
+ if @touch_socket
40
+ @touch_socket.close rescue nil
41
+ @touch_socket = nil
42
+ end
33
43
  end
34
44
 
35
45
  def identity
@@ -45,8 +55,9 @@ module JobDispatch
45
55
  #
46
56
  # @return [JobDispatch::Item] the item to be processed (or nil if there isn't a valid job)
47
57
  def read_item
48
- json = @socket.recv
49
58
  begin
59
+ drain_touch_socket
60
+ json = @socket.recv
50
61
  params = JSON.parse(json)
51
62
  case params["command"]
52
63
  when "job"
@@ -67,6 +78,14 @@ module JobDispatch
67
78
  item
68
79
  end
69
80
 
81
+ # drain any messages that may have been received on the touch socket.
82
+ def drain_touch_socket
83
+ loop do
84
+ message = @touch_socket.recv_nonblock
85
+ break if message.nil?
86
+ end
87
+ end
88
+
70
89
  # after execution, send the response.
71
90
  def send_response(job_id, status, result)
72
91
  JobDispatch.logger.info "Worker #{Process.pid} completed job_id: #{job_id}: #{status}, result: #{result}"
@@ -86,11 +105,8 @@ module JobDispatch
86
105
  job_id: job_id
87
106
  }
88
107
  hash[:timeout] = timeout if timeout
89
- @socket.send(JSON.dump(hash))
90
- json = @socket.recv # wait for acknowledgement... this could be done via pub/sub to be asynchronous.
91
- JSON.parse(json) rescue {:error => "Failed to decode JSON from dispatcher: #{json}"}
108
+ @touch_socket.send(JSON.dump(hash))
92
109
  end
93
-
94
110
  end
95
111
  end
96
112
  end
@@ -488,6 +488,8 @@ describe JobDispatch::Broker do
488
488
  @job = FactoryGirl.build :job
489
489
  @socket = double('Broker::Socket', :send_command => nil)
490
490
  subject.stub(:socket => @socket)
491
+ @job_class = double('JobClass')
492
+ JobDispatch.config.job_class = @job_class
491
493
  end
492
494
 
493
495
  it "the job is sent to an idle worker" do
@@ -497,10 +499,8 @@ describe JobDispatch::Broker do
497
499
  expect(cmd.parameters[:target]).to eq(@job.target)
498
500
  end
499
501
 
500
- job_class = double('JobClass')
501
- job_class.stub(:dequeue_job_for_queue).and_return(@job)
502
- job_class.should_receive(:dequeue_job_for_queue).with('example')
503
- JobDispatch.config.job_class = job_class
502
+ @job_class.stub(:dequeue_job_for_queue).and_return(@job)
503
+ @job_class.should_receive(:dequeue_job_for_queue).with('example')
504
504
 
505
505
  # send ready command => adds idle worker state
506
506
  subject.workers_waiting_for_reply << worker_id # simulating read_command
@@ -511,9 +511,29 @@ describe JobDispatch::Broker do
511
511
  }))
512
512
  expect(@result).to be_nil # no immediate response
513
513
  expect(subject.workers_waiting_for_jobs[worker_id]).not_to be_nil
514
+ subject.queues_ready[:example] = true
514
515
 
515
516
  subject.dispatch_jobs_to_workers
516
517
  end
518
+
519
+ it "when no job is found, the queue is marked inactive" do
520
+ # send ready command => adds idle worker state
521
+ subject.workers_waiting_for_reply << worker_id # simulating read_command
522
+ @result = subject.process_command(Command.new(worker_id, {
523
+ command: 'ready',
524
+ queue: 'example',
525
+ worker_name: 'ruby worker',
526
+ }))
527
+
528
+ @job_class.stub(:dequeue_job_for_queue).and_return(nil)
529
+
530
+ expect(@result).to be_nil # no immediate response
531
+ expect(subject.workers_waiting_for_jobs[worker_id]).not_to be_nil
532
+ subject.queues_ready[:example] = true
533
+
534
+ subject.dispatch_jobs_to_workers
535
+ expect(subject.queues_ready[:example]).to be_false
536
+ end
517
537
  end
518
538
 
519
539
  context "when an error occurs dequeuing jobs" do
@@ -578,7 +598,7 @@ describe JobDispatch::Broker do
578
598
 
579
599
  context "touching a job" do
580
600
  before :each do
581
- @time = Time.now
601
+ @time = Time.now.change(:usec => 0)
582
602
  # this worker will be IDLE
583
603
  @job = FactoryGirl.build :job, :expire_execution_at => @time + 5.seconds
584
604
  @job_id = @job.id.to_s
@@ -594,7 +614,7 @@ describe JobDispatch::Broker do
594
614
  Timecop.freeze(@time) do
595
615
  subject.touch_job(Command.new(worker_id, {command: "touch", job_id: @job_id}))
596
616
  end
597
- expect(@job.expire_execution_at).to eq(@time + JobDispatch::Job::DEFAULT_EXECUTION_TIMEOUT)
617
+ expect(@job.expire_execution_at).to eq(@time + @job.timeout)
598
618
  end
599
619
 
600
620
  it "updates the expire_execution_at time with a custom timeout" do
@@ -608,10 +628,10 @@ describe JobDispatch::Broker do
608
628
  context "enqueue a job" do
609
629
  before :each do
610
630
  @job_attrs = FactoryGirl.attributes_for :job
631
+ JobDispatch.config.job_class = double('JobClass')
611
632
  end
612
633
 
613
634
  it "Creates a job" do
614
- JobDispatch.config.job_class = double('JobClass')
615
635
  JobDispatch.config.job_class.should_receive(:create!).with(@job_attrs)
616
636
  command = Command.new(:some_client, {command: "enqueue", job: @job_attrs})
617
637
  subject.process_command(command)
@@ -619,7 +639,6 @@ describe JobDispatch::Broker do
619
639
 
620
640
  it "returns the job id" do
621
641
  job_id = 12345
622
- JobDispatch.config.job_class = double('JobClass')
623
642
  JobDispatch.config.job_class.stub(:create! => double('Job', :id => job_id))
624
643
  command = Command.new(:some_client, {command: "enqueue", job: @job_attrs})
625
644
  result = subject.process_command(command)
@@ -628,7 +647,6 @@ describe JobDispatch::Broker do
628
647
  end
629
648
 
630
649
  it "returns an error if the arguments are no good" do
631
- JobDispatch.config.job_class = double('JobClass')
632
650
  JobDispatch.config.job_class.stub(:create!).and_raise("no good") # simulate some database save error
633
651
  command = Command.new(:some_client, {command: "enqueue", job: @job_attrs})
634
652
  result = subject.process_command(command)
@@ -637,13 +655,18 @@ describe JobDispatch::Broker do
637
655
  end
638
656
 
639
657
  it "returns an error if the 'job' parameter is missing" do
640
- JobDispatch.config.job_class = double('JobClass')
641
658
  command = Command.new(:some_client, {command: "enqueue"})
642
659
  result = subject.process_command(command)
643
660
  expect(result.parameters[:status]).to eq('error')
644
661
  end
645
- end
646
662
 
663
+ it "marks the queue as ready" do
664
+ JobDispatch.config.job_class.stub(:create! => double('Job', :id => 1))
665
+ command = Command.new(:some_client, {command: "enqueue", job: @job_attrs})
666
+ result = subject.process_command(command)
667
+ expect(subject.queues_ready[:default]).to be_true
668
+ end
669
+ end
647
670
 
648
671
  context "'notify'" do
649
672
 
@@ -779,4 +802,61 @@ describe JobDispatch::Broker do
779
802
  end
780
803
  end
781
804
 
805
+ context "last" do
806
+ let(:json){ {'id' => '12341234', 'target' => 'Example', 'method' => 'method'}}
807
+ before do
808
+ @job_class = double('JobClass')
809
+ JobDispatch.config.job_class = @job_class
810
+ end
811
+ it "returns last job in specified queue" do
812
+ command = Command.new(:client, {command: 'last', queue: 'ruby'})
813
+ relation = double('relation')
814
+ @job_class.should_receive(:where).with(queue: 'ruby').and_return(relation)
815
+ relation.should_receive(:last).and_return(double("job", id: "12341234", as_json: json))
816
+ result = subject.process_command(command)
817
+ expect(result.parameters).to eq({status: 'success', job: json})
818
+ end
819
+ it "returns last job in default queue" do
820
+ command = Command.new(:client, {command: 'last'})
821
+ relation = double('relation')
822
+ @job_class.should_receive(:where).with(queue: 'default').and_return(relation)
823
+ relation.should_receive(:last).and_return(double("job", id: "12341234", as_json: json))
824
+ result = subject.process_command(command)
825
+ expect(result.parameters).to eq({status: 'success', job: json})
826
+ end
827
+ it "handles no last job" do
828
+ command = Command.new(:client, {command: 'last'})
829
+ relation = double('relation')
830
+ @job_class.should_receive(:where).with(queue: 'default').and_return(relation)
831
+ relation.should_receive(:last).and_return(nil)
832
+ result = subject.process_command(command)
833
+ expect(result.parameters).to eq({status: 'error', message: 'no last job'})
834
+ end
835
+ end
836
+
837
+ context "fetch" do
838
+ before do
839
+ @job_class = double('JobClass')
840
+ JobDispatch.config.job_class = @job_class
841
+ end
842
+ it "returns the job" do
843
+ command = Command.new(:client, {command: 'fetch', job_id: '12341234'})
844
+ json = {'id' => '12341234', 'queue' => 'ruby', 'target' => 'String', 'method' => 'new'}
845
+ job = double("Job", as_json: json)
846
+ @job_class.should_receive(:find).with('12341234').and_return(job)
847
+ result = subject.process_command(command)
848
+ expect(result.parameters).to eq({status: 'success', job: json})
849
+ end
850
+ it "returns error when job_id is not present" do
851
+ command = Command.new(:client, {command: 'fetch'})
852
+ result = subject.process_command(command)
853
+ expect(result.parameters[:status]).to eq('error')
854
+ end
855
+ it "returns error when job_id is not found" do
856
+ command = Command.new(:client, {command: 'fetch', job_id: '12341234'})
857
+ @job_class.should_receive(:find).with('12341234').and_raise(StandardError, "not found")
858
+ result = subject.process_command(command)
859
+ expect(result.parameters[:status]).to eq('error')
860
+ end
861
+ end
782
862
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: job_dispatch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Connolly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-13 00:00:00.000000000 Z
11
+ date: 2014-07-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbczmq