procrastinate 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY.txt CHANGED
@@ -1,6 +1,7 @@
1
- = 0.4.1 / 23Jan2012
1
+ = 0.5 / last minor release: 18Dez2012
2
2
 
3
3
  + Replaces the custom IPC implementation with the cod gem.
4
+ * Maintenance release to be able to use modern gems.
4
5
 
5
6
  = 0.4.0 / 16Dez2011
6
7
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require "rubygems"
2
- require "rdoc/task"
3
2
  require 'rspec/core/rake_task'
4
3
  require 'rubygems/package_task'
4
+ require 'rake/clean'
5
5
 
6
6
  desc "Run all tests: Exhaustive."
7
7
  RSpec::Core::RakeTask.new
@@ -15,19 +15,14 @@ task :stats do
15
15
  end
16
16
  end
17
17
 
18
- # Generate documentation
19
- RDoc::Task.new do |rdoc|
20
- rdoc.title = "procrastinate - a framework to run tasks in separate processes."
21
- rdoc.options << '--line-numbers'
22
- rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
23
- rdoc.template = 'direct' # lighter template used on railsapi.com
24
- rdoc.main = "README"
25
- rdoc.rdoc_files.include("README", "lib/**/*.rb")
26
- rdoc.rdoc_dir = "rdoc"
18
+ require 'yard'
19
+ YARD::Rake::YardocTask.new do |t|
20
+ # t.files = ['lib/**/*.rb']
21
+ # t.options = ['--any', '--extra', '--opts'] # optional
27
22
  end
28
23
 
29
24
  desc 'Clear out RDoc'
30
- task :clean => [:clobber_rdoc, :clobber_package]
25
+ task :clean => [:clobber_package]
31
26
 
32
27
  # This task actually builds the gem.
33
28
  task :gem => :spec
@@ -37,3 +32,5 @@ desc "Generate the gem package."
37
32
  Gem::PackageTask.new(spec) do |pkg|
38
33
  # pkg.need_tar = true
39
34
  end
35
+
36
+ CLEAN << 'doc'
@@ -0,0 +1,31 @@
1
+
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'procrastinate'
5
+
6
+ # * murder lazies
7
+ # * maintain worker count
8
+
9
+ scheduler = Procrastinate::Scheduler.start
10
+ scheduler.spawn_workers(6) {
11
+ # Worker body
12
+ loop do
13
+ puts "Hiho from worker #{Process.pid}."
14
+ sleep rand(1.0) * 3
15
+ end
16
+ }
17
+
18
+ # Wait around until something important happens
19
+ r, w = IO.pipe
20
+ trap('QUIT') { w.write '.' }
21
+
22
+ loop do
23
+ IO.select([r], nil, nil)
24
+ r.read_nonblock(1000)
25
+
26
+ # When we reach this point, a QUIT signal has been sent to the process.
27
+ # Abort.
28
+ break
29
+ end
30
+
31
+ scheduler.shutdown
data/lib/procrastinate.rb CHANGED
@@ -13,4 +13,5 @@ end
13
13
  require 'procrastinate/spawn_strategy'
14
14
  require 'procrastinate/proxy'
15
15
  require 'procrastinate/process_manager'
16
- require 'procrastinate/scheduler'
16
+ require 'procrastinate/scheduler'
17
+ require 'procrastinate/server'
@@ -2,57 +2,58 @@
2
2
  require 'procrastinate'
3
3
 
4
4
  module Procrastinate
5
- # call-seq:
6
- # Procrastinate.scheduler => scheduler
7
- #
8
5
  # Returns the scheduler instance. When using procrastinate/implicit, there
9
6
  # is one global scheduler to your ruby process, this one.
7
+ #
8
+ # @return [Scheduler] singleton scheduler for implicit scheduling.
10
9
  #
11
10
  def scheduler
12
11
  @scheduler ||= Scheduler.start
13
12
  end
14
13
  module_function :scheduler
15
14
 
16
- # call-seq:
17
- # Procrastinate.proxy(obj) => proxy
18
- #
19
15
  # Creates a proxy that will execute methods called on obj in a child process.
20
16
  #
21
- # Example:
22
- #
17
+ # @example
23
18
  # proxy = Procrastinate.proxy("foo")
24
19
  # r = proxy += "bar"
25
20
  # r.value # => 'foobar'
26
21
  #
