cabiri 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/cabiri.rb +92 -0
  2. metadata +56 -0
@@ -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: []