diff --git a/README b/README
index 2162c8e8c..191d84354 100644
--- a/README
+++ b/README
@@ -34,10 +34,10 @@ dependency tree has not been properly defined. Consider
With single-threaded Rake, _x_,_y_,_z_ will be invoked in that
order before _a_ is invoked (assuming there are no other rules
-involving these tasks). However with drake --threads=N
-(for N > 1), one should not expect any particular order of execution.
-Since there is no dependency specified between _x_,_y_,_z_ above,
-Drake is free to run them in any order.
+involving these tasks). However with drake -jN
(for +N+
+> 1), one should not expect any particular order of execution. Since
+there is no dependency specified between _x_,_y_,_z_ above, Drake is
+free to run them in any order.
If you wish _x_,_y_,_z_ to be invoked sequentially, then write
@@ -59,11 +59,13 @@ Package maintainers affectionately call it "not j-safe."
=== MultiTask
-The use of +multitask+ is deprecated. Tasks which may properly be run
-in parallel will be run in parallel; those which cannot, will not. It
-is not the user's job to decide.
+When more than one thread is given, +multitask+ behaves just like
++task+. Those tasks which may properly be run in parallel will be run
+in parallel; those which cannot, will not. It is not the user's job
+to decide. In other words, for -jN (+N+ > 1), +multitask+ is
+an alias of +task+.
-Drake's +multitask+ is an alias of +task+.
+For -j1 (default), +multitask+ behaves as the original.
=== Task#invoke inside Task#invoke
@@ -71,7 +73,7 @@ Parallelizing code means surrendering control over the
micro-management of its execution. Manually invoking tasks inside
other tasks is rather contrary to this notion, throwing a monkey
wrench into the system. An exception will be raised when this is
-attempted in non-single-threaded mode.
+attempted in multi-threaded mode.
== Links
diff --git a/lib/rake.rb b/lib/rake.rb
index 156cd5d65..13b9f7390 100755
--- a/lib/rake.rb
+++ b/lib/rake.rb
@@ -563,23 +563,23 @@ def clear_actions
self
end
+ def base_invoke(*args) #:nodoc:
+ invoke_with_call_chain(
+ TaskArguments.new(arg_names, args),
+ InvocationChain::EMPTY)
+ end
+
# Invoke the task if it is needed. Prerequites are invoked first.
def invoke(*args)
- run_invoke = lambda {
- invoke_with_call_chain(
- TaskArguments.new(arg_names, args),
- InvocationChain::EMPTY)
- }
-
if application.num_threads == 1
- run_invoke.call
+ base_invoke(*args)
else
if application.parallel_lock.locked?
raise "Calling Task#invoke within a task is not allowed."
end
application.parallel_lock.synchronize {
application.parallel_tasks.clear
- run_invoke.call
+ base_invoke(*args)
application.invoke_parallel_tasks
}
end
@@ -595,8 +595,13 @@ def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
end
return if @already_invoked
@already_invoked = true
- prereqs = application.num_threads == 1 ? nil : Array.new
- invoke_prerequisites(task_args, new_chain, prereqs)
+ prereqs =
+ if application.num_threads == 1
+ invoke_prerequisites(task_args, new_chain)
+ nil
+ else
+ invoke_prerequisites_parallel(task_args, new_chain)
+ end
if needed?
if application.num_threads == 1
execute(task_args)
@@ -609,16 +614,28 @@ def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
end
protected :invoke_with_call_chain
+ def invoke_prerequisite(prereq_name, task_args, invocation_chain) #:nodoc:
+ prereq = application[prereq_name, @scope]
+ prereq_args = task_args.new_scope(prereq.arg_names)
+ prereq.invoke_with_call_chain(prereq_args, invocation_chain)
+ prereq
+ end
+
# Invoke all the prerequisites of a task.
- def invoke_prerequisites(task_args, invocation_chain, accum=nil) #:nodoc:
+ def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
@prerequisites.each { |n|
- prereq = application[n, @scope]
- prereq_args = task_args.new_scope(prereq.arg_names)
- prereq.invoke_with_call_chain(prereq_args, invocation_chain)
- accum << prereq if accum
+ invoke_prerequisite(n, task_args, invocation_chain)
}
end
+ # Parallel dry-run accumulator.
+ # This also serves to circumvent MultiTask#invoke_prerequisites.
+ def invoke_prerequisites_parallel(task_args, invocation_chain) #:nodoc:
+ @prerequisites.map { |n|
+ invoke_prerequisite(n, task_args, invocation_chain)
+ }
+ end
+
# Format the trace flags for display.
def format_trace_flags
flags = []
@@ -767,10 +784,6 @@ def scope_name(scope, task_name)
end # class << Rake::Task
end # class Rake::Task
- #
- # DEPRECATED: do not use MultiTask
- #
- MultiTask = Task
# #########################################################################
# A FileTask is a task that includes time based dependencies. If any of a
@@ -834,6 +847,19 @@ def timestamp
Rake::EARLY
end
end
+
+ # #########################################################################
+ # Same as a regular task, but the immediate prerequisites are done in
+ # parallel using Ruby threads.
+ #
+ class MultiTask < Task
+ def invoke_prerequisites(args, invocation_chain)
+ threads = @prerequisites.collect { |p|
+ Thread.new(p) { |r| application[r].invoke_with_call_chain(args, invocation_chain) }
+ }
+ threads.each { |t| t.join }
+ end
+ end
end # module Rake
# ###########################################################################
@@ -850,12 +876,6 @@ def task(*args, &block)
Rake::Task.define_task(*args, &block)
end
-#
-# DEPRECATED: Do not use 'multitask'
-#
-def multitask(*args, &block)
- task(*args, &block)
-end
# Declare a file task.
#
@@ -893,6 +913,17 @@ def directory(dir)
end
end
+# Declare a task that performs its prerequisites in parallel. Multitasks does
+# *not* guarantee that its prerequisites will execute in any given order
+# (which is obvious when you think about it)
+#
+# Example:
+# multitask :deploy => [:deploy_gem, :deploy_rdoc]
+#
+def multitask(args, &block)
+ Rake::MultiTask.define_task(args, &block)
+end
+
# Create a new rake namespace and use it for evaluating the given block.
# Returns a NameSpace object that can be used to lookup tasks defined in the
# namespace.
@@ -2003,22 +2034,6 @@ def initialize
@tty_output = STDOUT.tty?
end
- #
- # Check for circular dependencies, without invoking.
- #
- def check_circular(task_name)
- helper = lambda { |name, chain|
- if chain.include? name
- raise "Circular dependency detected: " +
- "#{name} => #{chain.last} => #{name}"
- end
- Rake::Task[name].prerequisites.each { |prereq_name|
- helper.call(prereq_name, chain + [name])
- }
- }
- helper.call(task_name, [])
- end
-
# Run the Rake application. The run method performs the following three steps:
#
# * Initialize the command line options (+init+).
diff --git a/test/test_multitask.rb b/test/test_multitask.rb
new file mode 100644
index 000000000..ee9be773c
--- /dev/null
+++ b/test/test_multitask.rb
@@ -0,0 +1,45 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'rake'
+
+######################################################################
+class TestMultiTask < Test::Unit::TestCase
+ include Rake
+
+ def setup
+ Task.clear
+ @runs = Array.new
+ end
+
+ def test_running_multitasks
+ task :a do 3.times do |i| @runs << "A#{i}"; sleep 0.01; end end
+ task :b do 3.times do |i| @runs << "B#{i}"; sleep 0.01; end end
+ multitask :both => [:a, :b]
+ Task[:both].invoke
+ assert_equal 6, @runs.size
+ assert @runs.index("A0") < @runs.index("A1")
+ assert @runs.index("A1") < @runs.index("A2")
+ assert @runs.index("B0") < @runs.index("B1")
+ assert @runs.index("B1") < @runs.index("B2")
+ end
+
+ def test_all_multitasks_wait_on_slow_prerequisites
+ task :slow do 3.times do |i| @runs << "S#{i}"; sleep 0.05 end end
+ task :a => [:slow] do 3.times do |i| @runs << "A#{i}"; sleep 0.01 end end
+ task :b => [:slow] do 3.times do |i| @runs << "B#{i}"; sleep 0.01 end end
+ multitask :both => [:a, :b]
+ Task[:both].invoke
+ assert_equal 9, @runs.size
+ assert @runs.index("S0") < @runs.index("S1")
+ assert @runs.index("S1") < @runs.index("S2")
+ assert @runs.index("S2") < @runs.index("A0")
+ assert @runs.index("S2") < @runs.index("B0")
+ assert @runs.index("A0") < @runs.index("A1")
+ assert @runs.index("A1") < @runs.index("A2")
+ assert @runs.index("B0") < @runs.index("B1")
+ assert @runs.index("B1") < @runs.index("B2")
+ end
+end
+
+