22
+ # @param obj [Object] Ruby object that the calls need to be proxied to
23
+ # @return [Proxy] proxy object that will execute method calls in child
24
+ # processes
25
+ #
27
26
  def proxy(obj)
28
27
  scheduler.proxy(obj)
29
28
  end
30
29
  module_function :proxy
31
30
 
32
- # call-seq:
33
- # Procrastinate.schedule { some_work }
34
- #
35
31
  # Schedules a block to be executed in its own thread. Returns the future that
36
32
  # will return the blocks return value.
37
33
  #
34
+ # @example
35
+ # r = Procrastinate.schedule { do_some_work }
36
+ # r.value # => the blocks return value
37
+ #
38
+ # @param block [Proc] block that will be executed in a child process
39
+ # @return [Task::Result] a promise for the blocks return value
40
+ #
38
41
  def schedule(&block)
39
42
  scheduler.schedule(&block)
40
43
  end
41
44
  module_function :schedule
42
45
 
43
- # call-seq:
44
- # Procrastinate.join
45
- #
46
46
  # Waits for all currently scheduled tasks to complete. This is like calling
47
47
  # #value on all result objects, except that nothing is returned.
48
48
  #
49
- # Example:
50
- #
49
+ # @example
51
50
  # proxy = Procrastinate.proxy("foo")
52
51
  # r = proxy += "bar"
53
52
  # Procrastinate.join
54
53
  # r.ready? # => true
55
54
  #
55
+ # @return [void]
56
+ #
56
57
  def join
57
58
  scheduler.join
58
59
  end
@@ -62,6 +63,8 @@ module Procrastinate
62
63
  # since it consumes almost no resources when not active. This is mainly
63
64
  # useful in tests to achieve isolation.
64
65
  #
66
+ # @private
67
+ #
65
68
  def shutdown
66
69
  scheduler.shutdown
67
70
  end
@@ -166,13 +166,18 @@ class Procrastinate::ProcessManager
166
166
  # Ignore: This means that no childs remain.
167
167
  end
168
168
 
169
- # Spawns a process to work on +task+. If a block is given, it is called
170
- # when the task completes. This method should only be called from a strategy
169
+ # Spawns a process to work on +task+. If a block is given, it is called when
170
+ # the task completes. This method should only be called from a strategy
171
171
  # inside the dispatchers thread. Otherwise it will expose threading issues.
172
172
  #
173
- # Example:
174
- #
175
- # spawn(wi) { |pid| puts "Task is complete" }
173
+ # @example
174
+ # create_process(wi) { puts "Task is complete" }
175
+ #
176
+ # @param task [Procrastinate::Task::Callable] task to be run inside the
177
+ # forked process
178
+ # @param completion_handler [Proc] completion handler that is called when
179
+ # the process exits
180
+ # @return [void]
176
181
  #
177
182
  def create_process(task, &completion_handler)
178
183
  # Tasks that are interested in getting messages from their childs must
@@ -224,4 +229,14 @@ class Procrastinate::ProcessManager
224
229
  finalize_children
225
230
  end
226
231
  end
232
+
233
+ # Kills all running processes by sending them a QUIT signal.
234
+ #
235
+ # @param signal [String] signal to send to the forked processes.
236
+ #
237
+ def kill_processes(signal='QUIT')
238
+ children.each do |pid, process|
239
+ Process.kill(signal, pid)
240
+ end
241
+ end
227
242
  end
@@ -3,16 +3,41 @@ require 'thread'
3
3
 
4
4
  # API Frontend for the procrastinate library. Allows scheduling of tasks and
5
5
  # workers in seperate processes and provides minimal locking primitives.
6
+ #
7
+ # == Synopsis
8
+ # scheduler = Procrastinate::Scheduler.start
9
+ #
10
+ # Schedule a block to run in its own process:
11
+ # result = scheduler.schedule { Process.pid }
12
+ # result.value # => child process pid
6
13
  #
7
- # Each scheduler owns its own thread that does all the processing. The
8
- # interface between your main thread and the procrastinate thread is defined
9
- # in this class.
14
+ # Or schedule a message call to an object to be run in another process:
15
+ # proxy = scheduler.proxy(1)
16
+ # result = proxy + 2
17
+ # result.value # => 3
18
+ #
19
+ # You can ask the result value if it is ready yet:
20
+ # result.ready? # true/false
21
+ #
22
+ # Stop the scheduler, waiting for all scheduled work to finish:
23
+ # scheduler.shutdown
24
+ #
25
+ # Or shutting down hard, doesn't wait for work to finish:
26
+ # scheduler.shutting(true)
27
+ #
28
+ # @note Each scheduler owns its own thread that does all the processing. The
29
+ # interface between your main thread and the procrastinate thread is defined
30
+ # in this class.
10
31
  #
