procrastinate 0.4.1 → 0.5.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.
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: