procrastinate 0.3.1 → 0.4.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 ADDED
@@ -0,0 +1,37 @@
1
+ = 0.4.0 / ???
2
+
3
+ + Schedules blocks as well (Scheduler#schedule).
4
+
5
+ ! Fixes a small timing bug that would provoke a ChildDeath where the child
6
+   exited correctly in reality.
7
+
8
+ ! Fixes a data race on the result variable. This would have your process
9
+ hang forever waiting for a result that was already available.
10
+
11
+ = 0.3.1 / 13Sep2011
12
+
13
+ ! fix processor detection for Mac OS X Lion
14
+
15
+ = 0.3.0 / 27Dez2010
16
+ * create_proxy is now just 'proxy' to friends. This looks cleaner.
17
+
18
+ * require 'procrastinate/implicit' allows ignoring the scheduler in daily
19
+ usage. This is probably what will be more common.
20
+
21
+ * Auto-detection of the number of cores procrastinate runs on.
22
+ (SpawnStrategy::Default)
23
+
24
+ * Ruby 1.8 now uses absolutely poor busy-loop synchs. This is still better
25
+ than inserting puts in the code to unblock some magical mystical internal
26
+ state. I'll try to find a better solution.
27
+
28
+ = 0.2.0 / 22Dez2010
29
+ * Big rewrite, trying to make things more clear. Also: There might be more
30
+ features lurking in there.
31
+
32
+ * All proxy method calls now have a return value that works like a future:
33
+ Access to the real return value through future.value, which might block.
34
+
35
+ = 0.1.0 / 10Dez2010
36
+
37
+ * Initial version
data/README CHANGED
@@ -60,6 +60,10 @@ This library runs with at least Ruby 1.8.7 and Ruby 1.9.
60
60
  Ruby 1.9-p136 users must use this patch:
61
61
  https://gist.github.com/762807
62
62
 
63
+ As a general remark: Interaction with Ruby versions is significant. Please
64
+ use the latest version available to you, since fork & threading bugs are
65
+ likely to be fixed there.
66
+
63
67
  KNOWN BUGS
64
68
 
65
69
  Due to the way we handle signal traps, you cannot start more than one
data/Rakefile CHANGED
@@ -1,70 +1,39 @@
1
- require 'rspec'
2
- require 'rspec/core/rake_task'
3
- Rspec::Core::RakeTask.new
4
- task :default => :spec
5
-
6
1
  require "rubygems"
7
- require "rake/gempackagetask"
8
- require "rake/rdoctask"
9
-
10
- # This builds the actual gem. For details of what all these options
11
- # mean, and other ones you can add, check the documentation here:
12
- #
13
- # http://rubygems.org/read/chapter/20
14
- #
15
- spec = Gem::Specification.new do |s|
16
-
17
- # Change these as appropriate
18
- s.name = "procrastinate"
19
- s.version = "0.3.1"
20
- s.summary = "Framework to run tasks in separate processes."
21
- s.authors = ['Kaspar Schiess', 'Patrick Marchi']
22
- s.email = ['kaspar.schiess@absurd.li', 'mail@patrickmarchi.ch']
23
- s.homepage = "http://github.com/kschiess/procrastinate"
24
-
25
- s.has_rdoc = true
26
- s.extra_rdoc_files = %w(README)
27
- s.rdoc_options = %w(--main README)
2
+ require "rdoc/task"
3
+ require 'rspec/core/rake_task'
4
+ require 'rubygems/package_task'
28
5
 
29
- # Add any extra files to include in the gem
30
- s.files = %w(LICENSE Rakefile README) + Dir.glob("{spec,lib/**/*}")
31
- s.require_paths = ["lib"]
6
+ desc "Run all tests: Exhaustive."
7
+ RSpec::Core::RakeTask.new
32
8
 
33
- # If you want to depend on other gems, add them here, along with any
34
- # relevant versions
35
- s.add_dependency("state_machine", "~> 0.9.4")
9
+ task :default => :spec
36
10
 
37
- # If your tests use any gems, include them here
38
- s.add_development_dependency("rspec")
39
- s.add_development_dependency("flexmock")
11
+ task :stats do
12
+ %w(lib spec).each do |path|
13
+ printf "%10s:", path
14
+ system %Q(find #{path} -name "*.rb" | xargs wc -l | grep total)
15
+ end
40
16
  end
41
17
 
42
- desc "Regenerate the .gemspec file for github/bundler."
43
- task :gemspec do
44
- # Generate the gemspec file for github.
45
- file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
46
- File.open(file, "w") {|f| f << spec.to_ruby }
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"
47
27
  end
48
28
 
49
- # This task actually builds the gem. We also regenerate a static
50
- # .gemspec file, which is useful if something (i.e. GitHub) will
51
- # be automatically building a gem for this project. If you're not
52
- # using GitHub, edit as appropriate.
53
- #
54
- # To publish your gem online, install the 'gemcutter' gem; Read more
55
- # about that here: http://gemcutter.org/pages/gem_docs
56
- Rake::GemPackageTask.new(spec) do |pkg|
57
- pkg.gem_spec = spec
58
- end
29
+ desc 'Clear out RDoc'
30
+ task :clean => [:clobber_rdoc, :clobber_package]
59
31
 
60
- # Generate documentation
61
- Rake::RDocTask.new do |rd|
62
- rd.main = "README"
63
- rd.rdoc_files.include("README", "lib/**/*.rb")
64
- rd.rdoc_dir = "rdoc"
65
- end
32
+ # This task actually builds the gem.
33
+ task :gem => :spec
34
+ spec = eval(File.read('procrastinate.gemspec'))
66
35
 
67
- desc 'Clear out RDoc and generated packages'
68
- task :clean => [:clobber_rdoc, :clobber_package] do
69
- rm "#{spec.name}.gemspec"
36
+ desc "Generate the gem package."
37
+ Gem::PackageTask.new(spec) do |pkg|
38
+ # pkg.need_tar = true
70
39
  end
@@ -0,0 +1,26 @@
1
+
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'procrastinate'
5
+
6
+ class Worker < Struct.new(:runtime)
7
+ def do_work
8
+ puts "#{Process.pid}: starting..."
9
+ runtime.lock('lock') do
10
+ puts "#{Process.pid}: holds lock"
11
+ sleep 0.1
12
+ puts "#{Process.pid}: releases"
13
+ end
14
+ puts "#{Process.pid}: done."
15
+ end
16
+ end
17
+
18
+ Procrastinate::Lock.base = '/tmp' # This will have to be moved into scheduler
19
+ scheduler = Procrastinate::Scheduler.start
20
+ worker = scheduler.proxy(Worker.new(scheduler.runtime))
21
+
22
+ 10.times do
23
+ worker.do_work
24
+ end
25
+
26
+ scheduler.shutdown
@@ -0,0 +1,30 @@
1
+
2
+ # Computes Pascals triangle using procrastinate. This is mainly a stress test
3
+ # for result handling code and is NOT how I would parallelize this task!
4
+
5
+ $:.unshift File.dirname(__FILE__) + '/../lib'
6
+
7
+ require 'procrastinate'
8
+ require 'procrastinate/implicit'
9
+ include Procrastinate
10
+
11
+ V = Struct.new(:value) do
12
+ def ready?; true; end
13
+ end
14
+
15
+ current = [
16
+ V.new(1),
17
+ V.new(1)
18
+ ]
19
+
20
+ loop do
21
+ last = current
22
+
23
+ puts last.map { |e| sprintf("%3d", e.value) }.join(' ')
24
+
25
+ current = [V.new(1)] +
26
+ last.each_cons(2).map { |l,r|
27
+ Procrastinate.schedule { l.value + r.value }
28
+ } +
29
+ [V.new(1)]
30
+ end
@@ -0,0 +1,20 @@
1
+
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'procrastinate/implicit'
5
+
6
+ class Worker
7
+ def do_work
8
+ puts "> Starting work in process #{Process.pid}"
9
+ sleep 2
10
+ puts "< Work completed in process #{Process.pid}"
11
+ end
12
+ end
13
+
14
+ worker = Procrastinate.proxy(Worker.new)
15
+
16
+ 10.times do
17
+ worker.do_work
18
+ end
19
+
20
+ Procrastinate.join
@@ -0,0 +1,22 @@
1
+
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'procrastinate'
5
+ include Procrastinate
6
+
7
+ class Worker
8
+ def do_work
9
+ puts "> Starting work in process #{Process.pid}"
10
+ sleep 2
11
+ puts "< Work completed in process #{Process.pid}"
12
+ end
13
+ end
14
+
15
+ scheduler = Scheduler.start(SpawnStrategy::Throttled.new(5))
16
+ worker = scheduler.proxy(Worker.new)
17
+
18
+ 10.times do
19
+ worker.do_work
20
+ end
21
+
22
+ scheduler.shutdown
@@ -29,6 +29,17 @@ module Procrastinate
29
29
  end
30
30
  module_function :proxy
31
31
 
32
+ # call-seq:
33
+ # Procrastinate.schedule { some_work }
34
+ #
35
+ # Schedules a block to be executed in its own thread. Returns the future that
36
+ # will return the blocks return value.
37
+ #
38
+ def schedule(&block)
39
+ scheduler.schedule(&block)
40
+ end
41
+ module_function :schedule
42
+
32
43
  # call-seq:
33
44
  # Procrastinate.join
34
45
  #
@@ -89,9 +89,12 @@ class Procrastinate::ProcessManager
89
89
  trap('CHLD', 'DEFAULT')
90
90
  end
91
91
 
92
- # Called from the child management thread, will put that thread to sleep
92
+ # Called from the child management thread, will put that thread to sleep
93
93
  # until someone requests it to become active again. See #wakeup.
94
94
  #
95
+ # This method also depletes the child queue, reading end of processing
96
+ # messages from all childs and dispatching them to the children.
97
+ #
95
98
  def wait_for_event
96
99
  cp_read_end = control_pipe.first
97
100
 
@@ -100,11 +103,10 @@ class Procrastinate::ProcessManager
100
103
 
101
104
  read_child_messages if ready.include? @cmc_server
102
105
 
103
- # Kill children here, since we've just depleted the communication
104
- # endpoint. This avoids the situation where the child process
105
- # communicates but we remove it from our records before it can be told
106
- # about it.
107
- kill_children
106
+ # Send the tracking code for the child processes the final notifications
107
+ # and remove them from the children hash. At this point we know that
108
+ # no messages are waiting in the child queue.
109
+ finalize_children
108
110
 
109
111
  if ready.include? cp_read_end
110
112
  # Consume the data (not important)
@@ -112,18 +114,19 @@ class Procrastinate::ProcessManager
112
114
  return
113
115
  end
114
116
  end
115
-
116
- # rescue Errno::EAGAIN, Errno::EINTR
117
- # TODO Is this needed?
118
- # A signal has been received. Mostly, this is as if we had received
119
- # something in the control pipe.
120
117
  end
121
118
 
122
- def kill_children
123
- children.delete_if { |pid, child| child.dead? }
119
+ def finalize_children
120
+ children.
121
+ select { |pid, child| child.stopped? }.
122
+ each { |pid, child| child.finalize }
123
+
124
+ children.delete_if { |pid, child|
125
+ child.removable? }
124
126
  end
125
127
 
126
- # Once the @cmc_server endpoint is ready, loops and reads all child communication.
128
+ # Once the @cmc_server endpoint is ready, loops and reads all child
129
+ # communication.
127
130
  #
128
131
  def read_child_messages
129
132
  loop do
@@ -138,15 +141,18 @@ class Procrastinate::ProcessManager
138
141
  # that still needs decoding.
139
142
  #
140
143
  def decode_and_handle_message(msg)
141
- pid, obj = Marshal.load(msg)
144
+ pid, obj = begin
145
+ Marshal.load(msg)
146
+ rescue => b
147
+ # Messages that cannot be unmarshalled will be ignored.
148
+ warn "Can't unmarshal child communication: #{b}"
149
+ end
150
+
142
151
  if child=children[pid]
143
152
  child.incoming_message(obj)
144
153
  else
145
154
  warn "Communication from child #{pid} received, but child is gone."
146
155
  end
147
- rescue => b
148
- # Messages that cannot be unmarshalled will be ignored.
149
- warn "Can't unmarshal child communication."
150
156
  end
151
157
 
152
158
  # Calls completion handlers for all the childs that have now exited.
@@ -158,7 +164,11 @@ class Procrastinate::ProcessManager
158
164
 
159
165
  # Trigger the completion callback
160
166
  if child=children[child_pid]
161
- child.died
167
+ child.sigchld_received
168
+ # Maybe there are messages queued for this child. If nothing is queued
169
+ # up, the thread will hang in the select in #wait_for_event unless
170
+ # we wake it up.
171
+ wakeup
162
172
  end
163
173
  end
164
174
  rescue Errno::ECHILD
@@ -190,11 +200,16 @@ class Procrastinate::ProcessManager
190
200
 
191
201
  exit! # this seems to be needed to avoid rspecs cleanup tasks
192
202
  end
203
+
204
+ # This should never fire: New children are spawned only after we loose
205
+ # track of the old ones because they have been successfully processed.
206
+ fail "PID REUSE!" if children.has_key?(pid)
193
207
 
194
208
  # The spawning is done in the same thread as the reaping is done. This is
195
209
  # why no race condition to the following line exists. (or in other code,
196
210
  # for that matter.)
197
- children[pid] = ChildProcess.new(completion_handler, result).tap { |s| s.start }
211
+ children[pid] = ChildProcess.new(completion_handler, result).
212
+ tap { |s| s.start }
198
213
  end
199
214
 
200
215
  # Gets executed in child process to clean up file handles and pipes that the
@@ -2,21 +2,38 @@
2
2
  # a child exits and the object that will handle child-master communication
3
3
  # if desired.
4
4
  #
5
- class Procrastinate::ProcessManager::ChildProcess < Struct.new(:handler, :result, :state)
5
+ Procrastinate::ProcessManager::ChildProcess =
6
+ Struct.new(:handler, :result, :state) do
7
+
8
+ def initialize(handler, result)
9
+ super(handler, result, "new")
10
+ end
11
+
6
12
  state_machine :state, :initial => :new do
7
- event(:start) { transition :new => :running }
8
- event(:died) { transition :running => :dead }
13
+ event(:start) { transition :new => :running }
14
+ event(:sigchld_received) { transition :running => :stopped }
15
+ event(:finalize) { transition :stopped => :removable }
9
16
 
10
- after_transition :on => :died, :do => :call_completion_handlers
17
+ after_transition :on => :sigchld_received,
18
+ :do => :call_completion_handlers
19
+ after_transition :on => :finalize,
20
+ :do => :notify_result
11
21
  end
12
22
 
13
- # Calls the completion handler for the child. This is triggered by the
14
- # transition into the 'dead' state.
23
+ # Calls the completion handler for the child. At this stage, the process is
24
+ # not around anymore, but we still need to do some tracking.
15
25
  #
16
26
  def call_completion_handlers
17
- result.process_died if result
18
27
  handler.call if handler
19
28
  end
29
+
30
+ # Notifies the childs result value that the child has died and that no
31
+ # more messages will be read. If this is the only notification, the result
32
+ # will yield a ChildDeath exception.
33
+ #
34
+ def notify_result
35
+ result.process_died if result
36
+ end
20
37
 
21
38
  # Handles incoming messages from the tasks process.
22
39
  #
@@ -17,7 +17,8 @@ class Procrastinate::Proxy
17
17
 
18
18
  def method_missing(name, *args, &block)
19
19
  if respond_to? name
20
- task = Procrastinate::Task::MethodCall.new(@worker, name, args, block)
20
+ task = Procrastinate::Task::Callable.new(
21
+ lambda { @worker.send(name, *args, &block) })
21
22
  @scheduler.schedule(task)
22
23
 
23
24
  return task.result
@@ -57,12 +57,22 @@ class Procrastinate::Scheduler
57
57
  # Called by the proxy to schedule work. You can implement your own Task
58
58
  # classes; the relevant interface consists of only a #run method.
59
59
  #
60
- def schedule(task)
60
+ def schedule(task=nil, &block)
61
61
  fail "Shutting down..." if @state != :running
62
+
63
+ fail ArgumentError, "Either task or block must be given." \
64
+ if !task && !block
65
+
66
+ if block
67
+ task = Procrastinate::Task::Callable.new(block)
68
+ end
69
+
62
70
  task_queue << task
63
71
 
64
72
  # Create an occasion for spawning
65
73
  manager.wakeup
74
+
75
+ task.result
66
76
  end
67
77
 
68
78
  # Waits for the currently queued work to complete. This can be used at the
@@ -90,14 +100,14 @@ class Procrastinate::Scheduler
90
100
  #
91
101
  def shutdown(hard=false)
92
102
  join unless hard
93
-
103
+
94
104
  # Set the flag that will provoke shutdown
95
105
  @state = :real_shutdown
96
106
  # Wake the manager up, making it check the flag
97
107
  manager.wakeup
98
108
  # Wait for the manager to finish its work. This waits for child processes
99
109
  # and then reaps their result, avoiding zombies.
100
- @thread.join
110
+ @thread.join if @thread
101
111
  end
102
112
 
103
113
  private
@@ -2,5 +2,5 @@
2
2
  # A collection of tasks that can be performed with procrastinate.
3
3
  #
4
4
  module Procrastinate::Task
5
- autoload :MethodCall, 'procrastinate/task/method_call'
5
+ autoload :Callable, 'procrastinate/task/callable'
6
6
  end
@@ -0,0 +1,27 @@
1
+
2
+ require 'procrastinate/task/result'
3
+
4
+ module Procrastinate::Task
5
+ # Calls the block and returns the result of execution.
6
+ #
7
+ class Callable
8
+ attr_reader :block
9
+ attr_reader :result
10
+
11
+ def initialize(block)
12
+ @b = block
13
+ @result = Result.new
14
+ end
15
+
16
+ # Runs this task. Gets passed an endpoint that can be used to communicate
17
+ # values back to the master. Every time you write a value to that endpoint
18
+ # (using #send), the server will call #incoming_message on the task object
19
+ # in the master process. This allows return values and other communication
20
+ # from children to the master (and to the caller in this case).
21
+ #
22
+ def run(endpoint)
23
+ r = @b.call
24
+ endpoint.send r if endpoint
25
+ end
26
+ end
27
+ end
@@ -15,7 +15,8 @@ class Procrastinate::Task::Result
15
15
  # *control thread*
16
16
  #
17
17
  def incoming_message(obj)
18
- return if ready?
18
+ fail "Read child answer too late, already marked dead." if ready? && @exception
19
+ fail "Double child answer." if ready?
19
20
 
20
21
  @value = obj
21
22
  @value_ready.set
@@ -1,3 +1,17 @@
1
+
2
+ # A flag that will allow threads to wait until it is set. Once it is set, it
3
+ # cannot be unset.
4
+ #
5
+ # Guarantees that this class tries to make:
6
+ #
7
+ # 1) No thread will start waiting for the flag once it is already set. There
8
+ # no set-hole, meaning that no thread goes to sleep while we're waking up
9
+ # threads because the flag has been set.
10
+ #
11
+ # Candidate stdlib classes violate some of these guarantees, here are some
12
+ # candidates:
13
+ # * ConditionVariable - violates 1)
14
+ #
1
15
  class Procrastinate::Utils::OneTimeFlag
2
16
  def initialize
3
17
  @waiting = []
@@ -12,7 +26,9 @@ class Procrastinate::Utils::OneTimeFlag
12
26
 
13
27
  @waiting_m.synchronize do
14
28
  @waiting << Thread.current
15
- @waiting_m.sleep(0.001) until set?
29
+ until set?
30
+ @waiting_m.sleep(0.001)
31
+ end
16
32
  end
17
33
  end
18
34
 
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.3.1
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-09-13 00:00:00.000000000Z
13
+ date: 2011-12-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: state_machine
17
- requirement: &70138440330900 !ruby/object:Gem::Requirement
17
+ requirement: &70314600072220 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.9.4
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70138440330900
25
+ version_requirements: *70314600072220
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rspec
28
- requirement: &70138440330260 !ruby/object:Gem::Requirement
28
+ requirement: &70314600070260 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70138440330260
36
+ version_requirements: *70314600070260
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: flexmock
39
- requirement: &70138440329160 !ruby/object:Gem::Requirement
39
+ requirement: &70314600069360 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: '0'
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70138440329160
47
+ version_requirements: *70314600069360
48
48
  description:
49
49
  email:
50
50
  - kaspar.schiess@absurd.li
@@ -54,6 +54,7 @@ extensions: []
54
54
  extra_rdoc_files:
55
55
  - README
56
56
  files:
57
+ - HISTORY.txt
57
58
  - LICENSE
58
59
  - Rakefile
59
60
  - README
@@ -71,13 +72,17 @@ files:
71
72
  - lib/procrastinate/spawn_strategy/simple.rb
72
73
  - lib/procrastinate/spawn_strategy/throttled.rb
73
74
  - lib/procrastinate/spawn_strategy.rb
74
- - lib/procrastinate/task/method_call.rb
75
+ - lib/procrastinate/task/callable.rb
75
76
  - lib/procrastinate/task/result.rb
76
77
  - lib/procrastinate/task.rb
77
78
  - lib/procrastinate/utils/one_time_flag.rb
78
79
  - lib/procrastinate/utils/one_time_flag_ruby18_shim.rb
79
80
  - lib/procrastinate/utils.rb
80
81
  - lib/procrastinate.rb
82
+ - examples/locking.rb
83
+ - examples/pascal.rb
84
+ - examples/simple.rb
85
+ - examples/throttled.rb
81
86
  homepage: http://github.com/kschiess/procrastinate
82
87
  licenses: []
83
88
  post_install_message:
@@ -92,6 +97,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
97
  - - ! '>='
93
98
  - !ruby/object:Gem::Version
94
99
  version: '0'
100
+ segments:
101
+ - 0
102
+ hash: 4532369798177350543
95
103
  required_rubygems_version: !ruby/object:Gem::Requirement
96
104
  none: false
97
105
  requirements:
@@ -1,35 +0,0 @@
1
-
2
- require 'procrastinate/task/result'
3
-
4
- # Constructs an object of type +klass+ and calls a method on it.
5
- #
6
- class Procrastinate::Task::MethodCall
7
- include Procrastinate::Task
8
-
9
- attr_reader :i
10
- attr_reader :m
11
- attr_reader :a
12
- attr_reader :b
13
-
14
- def initialize(instance, method, arguments, block)
15
- @i = instance
16
- @m = method
17
- @a = arguments
18
- @b = block
19
- end
20
-
21
- # Runs this task. Gets passed an endpoint that can be used to communicate
22
- # values back to the master. Every time you write a value to that endpoint
23
- # (using #send), the server will call #incoming_message on the task object
24
- # in the master process. This allows return values and other communication
25
- # from children to the master (and to the caller in this case).
26
- #
27
- def run(endpoint)
28
- r = @i.send(@m, *@a, &@b)
29
- endpoint.send r if endpoint
30
- end
31
-
32
- def result
33
- @result ||= Result.new
34
- end
35
- end