comp_tree 0.5.2 → 0.7.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/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
|