11
32
  class Procrastinate::Scheduler
33
+ # Process manager associated with this scheduler
12
34
  attr_reader :manager
35
+ # Schedule strategy associated with this scheduler
13
36
  attr_reader :strategy
14
- attr_reader :task_queue
37
+ # Task queue
38
+ attr_reader :task_producer
15
39
 
40
+ # @see Scheduler.start
16
41
  def initialize(strategy)
17
42
  @strategy = strategy || Procrastinate::SpawnStrategy::Default.new
18
43
  @manager = Procrastinate::ProcessManager.new
@@ -20,11 +45,18 @@ class Procrastinate::Scheduler
20
45
  # State takes three values: :running, :soft_shutdown, :real_shutdown
21
46
  # :soft_shutdown will not accept any new tasks and wait for completion
22
47
  # :real_shutdown will stop as soon as possible (still closing down nicely)
23
- @state = :running
24
- @task_queue = Queue.new
48
+ @state = :running
49
+
50
+ # If we're used in server mode, this will be replaced with a task producer
51
+ # that produces new worker processes.
52
+ @task_producer = Queue.new
25
53
  end
26
54
 
27
- # Starts a new scheduler
55
+ # Starts a new scheduler.
56
+ #
57
+ # @param strategy [SpawnStrategy] strategy to use when spawning new processes.
58
+ # Will default to {SpawnStrategy::Default}.
59
+ # @return [Scheduler]
28
60
  #
29
61
  def self.start(strategy=nil)
30
62
  new(strategy).
@@ -37,11 +69,13 @@ class Procrastinate::Scheduler
37
69
  # Returns a proxy for the +worker+ instance that will allow executing its
38
70
  # methods in a new process.
39
71
  #
40
- # Example:
41
- #
72
+ # @example
42
73
  # proxy = scheduler.proxy(worker)
43
74
  # status = proxy.do_some_work # will execute later and in its own process
44
75
  #
76
+ # @param worker [Object] Ruby object that executes the work
77
+ # @return [Proxy]
78
+ #
45
79
  def proxy(worker)
46
80
  return Procrastinate::Proxy.new(worker, self)
47
81
  end
@@ -50,6 +84,8 @@ class Procrastinate::Scheduler
50
84
  # used inside task execution processes; If you call it from your main
51
85
  # process, the result is undefined.
52
86
  #
87
+ # @return [Runtime]
88
+ #
53
89
  def runtime
54
90
  Procrastinate::Runtime.new
55
91
  end
@@ -57,6 +93,16 @@ class Procrastinate::Scheduler
57
93
  # Called by the proxy to schedule work. You can implement your own Task
58
94
  # classes; the relevant interface consists of only a #run method.
59
95
  #
96
+ # @overload schedule(task=nil)
97
+ # Runs task in its own worker process.
98
+ # @param task [#run] task to be run in its own worker process
99
+ # @return [Task::Result]
100
+ #
101
+ # @overload schedule(&block)
102
+ # Executes the Ruby block in its own worker process.
103
+ # @param block [Proc] block to be executed in worker process
104
+ # @return [Task::Result]
105
+ #
60
106
  def schedule(task=nil, &block)
61
107
  fail "Shutting down..." if @state != :running
62
108
 
@@ -67,7 +113,7 @@ class Procrastinate::Scheduler
67
113
  task = Procrastinate::Task::Callable.new(block)
68
114
  end
69
115
 
70
- task_queue << task
116
+ task_producer << task
71
117
 
72
118
  # Create an occasion for spawning
73
119
  manager.wakeup
@@ -87,7 +133,7 @@ class Procrastinate::Scheduler
87
133
  # Wait until all tasks are done.
88
134
  loop do
89
135
  manager.wakeup
90
- break if task_queue.empty? && manager.process_count==0
136
+ break if task_producer.empty? && manager.process_count==0
91
137
  sleep 0.01
92
138
  end
93
139
 
@@ -115,8 +161,8 @@ private
115
161
  # *control thread*
116
162
  #
117
163
  def spawn
