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 + +