Skip to content

Commit

Permalink
bring back original 'multitask' for -j1
Browse files Browse the repository at this point in the history
  • Loading branch information
quix committed Sep 13, 2008
1 parent 8139e00 commit a5df0c1
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 50 deletions.
20 changes: 11 additions & 9 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ dependency tree has not been properly defined. Consider

With single-threaded Rake, _x_,_y_,_z_ will be invoked <em>in that
order</em> before _a_ is invoked (assuming there are no other rules
involving these tasks). However with <code>drake --threads=N</code>
(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 <code>drake -jN</code> (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

Expand All @@ -59,19 +59,21 @@ 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 <tt>-jN</tt> (+N+ > 1), +multitask+ is
an alias of +task+.

Drake's +multitask+ is an alias of +task+.
For <tt>-j1</tt> (default), +multitask+ behaves as the original.

=== Task#invoke inside Task#invoke

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

Expand Down
97 changes: 56 additions & 41 deletions lib/rake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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 = []
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

# ###########################################################################
Expand All @@ -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.
#
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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+).
Expand Down
45 changes: 45 additions & 0 deletions test/test_multitask.rb
Original file line number Diff line number Diff line change
@@ -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


0 comments on commit a5df0c1

Please sign in to comment.