118
- while strategy.should_spawn? && !task_queue.empty?
119
- task = task_queue.pop
164
+ while strategy.should_spawn? && !task_producer.empty?
165
+ task = task_producer.pop
120
166
  strategy.notify_spawn
121
167
  manager.create_process(task) do
122
168
  strategy.notify_dead
@@ -0,0 +1,78 @@
1
+ module Procrastinate
2
+ class Server
3
+ def initialize
4
+ @manager = Procrastinate::ProcessManager.new
5
+ @state = :new
6
+ end
7
+
8
+ def start(n, activity_check_interval=nil, &block)
9
+ fail "Already running server." unless @state == :new
10
+
11
+ @block = block
12
+ @strategy = Procrastinate::SpawnStrategy::Throttled.new(n)
13
+ @state = :running
14
+ @check_interval = activity_check_interval
15
+
16
+ start_thread
17
+ end
18
+
19
+ def shutdown
20
+ fail "For shutdown, server must be running." unless @state == :running
21
+
22
+ @state = :shutdown
23
+ @manager.wakeup
24
+
25
+ @thread.join if @thread
26
+
27
+ @thread = nil
28
+ @state = :new
29
+ end
30
+
31
+ private
32
+ def start_thread
33
+ @thread = Thread.start(&method(:control_thread_main))
34
+ end
35
+
36
+ # @note This method runs in the control thread only.
37
+ #
38
+ def spawn_new_workers
39
+ while @strategy.should_spawn?
40
+ task = Procrastinate::Task::Callable.new(@block)
41
+
42
+ @strategy.notify_spawn
43
+ @manager.create_process(task) do
44
+ @strategy.notify_dead
45
+ end
46
+ end
47
+ end
48
+
49
+ # @note This method runs in the control thread only.
50
+ #
51
+ def control_thread_main
52
+ # Start managers work
53
+ @manager.setup
54
+
55
+ # Loop until someone requests a shutdown.
56
+ loop do
57
+ spawn_new_workers
58
+
59
+ @manager.step
60
+
61
+ break if @state == :shutdown
62
+ end
63
+
64
+ @manager.kill_processes
65
+ @manager.teardown
66
+ rescue => ex
67
+ # Sometimes exceptions vanish silently. This will avoid that, even though
68
+ # they should abort the whole process.
69
+
70
+ warn "Exception #{ex.inspect} caught."
71
+ ex.backtrace.first(5).each do |line|
72
+ warn line
73
+ end
74
+
75
+ raise
76
+ end
77
+ end
78
+ end
@@ -2,7 +2,7 @@
2
2
  require 'procrastinate/task/result'
3
3
 
4
4
  module Procrastinate::Task
5
- # Calls the block and returns the result of execution.
5
+ # A task that calls the block and returns the result of execution.
6
6
  #
7
7
  class Callable
8
8
  attr_reader :block
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procrastinate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,85 +10,136 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-01-23 00:00:00.000000000 Z
13
+ date: 2012-12-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: state_machine
17
- requirement: &70180063938160 !ruby/object:Gem::Requirement
18
- none: false
17
+ prerelease: false
18
+ requirement: !ruby/object:Gem::Requirement
19
19
  requirements:
20
20
  - - ~>
21
21
  - !ruby/object:Gem::Version
22
- version: 0.9.4
22
+ version: '1.1'
23
+ none: false
23
24
  type: :runtime
24
- prerelease: false
25
- version_requirements: *70180063938160
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ none: false
26
31
  - !ruby/object:Gem::Dependency
27
32
  name: cod
28
- requirement: &70180063966580 !ruby/object:Gem::Requirement
29
- none: false
33
+ prerelease: false
34
+ requirement: !ruby/object:Gem::Requirement
30
35
  requirements:
31
36
  - - ~>
32
37
  - !ruby/object:Gem::Version
33
- version: '0.4'
38
+ version: '0.5'
39
+ none: false
34
40
  type: :runtime
35
- prerelease: false
36
- version_requirements: *70180063966580
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.5'
46
+ none: false
37
47
  - !ruby/object:Gem::Dependency
38
48
  name: rake
39
- requirement: &70180063963140 !ruby/object:Gem::Requirement
40
- none: false
49
+ prerelease: false
50
+ requirement: !ruby/object:Gem::Requirement
41
51
  requirements:
42
52
  - - ! '>='
43
53
  - !ruby/object:Gem::Version
