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.
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: []