cabiri 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.
- data/lib/cabiri.rb +92 -0
- metadata +56 -0
data/lib/cabiri.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'adeona'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module Cabiri
|
5
|
+
class JobQueue
|
6
|
+
# the only thing here that is not self evident is the use of self_pipe.
|
7
|
+
# This will be used by the wait_until_finished method to implement a
|
8
|
+
# blocking wait. More information can be found in the comments of that
|
9
|
+
# method.
|
10
|
+
def initialize
|
11
|
+
@remaining_jobs = []
|
12
|
+
@active_jobs_pids = []
|
13
|
+
|
14
|
+
@self_pipe = IO.pipe()
|
15
|
+
@self_pipe[0].sync = true
|
16
|
+
@self_pipe[1].sync = true
|
17
|
+
|
18
|
+
@logger = Logger.new($stdout)
|
19
|
+
end
|
20
|
+
|
21
|
+
def add(&block)
|
22
|
+
@remaining_jobs << block
|
23
|
+
end
|
24
|
+
|
25
|
+
def finished?
|
26
|
+
@remaining_jobs.empty? and @active_jobs_pids.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
# this is a blocking wait that won't return until after all jobs in the
|
30
|
+
# queue are finished. The initialize method has set up a self_pipe. When
|
31
|
+
# the last job of the queue is finished, the start method will close the
|
32
|
+
# write end of this pipe. This causes the kernel to notice that nothing can
|
33
|
+
# write to the pipe anymore and thus the kernel sends an EOF down this pipe,
|
34
|
+
# which in turn causes IO.select to return.
|
35
|
+
# When IO.select returns we close the read end of the pipe, such that any
|
36
|
+
# future calls to the wait_until_finished method can return immediately.
|
37
|
+
def wait_until_finished
|
38
|
+
if(!@self_pipe[0].closed?)
|
39
|
+
IO.select([@self_pipe[0]])
|
40
|
+
@self_pipe[0].close
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# here we start by defining a signal handler that deals with SIGCHLD signals
|
45
|
+
# (a signal that indicates that a child process has terminated). When we receive
|
46
|
+
# such a signal we get the pid and make sure that the child process was one of
|
47
|
+
# the jobs belonging to the job queue.
|
48
|
+
# This needs to be done inside a while loop as two or more child processes exiting
|
49
|
+
# in quick succession might only generate one signal. For example, the first dead
|
50
|
+
# child process will generate a SIGCHLD. However, when a second process dies quickly
|
51
|
+
# afterwards and the previous SIGCHLD signal has not yet been handled, this second
|
52
|
+
# process won't send a second SIGCHLD signal, but will instead assume that the
|
53
|
+
# SIGCHLD handler knows to look for multiple dead processes.
|
54
|
+
# You might also notice that old_handler is being used to redirect this signal to
|
55
|
+
# a possible other previously defined SIGCHLD signal handler.
|
56
|
+
# Also note that we close the write end of the self_pipe when there are no jobs left.
|
57
|
+
# See the comments on the wait_until_finished method for more information on this.
|
58
|
+
def start(max_active_jobs)
|
59
|
+
old_handler = trap(:CLD) do
|
60
|
+
begin
|
61
|
+
while pid = Process.wait(-1, Process::WNOHANG)
|
62
|
+
if(@active_jobs_pids.include?(pid))
|
63
|
+
@active_jobs_pids.delete(pid)
|
64
|
+
fill_job_slots(max_active_jobs)
|
65
|
+
@self_pipe[1].close if finished?
|
66
|
+
end
|
67
|
+
old_handler.call if old_handler.respond_to?(:call)
|
68
|
+
end
|
69
|
+
rescue Errno::ECHILD
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
fill_job_slots(max_active_jobs)
|
74
|
+
end
|
75
|
+
|
76
|
+
# here we fill all the empty job slots. When we take a new job two things can happen.
|
77
|
+
# Either we manage to successfully spawn a new process or something goes wrong and we log
|
78
|
+
# it. In either case we assume that we are done with the job and remove it from the
|
79
|
+
# remaining_jobs array.
|
80
|
+
def fill_job_slots(max_active_jobs)
|
81
|
+
while(@active_jobs_pids.length < max_active_jobs and !@remaining_jobs.empty?)
|
82
|
+
begin
|
83
|
+
@active_jobs_pids << Adeona.spawn_child(:detach => false) { @remaining_jobs[0].call }
|
84
|
+
rescue => ex
|
85
|
+
@logger.warn(self.class.to_s) { "Exception thrown when trying to instantiate job. Job info: #{@remaining_jobs[0].to_s}. Exception info: #{ex.to_s}." }
|
86
|
+
ensure
|
87
|
+
@remaining_jobs.delete_at(0)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cabiri
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Van Eyck
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-01 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: adeona
|
16
|
+
requirement: &70273552307120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70273552307120
|
25
|
+
description: An easy and intuitive Ruby job queue.
|
26
|
+
email: tomvaneyck@gmail.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files: []
|
30
|
+
files:
|
31
|
+
- lib/cabiri.rb
|
32
|
+
homepage: https://github.com/vaneyckt/Cabiri
|
33
|
+
licenses: []
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.17
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: An easy and intuitive Ruby job queue.
|
56
|
+
test_files: []
|