44
54
  version: '0'
55
+ none: false
45
56
  type: :development
46
- prerelease: false
47
- version_requirements: *70180063963140
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ none: false
48
63
  - !ruby/object:Gem::Dependency
49
64
  name: rspec
50
- requirement: &70180063959700 !ruby/object:Gem::Requirement
51
- none: false
65
+ prerelease: false
66
+ requirement: !ruby/object:Gem::Requirement
52
67
  requirements:
53
68
  - - ! '>='
54
69
  - !ruby/object:Gem::Version
55
70
  version: '0'
71
+ none: false
56
72
  type: :development
57
- prerelease: false
58
- version_requirements: *70180063959700
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ none: false
59
79
  - !ruby/object:Gem::Dependency
60
80
  name: flexmock
61
- requirement: &70180063973380 !ruby/object:Gem::Requirement
62
- none: false
81
+ prerelease: false
82
+ requirement: !ruby/object:Gem::Requirement
63
83
  requirements:
64
84
  - - ! '>='
65
85
  - !ruby/object:Gem::Version
66
86
  version: '0'
87
+ none: false
67
88
  type: :development
68
- prerelease: false
69
- version_requirements: *70180063973380
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ none: false
70
95
  - !ruby/object:Gem::Dependency
71
96
  name: guard
72
- requirement: &70180063972060 !ruby/object:Gem::Requirement
73
- none: false
97
+ prerelease: false
98
+ requirement: !ruby/object:Gem::Requirement
74
99
  requirements:
75
100
  - - ! '>='
76
101
  - !ruby/object:Gem::Version
77
102
  version: '0'
103
+ none: false
78
104
  type: :development
79
- prerelease: false
80
- version_requirements: *70180063972060
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ none: false
81
111
  - !ruby/object:Gem::Dependency
82
112
  name: growl
83
- requirement: &70180063970680 !ruby/object:Gem::Requirement
84
- none: false
113
+ prerelease: false
114
+ requirement: !ruby/object:Gem::Requirement
85
115
  requirements:
86
116
  - - ! '>='
87
117
  - !ruby/object:Gem::Version
88
118
  version: '0'
119
+ none: false
89
120
  type: :development
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ none: false
127
+ - !ruby/object:Gem::Dependency
128
+ name: yard
90
129
  prerelease: false
91
- version_requirements: *70180063970680
130
+ requirement: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ none: false
136
+ type: :development
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ none: false
92
143
  description:
93
144
  email:
94
145
  - kaspar.schiess@absurd.li
@@ -109,6 +160,7 @@ files:
109
160
  - lib/procrastinate/proxy.rb
110
161
  - lib/procrastinate/runtime.rb
111
162
  - lib/procrastinate/scheduler.rb
163
+ - lib/procrastinate/server.rb
112
164
  - lib/procrastinate/spawn_strategy/default.rb
113
165
  - lib/procrastinate/spawn_strategy/simple.rb
114
166
  - lib/procrastinate/spawn_strategy/throttled.rb
@@ -122,6 +174,7 @@ files:
122
174
  - lib/procrastinate.rb
123
175
  - examples/locking.rb
124
176
  - examples/pascal.rb
177
+ - examples/server.rb
125
178
  - examples/simple.rb
126
179
  - examples/throttled.rb
127
180
  homepage: http://github.com/kschiess/procrastinate
@@ -133,27 +186,22 @@ rdoc_options:
133
186
  require_paths:
134
187
  - lib
135
188
  required_ruby_version: !ruby/object:Gem::Requirement
136
- none: false
137
189
  requirements:
138
190
  - - ! '>='
139
191
  - !ruby/object:Gem::Version
140
192
  version: '0'
141
- segments:
142
- - 0
143
- hash: 3407763989240963082
144
- required_rubygems_version: !ruby/object:Gem::Requirement
145
193
  none: false
194
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
195
  requirements:
147
196
  - - ! '>='
148
197
  - !ruby/object:Gem::Version
149
198
  version: '0'
150
- segments:
151
- - 0
152
- hash: 3407763989240963082
199
+ none: false
153
200
  requirements: []
154
201
  rubyforge_project:
155
- rubygems_version: 1.8.10
202
+ rubygems_version: 1.8.24
156
203
  signing_key:
157
204
  specification_version: 3
158
205
  summary: Framework to run tasks in separate processes.
159
206
  test_files: []
207
+ has_rdoc: