comp_tree 0.5.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +24 -0
- data/README +19 -52
- data/Rakefile +1 -138
- data/comp_tree.gemspec +33 -30
- data/install.rb +3 -3
- data/lib/comp_tree/algorithm.rb +117 -156
- data/lib/comp_tree/driver.rb +39 -154
- data/lib/comp_tree/error.rb +18 -23
- data/lib/comp_tree/node.rb +46 -50
- data/lib/comp_tree.rb +56 -0
- data/rakelib/jumpstart/ruby.rb +51 -0
- data/{contrib/quix/lib/quix → rakelib/jumpstart}/simple_installer.rb +11 -13
- data/test/common.rb +29 -0
- data/test/test_basic.rb +189 -0
- data/test/test_circular.rb +34 -31
- data/test/test_drain.rb +38 -0
- data/test/test_exception.rb +37 -86
- data/test/test_flood.rb +14 -0
- data/test/test_grind.rb +77 -0
- data/test/test_sequential.rb +21 -0
- metadata +45 -58
- data/contrib/quix/Rakefile +0 -16
- data/contrib/quix/install.rb +0 -3
- data/contrib/quix/lib/quix/builtin/dir/casefold_brackets.rb +0 -7
- data/contrib/quix/lib/quix/builtin/kernel/tap.rb +0 -9
- data/contrib/quix/lib/quix/builtin/module/include.rb +0 -21
- data/contrib/quix/lib/quix/builtin/module/private.rb +0 -41
- data/contrib/quix/lib/quix/config.rb +0 -37
- data/contrib/quix/lib/quix/cygwin.rb +0 -60
- data/contrib/quix/lib/quix/diagnostic.rb +0 -44
- data/contrib/quix/lib/quix/enumerable.rb +0 -33
- data/contrib/quix/lib/quix/fileutils.rb +0 -37
- data/contrib/quix/lib/quix/hash_struct.rb +0 -27
- data/contrib/quix/lib/quix/kernel.rb +0 -61
- data/contrib/quix/lib/quix/lazy_struct.rb +0 -55
- data/contrib/quix/lib/quix/string.rb +0 -38
- data/contrib/quix/lib/quix/subpackager.rb +0 -52
- data/contrib/quix/lib/quix/thread_local.rb +0 -32
- data/contrib/quix/lib/quix/vars.rb +0 -138
- data/contrib/quix/lib/quix.rb +0 -32
- data/contrib/quix/test/all.rb +0 -12
- data/contrib/quix/test/test_deps.rb +0 -25
- data/contrib/quix/test/test_include.rb +0 -47
- data/contrib/quix/test/test_private.rb +0 -86
- data/contrib/quix/test/test_root.rb +0 -19
- data/contrib/quix/test/test_struct.rb +0 -48
- data/contrib/quix/test/test_vars.rb +0 -187
- data/lib/comp_tree/bucket_ipc.rb +0 -151
- data/lib/comp_tree/diagnostic.rb +0 -44
- data/lib/comp_tree/misc.rb +0 -61
- data/lib/comp_tree/retriable_fork.rb +0 -42
- data/lib/comp_tree/tap.rb +0 -9
- data/lib/comp_tree/task_node.rb +0 -22
- data/test/all.rb +0 -12
- data/test/test_bucketipc.rb +0 -72
- data/test/test_comp_tree.rb +0 -364
data/CHANGES
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
= CompTree ChangeLog
|
3
|
+
|
4
|
+
== Version 0.7.0
|
5
|
+
|
6
|
+
* remove fork and discard_result options
|
7
|
+
* remove contrib/
|
8
|
+
* remove block arg to CompTree.new; use CompTree.build
|
9
|
+
* remove CompTree::Error nesting
|
10
|
+
* Driver#compute accepts integer as second option
|
11
|
+
* remove method_missing and eval tricks for node definitions;
|
12
|
+
new project 'pure' makes this obsolete
|
13
|
+
* split up tests
|
14
|
+
* allow non-symbols for node IDs
|
15
|
+
* check_circular now returns the loop instead of raising
|
16
|
+
* allow computation result of nil
|
17
|
+
|
18
|
+
== Version 0.5.2
|
19
|
+
|
20
|
+
* internal project cleanup (no relevant code changes).
|
21
|
+
|
22
|
+
== Version 0.5.0
|
23
|
+
|
24
|
+
* Initial release.
|
data/README
CHANGED
@@ -5,71 +5,38 @@
|
|
5
5
|
|
6
6
|
require 'comp_tree'
|
7
7
|
|
8
|
-
CompTree
|
8
|
+
CompTree.build do |driver|
|
9
9
|
|
10
|
-
# Define a function named 'area' taking these
|
11
|
-
driver.
|
12
|
-
width*height
|
10
|
+
# Define a function named 'area' taking these two arguments.
|
11
|
+
driver.define(:area, :width, :height) { |width, height|
|
12
|
+
width*height
|
13
13
|
}
|
14
14
|
|
15
15
|
# Define a function 'width' which takes a 'border' argument.
|
16
|
-
driver.
|
17
|
-
|
16
|
+
driver.define(:width, :border) { |border|
|
17
|
+
7 + border
|
18
18
|
}
|
19
19
|
|
20
20
|
# Ditto for 'height'.
|
21
|
-
driver.
|
22
|
-
|
21
|
+
driver.define(:height, :border) { |border|
|
22
|
+
5 + border
|
23
23
|
}
|
24
24
|
|
25
25
|
# Define a constant function 'border'.
|
26
|
-
driver.
|
27
|
-
|
28
|
-
}
|
29
|
-
|
30
|
-
# Ditto for 'offset'.
|
31
|
-
driver.define_offset {
|
32
|
-
7
|
26
|
+
driver.define(:border) {
|
27
|
+
2
|
33
28
|
}
|
34
29
|
|
35
30
|
# Compute the area using four parallel threads.
|
36
|
-
|
31
|
+
puts driver.compute(:area, :threads => 4)
|
32
|
+
# => 63
|
37
33
|
|
38
34
|
# We've done this computation.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
puts "Send bug report to ..."
|
43
|
-
end
|
44
|
-
}
|
35
|
+
puts((7 + 2)*(5 + 2))
|
36
|
+
# => 63
|
37
|
+
end
|
45
38
|
|
46
|
-
|
47
|
-
|
48
|
-
This form evals a lambda, saving you the repeat parameter list:
|
49
|
-
|
50
|
-
driver.define_area :width, :height, :offset, %{
|
51
|
-
width*height - offset
|
52
|
-
}
|
53
|
-
|
54
|
-
driver.define_width :border, %{
|
55
|
-
2 + border
|
56
|
-
}
|
57
|
-
|
58
|
-
Notice the '<code>%</code>' before the brace. The lambda is created
|
59
|
-
just once, during the time of definition.
|
60
|
-
|
61
|
-
Finally there is the raw form which uses no +eval+ or
|
62
|
-
+method_missing+ tricks:
|
63
|
-
|
64
|
-
driver.define(:area, :width, :height, :offset) { |width, height, offset|
|
65
|
-
width*height - offset
|
66
|
-
}
|
67
|
-
|
68
|
-
driver.define(:width, :border) { |border|
|
69
|
-
2 + border
|
70
|
-
}
|
71
|
-
|
72
|
-
== Important Notes
|
39
|
+
== Notes
|
73
40
|
|
74
41
|
The user should have a basic understanding of <em>functional
|
75
42
|
programming</em> (see for example
|
@@ -82,7 +49,7 @@ function you define must explicitly depend on the data it uses.
|
|
82
49
|
#
|
83
50
|
# BAD example: depending on state -- offset not listed as a parameter
|
84
51
|
#
|
85
|
-
driver.
|
52
|
+
driver.define(:area, :width, :height) { |width, height|
|
86
53
|
width*height - offset
|
87
54
|
}
|
88
55
|
|
@@ -96,9 +63,9 @@ to affect a state (to produce a <em>side effect</em>).
|
|
96
63
|
#
|
97
64
|
# BAD example: affecting state
|
98
65
|
#
|
99
|
-
driver.
|
66
|
+
driver.define(:area, :width, :height) { |width, height|
|
100
67
|
ACCUMULATOR.add "more data"
|
101
|
-
width*height
|
68
|
+
width*height
|
102
69
|
}
|
103
70
|
|
104
71
|
Given a tree where nodes are modifying _ACCUMULATOR_, the end state of
|
data/Rakefile
CHANGED
@@ -1,140 +1,3 @@
|
|
1
|
-
$LOAD_PATH.unshift "contrib/quix/lib"
|
2
1
|
|
3
|
-
|
4
|
-
require 'rake/contrib/rubyforgepublisher'
|
2
|
+
# rakelib generates tasks
|
5
3
|
|
6
|
-
$VERBOSE = nil
|
7
|
-
require 'rdoc/rdoc'
|
8
|
-
$VERBOSE = true
|
9
|
-
|
10
|
-
require 'fileutils'
|
11
|
-
include FileUtils
|
12
|
-
|
13
|
-
GEMSPEC = eval(File.read("comp_tree.gemspec"))
|
14
|
-
DOC_DIR = "html"
|
15
|
-
|
16
|
-
######################################################################
|
17
|
-
# clean
|
18
|
-
|
19
|
-
task :clean => [:clobber, :clean_doc] do
|
20
|
-
end
|
21
|
-
|
22
|
-
task :clean_doc do
|
23
|
-
rm_rf(DOC_DIR)
|
24
|
-
end
|
25
|
-
|
26
|
-
######################################################################
|
27
|
-
# test
|
28
|
-
|
29
|
-
task :test do
|
30
|
-
require 'test/all'
|
31
|
-
end
|
32
|
-
|
33
|
-
######################################################################
|
34
|
-
# package
|
35
|
-
|
36
|
-
task :package => :clean
|
37
|
-
|
38
|
-
Rake::GemPackageTask.new(GEMSPEC) { |t|
|
39
|
-
t.need_tar = true
|
40
|
-
}
|
41
|
-
|
42
|
-
######################################################################
|
43
|
-
# doc
|
44
|
-
|
45
|
-
task :doc => :clean_doc do
|
46
|
-
files = %w(README) + %w(driver error node task_node).map { |name|
|
47
|
-
"lib/comp_tree/#{name}.rb"
|
48
|
-
}
|
49
|
-
|
50
|
-
options = [
|
51
|
-
"-o", DOC_DIR,
|
52
|
-
"--title", "comp_tree: #{GEMSPEC.summary}",
|
53
|
-
"--main", "README"
|
54
|
-
]
|
55
|
-
|
56
|
-
RDoc::RDoc.new.document(files + options)
|
57
|
-
end
|
58
|
-
|
59
|
-
######################################################################
|
60
|
-
# git
|
61
|
-
|
62
|
-
def git(*args)
|
63
|
-
cmd = ["git"] + args
|
64
|
-
sh(*cmd)
|
65
|
-
end
|
66
|
-
|
67
|
-
task :init_contrib do
|
68
|
-
unless `git remote`.split.include? "quix"
|
69
|
-
git(*%w!remote add -f quix git@github.com:quix/quix.git!)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
task :add_contrib_first_time => :init_contrib do
|
74
|
-
git(*%w!merge --squash -s ours --no-commit quix/master!)
|
75
|
-
git(*%w!read-tree --prefix=contrib/quix -u quix/master!)
|
76
|
-
git("commit", "-m", "add quix utils")
|
77
|
-
end
|
78
|
-
|
79
|
-
task :run_pull_contrib do
|
80
|
-
git(*%w!pull --no-commit -s subtree quix master!)
|
81
|
-
end
|
82
|
-
|
83
|
-
task :pull_contrib => [ :init_contrib, :run_pull_contrib ]
|
84
|
-
|
85
|
-
######################################################################
|
86
|
-
# publisher
|
87
|
-
|
88
|
-
task :publish => :doc do
|
89
|
-
Rake::RubyForgePublisher.new('comptree', 'quix').upload
|
90
|
-
end
|
91
|
-
|
92
|
-
######################################################################
|
93
|
-
# release
|
94
|
-
|
95
|
-
task :prerelease => :clean do
|
96
|
-
rm_rf(DOC_DIR)
|
97
|
-
rm_rf("pkg")
|
98
|
-
unless `git status` =~ %r!nothing to commit \(working directory clean\)!
|
99
|
-
raise "Directory not clean"
|
100
|
-
end
|
101
|
-
unless `ping -c2 github.com` =~ %r!0% packet loss!i
|
102
|
-
raise "No ping for github.com"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def rubyforge(command, file)
|
107
|
-
sh("rubyforge",
|
108
|
-
command,
|
109
|
-
GEMSPEC.rubyforge_project,
|
110
|
-
GEMSPEC.rubyforge_project,
|
111
|
-
GEMSPEC.version.to_s,
|
112
|
-
file)
|
113
|
-
end
|
114
|
-
|
115
|
-
task :finish_release do
|
116
|
-
gem, tgz = %w(gem tgz).map { |ext|
|
117
|
-
"pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.#{ext}"
|
118
|
-
}
|
119
|
-
gem_md5, tgz_md5 = [gem, tgz].map { |file|
|
120
|
-
"#{file}.md5".tap { |md5|
|
121
|
-
sh("md5sum #{file} > #{md5}")
|
122
|
-
}
|
123
|
-
}
|
124
|
-
|
125
|
-
rubyforge("add_release", gem)
|
126
|
-
rubyforge("add_file", gem_md5)
|
127
|
-
rubyforge("add_file", tgz)
|
128
|
-
rubyforge("add_file", tgz_md5)
|
129
|
-
|
130
|
-
git("tag", "comp_tree-" + GEMSPEC.version.to_s)
|
131
|
-
git(*%w(push --tags origin master))
|
132
|
-
end
|
133
|
-
|
134
|
-
task :release =>
|
135
|
-
[
|
136
|
-
:prerelease,
|
137
|
-
:package,
|
138
|
-
:publish,
|
139
|
-
:finish_release,
|
140
|
-
]
|
data/comp_tree.gemspec
CHANGED
@@ -1,38 +1,41 @@
|
|
1
1
|
|
2
|
-
Gem::Specification.new { |
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
"
|
2
|
+
Gem::Specification.new { |g|
|
3
|
+
g.author = "James M. Lawrence"
|
4
|
+
g.email = "quixoticsycophant@gmail.com"
|
5
|
+
g.summary = "Parallel Computation Tree"
|
6
|
+
g.name = "comp_tree"
|
7
|
+
g.rubyforge_project = "comptree"
|
8
|
+
g.homepage = "comptree.rubyforge.org"
|
9
|
+
g.version = "0.7.0"
|
10
|
+
g.description =
|
11
|
+
"Build a computation tree and execute it with N parallel threads."
|
12
12
|
|
13
|
-
|
14
|
-
Dir["./**/*.rb"] +
|
15
|
-
Dir["./**/Rakefile"]
|
13
|
+
readme = "README"
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
bucket
|
26
|
-
comp_tree\.rb
|
15
|
+
g.files = %W[
|
16
|
+
CHANGES
|
17
|
+
#{readme}
|
18
|
+
Rakefile
|
19
|
+
#{g.name}.gemspec
|
20
|
+
install.rb
|
21
|
+
] + %w[lib rakelib test].inject(Array.new) { |acc, dir|
|
22
|
+
acc + Dir[dir + "/**/*.rb"]
|
27
23
|
}
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
g.has_rdoc = true
|
25
|
+
rdoc_files = [
|
26
|
+
readme,
|
27
|
+
"lib/comp_tree.rb",
|
28
|
+
"lib/comp_tree/driver.rb",
|
29
|
+
"lib/comp_tree/error.rb"
|
30
|
+
]
|
31
|
+
g.extra_rdoc_files += [readme]
|
32
|
+
|
33
|
+
g.rdoc_options += [
|
31
34
|
"--main",
|
32
|
-
|
35
|
+
readme,
|
33
36
|
"--title",
|
34
|
-
"
|
35
|
-
] +
|
36
|
-
acc + ["--exclude",
|
37
|
+
"#{g.name}: #{g.summary}"
|
38
|
+
] + (g.files - rdoc_files).inject(Array.new) { |acc, file|
|
39
|
+
acc + ["--exclude", file]
|
37
40
|
}
|
38
41
|
}
|
data/install.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
$LOAD_PATH.unshift "
|
2
|
-
require '
|
3
|
-
|
1
|
+
$LOAD_PATH.unshift "rakelib"
|
2
|
+
require 'jumpstart/simple_installer'
|
3
|
+
Jumpstart::SimpleInstaller.new.run
|
data/lib/comp_tree/algorithm.rb
CHANGED
@@ -1,210 +1,171 @@
|
|
1
1
|
|
2
|
-
require 'comp_tree/diagnostic'
|
3
|
-
require 'comp_tree/retriable_fork'
|
4
|
-
|
5
2
|
module CompTree
|
6
3
|
module Algorithm
|
7
|
-
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def loop_with(leave, again)
|
7
|
+
catch(leave) {
|
8
|
+
while true
|
9
|
+
catch(again) {
|
10
|
+
yield
|
11
|
+
}
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
8
15
|
|
9
|
-
def compute_multithreaded(root, num_threads
|
10
|
-
trace "Computing #{root.name} with #{num_threads} threads"
|
11
|
-
|
12
|
-
|
16
|
+
def compute_multithreaded(root, num_threads)
|
17
|
+
#trace "Computing #{root.name} with #{num_threads} threads"
|
18
|
+
finished = nil
|
19
|
+
tree_mutex = Mutex.new
|
13
20
|
node_finished_condition = ConditionVariable.new
|
14
21
|
thread_wake_condition = ConditionVariable.new
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
num_threads_ready = 0
|
20
|
-
|
21
|
-
num_threads.times { |thread_index|
|
22
|
-
threads << Thread.new {
|
22
|
+
num_threads_in_use = 0
|
23
|
+
|
24
|
+
threads = (0...num_threads).map { |thread_index|
|
25
|
+
Thread.new {
|
23
26
|
#
|
24
27
|
# wait for main thread
|
25
28
|
#
|
26
|
-
|
27
|
-
trace "Thread #{thread_index} waiting to start"
|
28
|
-
|
29
|
-
thread_wake_condition.wait(
|
29
|
+
tree_mutex.synchronize {
|
30
|
+
#trace "Thread #{thread_index} waiting to start"
|
31
|
+
num_threads_in_use += 1
|
32
|
+
thread_wake_condition.wait(tree_mutex)
|
30
33
|
}
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
35
|
+
loop_with(:leave, :again) {
|
36
|
+
node = tree_mutex.synchronize {
|
37
|
+
#trace "Thread #{thread_index} acquired tree lock; begin search"
|
38
|
+
if finished
|
39
|
+
#trace "Thread #{thread_index} detected finish"
|
40
|
+
num_threads_in_use -= 1
|
41
|
+
throw :leave
|
42
|
+
else
|
43
|
+
#
|
44
|
+
# Find a node. The node we obtain, if any, will be locked.
|
45
|
+
#
|
46
|
+
node = find_node(root)
|
47
|
+
if node
|
48
|
+
#trace "Thread #{thread_index} found node #{node.name}"
|
49
|
+
node
|
50
|
+
else
|
51
|
+
#trace "Thread #{thread_index}: no node found; sleeping."
|
52
|
+
thread_wake_condition.wait(tree_mutex)
|
53
|
+
throw :again
|
54
|
+
end
|
55
|
+
end
|
48
56
|
}
|
49
57
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
}
|
62
|
-
|
58
|
+
#trace "Thread #{thread_index} computing node"
|
59
|
+
#debug {
|
60
|
+
# node.trace_compute
|
61
|
+
#}
|
62
|
+
node.compute
|
63
|
+
#trace "Thread #{thread_index} node computed; waiting for tree lock"
|
64
|
+
|
65
|
+
tree_mutex.synchronize {
|
66
|
+
#trace "Thread #{thread_index} acquired tree lock"
|
67
|
+
#debug {
|
68
|
+
# name = "#{node.name}" + ((node == root) ? " (ROOT NODE)" : "")
|
69
|
+
# initial = "Thread #{thread_index} compute result for #{name}: "
|
70
|
+
# status = node.computed.is_a?(Exception) ? "error" : "success"
|
71
|
+
# trace initial + status
|
72
|
+
# trace "Thread #{thread_index} node result: #{node.result}"
|
73
|
+
#}
|
74
|
+
|
75
|
+
if node.computed.is_a? Exception
|
76
|
+
#
|
77
|
+
# An error occurred; we are done.
|
78
|
+
#
|
79
|
+
finished = node.computed
|
80
|
+
elsif node == root
|
81
|
+
#
|
82
|
+
# Root node was computed; we are done.
|
83
|
+
#
|
84
|
+
finished = true
|
85
|
+
end
|
86
|
+
|
63
87
|
#
|
64
88
|
# remove locks for this node (shared lock and own lock)
|
65
89
|
#
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
node_finished_condition.signal
|
76
|
-
}
|
77
|
-
else
|
78
|
-
trace "Thread #{thread_index}: no node found; sleeping."
|
79
|
-
mutex.synchronize {
|
80
|
-
thread_wake_condition.wait(mutex)
|
81
|
-
}
|
82
|
-
end
|
83
|
-
end
|
84
|
-
trace "Thread #{thread_index} exiting"
|
90
|
+
node.unlock
|
91
|
+
|
92
|
+
#
|
93
|
+
# Tell the main thread that another node was computed.
|
94
|
+
#
|
95
|
+
node_finished_condition.signal
|
96
|
+
}
|
97
|
+
}
|
98
|
+
#trace "Thread #{thread_index} exiting"
|
85
99
|
}
|
86
100
|
}
|
87
101
|
|
88
|
-
trace "Main: waiting for threads to launch and block."
|
89
|
-
|
90
|
-
break if mutex.synchronize {
|
91
|
-
num_threads_ready == num_threads
|
92
|
-
}
|
102
|
+
#trace "Main: waiting for threads to launch and block."
|
103
|
+
until tree_mutex.synchronize { num_threads_in_use == num_threads }
|
93
104
|
Thread.pass
|
94
105
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
trace "Main: waking threads"
|
106
|
+
|
107
|
+
tree_mutex.synchronize {
|
108
|
+
#trace "Main: entering main loop"
|
109
|
+
until num_threads_in_use == 0
|
110
|
+
#trace "Main: waking threads"
|
100
111
|
thread_wake_condition.broadcast
|
101
112
|
|
102
|
-
if
|
103
|
-
trace "Main: detected finish."
|
113
|
+
if finished
|
114
|
+
#trace "Main: detected finish."
|
104
115
|
break
|
105
116
|
end
|
106
117
|
|
107
|
-
trace "Main: waiting for a node"
|
108
|
-
node_finished_condition.wait(
|
109
|
-
trace "Main: got a node"
|
118
|
+
#trace "Main: waiting for a node"
|
119
|
+
node_finished_condition.wait(tree_mutex)
|
120
|
+
#trace "Main: got a node"
|
110
121
|
end
|
111
122
|
}
|
112
123
|
|
113
|
-
trace "Main: waiting for threads to finish."
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
throw :
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
Thread.pass
|
123
|
-
end
|
124
|
+
#trace "Main: waiting for threads to finish."
|
125
|
+
loop_with(:leave, :again) {
|
126
|
+
tree_mutex.synchronize {
|
127
|
+
if threads.all? { |thread| thread.status == false }
|
128
|
+
throw :leave
|
129
|
+
end
|
130
|
+
thread_wake_condition.broadcast
|
131
|
+
}
|
132
|
+
Thread.pass
|
124
133
|
}
|
125
134
|
|
126
|
-
trace "Main: computation done."
|
127
|
-
|
135
|
+
#trace "Main: computation done."
|
136
|
+
if finished.is_a? Exception
|
137
|
+
raise finished
|
138
|
+
else
|
139
|
+
root.result
|
140
|
+
end
|
128
141
|
end
|
129
142
|
|
130
143
|
def find_node(node)
|
131
|
-
# --- only called inside mutex
|
132
|
-
trace "Looking for a node, starting with #{node.name}"
|
133
|
-
if node.
|
144
|
+
# --- only called inside shared tree mutex
|
145
|
+
#trace "Looking for a node, starting with #{node.name}"
|
146
|
+
if node.computed
|
134
147
|
#
|
135
148
|
# already computed
|
136
149
|
#
|
137
|
-
trace "#{node.name} has been computed"
|
150
|
+
#trace "#{node.name} has been computed"
|
138
151
|
nil
|
139
|
-
elsif node.
|
152
|
+
elsif (children_results = node.find_children_results) and node.try_lock
|
140
153
|
#
|
141
154
|
# Node is not computed and its children are computed;
|
142
155
|
# and we have the lock. Ready to compute.
|
143
156
|
#
|
157
|
+
node.children_results = children_results
|
144
158
|
node
|
145
159
|
else
|
146
160
|
#
|
147
161
|
# locked or children not computed; recurse to children
|
148
162
|
#
|
149
|
-
trace "Checking #{node.name}'s children"
|
163
|
+
#trace "Checking #{node.name}'s children"
|
150
164
|
node.each_child { |child|
|
151
|
-
|
152
|
-
return next_node
|
153
|
-
end
|
165
|
+
next_node = find_node(child) and return next_node
|
154
166
|
}
|
155
167
|
nil
|
156
168
|
end
|
157
169
|
end
|
158
|
-
|
159
|
-
def compute_node(node, use_fork, bucket)
|
160
|
-
if use_fork
|
161
|
-
trace "About to fork for node #{node.name}"
|
162
|
-
if bucket
|
163
|
-
#
|
164
|
-
# Use our assigned bucket to transfer the result.
|
165
|
-
#
|
166
|
-
fork_node(node) {
|
167
|
-
node.trace_compute
|
168
|
-
bucket.contents = node.compute
|
169
|
-
}
|
170
|
-
bucket.contents
|
171
|
-
else
|
172
|
-
#
|
173
|
-
# No bucket -- discarding result
|
174
|
-
#
|
175
|
-
fork_node(node) {
|
176
|
-
node.trace_compute
|
177
|
-
node.compute
|
178
|
-
}
|
179
|
-
true
|
180
|
-
end
|
181
|
-
else
|
182
|
-
#
|
183
|
-
# No fork
|
184
|
-
#
|
185
|
-
node.trace_compute
|
186
|
-
node.compute
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def fork_node(node)
|
191
|
-
trace "About to fork for node #{node.name}"
|
192
|
-
process_id = RetriableFork.fork {
|
193
|
-
trace "Fork: process #{Process.pid}"
|
194
|
-
node.trace_compute
|
195
|
-
yield
|
196
|
-
trace "Fork: computation done"
|
197
|
-
}
|
198
|
-
trace "Waiting for process #{process_id}"
|
199
|
-
Process.wait(process_id)
|
200
|
-
trace "Process #{process_id} finished"
|
201
|
-
exitstatus = $?.exitstatus
|
202
|
-
if exitstatus != 0
|
203
|
-
trace "Process #{process_id} returned #{exitstatus}; exiting."
|
204
|
-
exit(1)
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
extend self
|
209
170
|
end
|
210
171
|
end
|