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.
Files changed (56) hide show
  1. data/CHANGES +24 -0
  2. data/README +19 -52
  3. data/Rakefile +1 -138
  4. data/comp_tree.gemspec +33 -30
  5. data/install.rb +3 -3
  6. data/lib/comp_tree/algorithm.rb +117 -156
  7. data/lib/comp_tree/driver.rb +39 -154
  8. data/lib/comp_tree/error.rb +18 -23
  9. data/lib/comp_tree/node.rb +46 -50
  10. data/lib/comp_tree.rb +56 -0
  11. data/rakelib/jumpstart/ruby.rb +51 -0
  12. data/{contrib/quix/lib/quix → rakelib/jumpstart}/simple_installer.rb +11 -13
  13. data/test/common.rb +29 -0
  14. data/test/test_basic.rb +189 -0
  15. data/test/test_circular.rb +34 -31
  16. data/test/test_drain.rb +38 -0
  17. data/test/test_exception.rb +37 -86
  18. data/test/test_flood.rb +14 -0
  19. data/test/test_grind.rb +77 -0
  20. data/test/test_sequential.rb +21 -0
  21. metadata +45 -58
  22. data/contrib/quix/Rakefile +0 -16
  23. data/contrib/quix/install.rb +0 -3
  24. data/contrib/quix/lib/quix/builtin/dir/casefold_brackets.rb +0 -7
  25. data/contrib/quix/lib/quix/builtin/kernel/tap.rb +0 -9
  26. data/contrib/quix/lib/quix/builtin/module/include.rb +0 -21
  27. data/contrib/quix/lib/quix/builtin/module/private.rb +0 -41
  28. data/contrib/quix/lib/quix/config.rb +0 -37
  29. data/contrib/quix/lib/quix/cygwin.rb +0 -60
  30. data/contrib/quix/lib/quix/diagnostic.rb +0 -44
  31. data/contrib/quix/lib/quix/enumerable.rb +0 -33
  32. data/contrib/quix/lib/quix/fileutils.rb +0 -37
  33. data/contrib/quix/lib/quix/hash_struct.rb +0 -27
  34. data/contrib/quix/lib/quix/kernel.rb +0 -61
  35. data/contrib/quix/lib/quix/lazy_struct.rb +0 -55
  36. data/contrib/quix/lib/quix/string.rb +0 -38
  37. data/contrib/quix/lib/quix/subpackager.rb +0 -52
  38. data/contrib/quix/lib/quix/thread_local.rb +0 -32
  39. data/contrib/quix/lib/quix/vars.rb +0 -138
  40. data/contrib/quix/lib/quix.rb +0 -32
  41. data/contrib/quix/test/all.rb +0 -12
  42. data/contrib/quix/test/test_deps.rb +0 -25
  43. data/contrib/quix/test/test_include.rb +0 -47
  44. data/contrib/quix/test/test_private.rb +0 -86
  45. data/contrib/quix/test/test_root.rb +0 -19
  46. data/contrib/quix/test/test_struct.rb +0 -48
  47. data/contrib/quix/test/test_vars.rb +0 -187
  48. data/lib/comp_tree/bucket_ipc.rb +0 -151
  49. data/lib/comp_tree/diagnostic.rb +0 -44
  50. data/lib/comp_tree/misc.rb +0 -61
  51. data/lib/comp_tree/retriable_fork.rb +0 -42
  52. data/lib/comp_tree/tap.rb +0 -9
  53. data/lib/comp_tree/task_node.rb +0 -22
  54. data/test/all.rb +0 -12
  55. data/test/test_bucketipc.rb +0 -72
  56. data/test/test_comp_tree.rb +0 -364
@@ -1,10 +1,6 @@
1
1
 
2
- require 'comp_tree/bucket_ipc'
3
- require 'comp_tree/diagnostic'
4
- require 'comp_tree/misc'
5
2
  require 'comp_tree/algorithm'
6
3
  require 'comp_tree/node'
7
- require 'comp_tree/task_node'
8
4
  require 'comp_tree/error'
9
5
 
10
6
  require 'thread'
@@ -15,53 +11,25 @@ module CompTree
15
11
  # responsible for defining nodes and running computations.
16
12
  #
17
13
  class Driver
18
- DEFAULTS = {
19
- :threads => 1,
20
- :fork => false,
21
- :timeout => 5.0,
22
- :wait_interval => 0.02,
23
- }
14
+ include Algorithm
24
15
 
25
- include Diagnostic
26
- include Misc
27
-
28
16
  #
29
- # Begin a new computation tree.
17
+ # Build and run a new computation tree.
30
18
  #
31
19
  # Options hash:
32
20
  #
33
21
  # <tt>:node_class</tt> -- (Class) CompTree::Node subclass from
34
22
  # which nodes are created.
35
23
  #
36
- # <tt>:discard_result</tt> -- (boolean) If you are <em>not</em>
37
- # interested in the final answer, but only in the actions which
38
- # complete the computation, then set this to +true+. This is
39
- # equivalent to saying <tt>:node_class => CompTree::TaskNode</tt>.
40
- # (If you are forking processes, it is good to know that IPC is
41
- # not needed to communicate the result.)
42
- #
43
24
  def initialize(opts = nil)
44
- if opts and opts[:node_class] and opts[:discard_result]
45
- raise(
46
- Error::ArgumentError,
47
- "#{self.class.name}.new: :discard_result and :node_class " +
48
- "are mutually exclusive")
49
- end
50
-
51
- @node_class =
25
+ @node_class = (
52
26
  if opts and opts[:node_class]
53
27
  opts[:node_class]
54
- elsif opts and opts[:discard_result]
55
- TaskNode
56
28
  else
57
29
  Node
58
30
  end
59
-
31
+ )
60
32
  @nodes = Hash.new
61
-
62
- if block_given?
63
- yield self
64
- end
65
33
  end
66
34
 
67
35
  #
@@ -72,55 +40,43 @@ module CompTree
72
40
  #
73
41
  # Define a computation node.
74
42
  #
75
- # There are three distinct forms of a node definition. In each of
76
- # the following examples, a computation node named +area+ is
77
- # defined which depends on the nodes +height+, +width+, +offset+.
43
+ # The first argument is the name of the node to define.
44
+ # Subsequent arguments are the names of this node's children.
78
45
  #
79
- # The method_missing form:
80
- # driver.define_area(:width, :height, :offset) { |width, height, offset|
81
- # width*height - offset
82
- # }
46
+ # The values of the child nodes are passed to the block. The
47
+ # block returns the result of this node.
83
48
  #
84
- # The eval form:
85
- # driver.define_area :width, :height, :offset, %{
86
- # width*height - offset
87
- # }
88
- # (Note the '%' before the brace.)
49
+ # In this example, a computation node named +area+ is defined
50
+ # which depends on the nodes +width+ and +height+.
89
51
  #
90
- # The raw form:
91
- # driver.define(:area, :width, :height, :offset) { |width, height, offset|
92
- # width*height - offset
93
- # }
52
+ # driver.define(:area, :width, :height) { |width, height|
53
+ # width*height
54
+ # }
94
55
  #
95
56
  def define(*args, &block)
96
57
  parent_name = args.first
97
58
  children_names = args[1..-1]
98
59
 
99
60
  unless parent_name
100
- raise Error::ArgumentError, "No name given for node"
61
+ raise ArgumentError, "No name given for node"
101
62
  end
102
63
 
103
64
  #
104
65
  # retrieve or create parent and children
105
66
  #
106
- parent =
107
- if t = @nodes[parent_name]
108
- t
109
- else
110
- @nodes[parent_name] = @node_class.new(parent_name)
111
- end
67
+ parent = @nodes[parent_name] || (
68
+ @nodes[parent_name] = @node_class.new(parent_name)
69
+ )
112
70
 
113
71
  if parent.function
114
- raise Error::RedefinitionError, "Node #{parent.name} already defined."
72
+ raise RedefinitionError, "Node `#{parent.name.inspect}' redefined."
115
73
  end
116
74
  parent.function = block
117
75
 
118
76
  children = children_names.map { |child_name|
119
- if t = @nodes[child_name]
120
- t
121
- else
77
+ @nodes[child_name] || (
122
78
  @nodes[child_name] = @node_class.new(child_name)
123
- end
79
+ )
124
80
  }
125
81
 
126
82
  #
@@ -132,72 +88,37 @@ module CompTree
132
88
  }
133
89
  end
134
90
 
135
- #
136
- # parsing/evaling helper
137
- #
138
- def evaling_define(*args) #:nodoc:
139
- function_name = args[0]
140
- function_arg_names = args[1..-2]
141
- function_string = args.last.to_str
142
-
143
- comma_separated = function_arg_names.map { |name|
144
- name.to_s
145
- }.join(",")
146
-
147
- eval_me = %{
148
- lambda { |#{comma_separated}|
149
- #{function_string}
150
- }
151
- }
152
-
153
- function = eval(eval_me, TOPLEVEL_BINDING)
154
-
155
- define(function_name, *function_arg_names, &function)
156
- end
157
-
158
- def method_missing(symbol, *args, &block) #:nodoc:
159
- if match = symbol.to_s.match(%r!\Adefine_(\w+)\Z!)
160
- method_name = match.captures.first.to_sym
161
- if block
162
- define(method_name, *args, &block)
163
- else
164
- evaling_define(method_name, *args)
165
- end
166
- else
167
- super(symbol, *args, &block)
168
- end
169
- end
170
-
171
91
  #
172
92
  # Mark this node and all its children as uncomputed.
173
93
  #
174
94
  # Arguments:
175
95
  #
176
- # +name+ -- (Symbol) node name.
96
+ # +name+ -- unique node identifier (usually a symbol).
177
97
  #
178
98
  def reset(name)
179
99
  @nodes[name].reset
180
100
  end
181
101
 
182
102
  #
183
- # Check for a cyclic graph below the given node. Raises
184
- # CompTree::Error::CircularError if found.
103
+ # Check for a cyclic graph below the given node. If found,
104
+ # returns the names of the nodes (in order) which form a loop.
105
+ # Otherwise returns nil.
185
106
  #
186
107
  # Arguments:
187
108
  #
188
- # +name+ -- (Symbol) node name.
109
+ # +name+ -- unique node identifier (usually a symbol).
189
110
  #
190
111
  def check_circular(name)
191
- helper = lambda { |root, chain|
112
+ helper = Proc.new { |root, chain|
192
113
  if chain.include? root
193
- raise Error::CircularError,
194
- "Circular dependency detected: #{root} => #{chain.last} => #{root}"
114
+ return chain + [root]
195
115
  end
196
116
  @nodes[root].children.each { |child|
197
117
  helper.call(child.name, chain + [root])
198
118
  }
199
119
  }
200
120
  helper.call(name, [])
121
+ nil
201
122
  end
202
123
 
203
124
  #
@@ -205,62 +126,26 @@ module CompTree
205
126
  #
206
127
  # Arguments:
207
128
  #
208
- # +name+ -- (Symbol) node name.
209
- #
210
- # Options hash:
211
- #
212
- # <tt>:threads</tt> -- (Integer) Number of parallel threads.
129
+ # +name+ -- unique node identifier (usually a symbol).
213
130
  #
214
- # <tt>:fork</tt> -- (boolean) Whether to fork each computation
215
- # node into its own process.
131
+ # +threads+ -- (Integer) number of threads.
216
132
  #
217
- # Defaults options are taken from Driver::DEFAULTS.
133
+ # compute(:volume, :threads => 4) syntax is also accepted.
218
134
  #
219
- def compute(name, opts = nil)
220
- #
221
- # Undocumented options:
222
- #
223
- # <tt>:wait_interval</tt> -- (seconds) (Obscure) How long to
224
- # wait after an IPC failure.
225
- #
226
- # <tt>:timeout</tt> -- (seconds) (Obscure) Give up after this
227
- # period of persistent IPC failures.
228
- #
229
-
230
- abort_on_exception {
231
- compute_private(name, opts || Hash.new)
232
- }
233
- end
234
-
235
- private
236
-
237
- def compute_private(name, opts_in)
238
- opts = DEFAULTS.merge(opts_in)
135
+ def compute(name, opts)
136
+ threads = opts.is_a?(Hash) ? opts[:threads] : opts
239
137
  root = @nodes[name]
240
138
 
241
- if opts[:threads] < 1
242
- raise Error::ArgumentError, "threads is #{opts[:threads]}"
139
+ if threads < 1
140
+ raise ArgumentError, "threads is #{threads}"
243
141
  end
244
142
 
245
- if opts[:threads] == 1
143
+ if root.computed
144
+ root.result
145
+ elsif threads == 1
246
146
  root.result = root.compute_now
247
- elsif opts[:fork] and not @node_class.discard_result?
248
- #
249
- # Use buckets to send results across forks.
250
- #
251
- result = nil
252
- BucketIPC::Driver.new(opts[:threads], opts) { |buckets|
253
- result =
254
- Algorithm.compute_multithreaded(
255
- root, opts[:threads], opts[:fork], buckets)
256
- }
257
- result
258
147
  else
259
- #
260
- # Multithreaded computation without fork.
261
- #
262
- Algorithm.compute_multithreaded(
263
- root, opts[:threads], opts[:fork], nil)
148
+ compute_multithreaded(root, threads)
264
149
  end
265
150
  end
266
151
  end
@@ -1,27 +1,22 @@
1
1
 
2
2
  module CompTree
3
- module Error
4
- # Base class for CompTree errors.
5
- class Base < StandardError ; end
3
+ # Base class for CompTree errors.
4
+ class Error < StandardError ; end
6
5
 
7
- # Internal error inside CompTree. Please send a bug report.
8
- class AssertionFailed < Base ; end
9
-
10
- # Bad arguments were passed to a method.
11
- class ArgumentError < Base ; end
12
-
13
- #
14
- # Attempt to redefine a Node.
15
- #
16
- # If you wish to only replace the function, set
17
- # driver.nodes[name].function = some_new_lambda
18
- #
19
- class RedefinitionError < Base ; end
20
-
21
- # A Cyclic graph was detected.
22
- class CircularError < Base ; end
23
-
24
- # No function was defined for this node.
25
- class NoFunctionError < Base ; end
26
- end
6
+ # Internal error inside CompTree. Please send a bug report.
7
+ class AssertionFailedError < Error ; end
8
+
9
+ # Bad arguments were passed to a method.
10
+ class ArgumentError < Error ; end
11
+
12
+ #
13
+ # Attempt to redefine a Node.
14
+ #
15
+ # If you wish to only replace the function, set
16
+ # driver.nodes[name].function = some_new_lambda
17
+ #
18
+ class RedefinitionError < Error ; end
19
+
20
+ # No function was defined for this node.
21
+ class NoFunctionError < Error ; end
27
22
  end
@@ -1,5 +1,4 @@
1
1
 
2
- require 'comp_tree/diagnostic'
3
2
  require 'thread'
4
3
 
5
4
  module CompTree
@@ -7,24 +6,26 @@ module CompTree
7
6
  # Base class for nodes in the computation tree.
8
7
  #
9
8
  class Node
10
- include Diagnostic
11
-
12
9
  attr_reader :name #:nodoc:
13
10
 
14
11
  attr_accessor :parents #:nodoc:
15
12
  attr_accessor :children #:nodoc:
16
13
  attr_accessor :function #:nodoc:
17
14
  attr_accessor :result #:nodoc:
15
+ attr_accessor :computed #:nodoc:
18
16
  attr_accessor :shared_lock #:nodoc:
19
17
 
18
+ attr_writer :children_results #:nodoc:
19
+
20
20
  #
21
21
  # Create a node
22
22
  #
23
23
  def initialize(name) #:nodoc:
24
24
  @name = name
25
- @mutex = Mutex.new
26
- @children = []
27
25
  @parents = []
26
+ @children = []
27
+ @function = nil
28
+ @mutex = Mutex.new
28
29
  reset_self
29
30
  end
30
31
 
@@ -32,9 +33,10 @@ module CompTree
32
33
  # Reset the computation for this node.
33
34
  #
34
35
  def reset_self #:nodoc:
36
+ @result = nil
37
+ @computed = nil
35
38
  @shared_lock = 0
36
39
  @children_results = nil
37
- @result = nil
38
40
  end
39
41
 
40
42
  #
@@ -83,50 +85,55 @@ module CompTree
83
85
  # If all children have been computed, return their results;
84
86
  # otherwise return nil.
85
87
  #
86
- def children_results #:nodoc:
87
- if @children_results
88
- @children_results
89
- else
90
- results = @children.map { |child|
91
- if child_result = child.result
92
- child_result
93
- else
88
+ # Do not assign to @children_results since own lock is not
89
+ # necessarily aquired.
90
+ #
91
+ def find_children_results #:nodoc:
92
+ @children_results or (
93
+ @children.map { |child|
94
+ unless child.computed
94
95
  return nil
95
96
  end
97
+ child.result
96
98
  }
97
- @children_results = results
98
- end
99
+ )
99
100
  end
100
101
 
101
- def trace_compute #:nodoc:
102
- debug {
103
- # --- own mutex
104
- trace "Computing #{@name}"
105
- raise Error::AssertionFailed if @result
106
- raise Error::AssertionFailed unless @mutex.locked?
107
- raise Error::AssertionFailed unless @children_results
108
- }
109
- end
102
+ #def trace_compute #:nodoc:
103
+ # debug {
104
+ # # --- own mutex
105
+ # trace "Computing #{@name}"
106
+ # raise AssertionFailedError if @computed
107
+ # raise AssertionFailedError unless @mutex.locked?
108
+ # raise AssertionFailedError unless @children_results
109
+ # }
110
+ #end
110
111
 
111
112
  #
112
113
  # Compute this node; children must be computed and lock must be
113
114
  # already acquired.
114
115
  #
115
116
  def compute #:nodoc:
116
- unless defined?(@function) and @function
117
- raise Error::NoFunctionError,
117
+ begin
118
+ unless @function
119
+ raise NoFunctionError,
118
120
  "No function was defined for node '#{@name.inspect}'"
121
+ end
122
+ @result = @function.call(*@children_results)
123
+ @computed = true
124
+ rescue Exception => e
125
+ @computed = e
119
126
  end
120
- @function.call(*@children_results)
127
+ @result
121
128
  end
122
129
 
123
130
  def try_lock #:nodoc:
124
131
  # --- shared tree mutex and own mutex
125
132
  if @shared_lock == 0 and @mutex.try_lock
126
- trace "Locking #{@name}"
133
+ #trace "Locking #{@name}"
127
134
  each_upward { |node|
128
135
  node.shared_lock += 1
129
- trace "#{node.name} locked by #{@name}: level: #{node.shared_lock}"
136
+ #trace "#{node.name} locked by #{@name}: level: #{node.shared_lock}"
130
137
  }
131
138
  true
132
139
  else
@@ -136,30 +143,19 @@ module CompTree
136
143
 
137
144
  def unlock #:nodoc:
138
145
  # --- shared tree mutex and own mutex
139
- debug {
140
- raise Error::AssertionFailed unless @mutex.locked?
141
- trace "Unlocking #{@name}"
142
- }
146
+ #debug {
147
+ # raise AssertionFailedError unless @mutex.locked?
148
+ # trace "Unlocking #{@name}"
149
+ #}
143
150
  each_upward { |node|
144
151
  node.shared_lock -= 1
145
- debug {
146
- if node.shared_lock == 0
147
- trace "#{node.name} unlocked by #{@name}"
148
- end
149
- }
152
+ #debug {
153
+ # if node.shared_lock == 0
154
+ # trace "#{node.name} unlocked by #{@name}"
155
+ # end
156
+ #}
150
157
  }
151
158
  @mutex.unlock
152
159
  end
153
-
154
- class << self
155
- #
156
- # Throw away the computation result?
157
- #
158
- # This Node base class always returns false.
159
- #
160
- def discard_result?
161
- false
162
- end
163
- end
164
160
  end
165
161
  end
data/lib/comp_tree.rb CHANGED
@@ -21,3 +21,59 @@
21
21
  #
22
22
 
23
23
  require 'comp_tree/driver'
24
+
25
+ #
26
+ # CompTree -- Parallel Computation Tree.
27
+ #
28
+ # See README.
29
+ #
30
+ module CompTree
31
+ class << self
32
+ #
33
+ # Build and run a new computation tree.
34
+ #
35
+ # A Driver instance is passed to the given block.
36
+ #
37
+ # Options hash:
38
+ #
39
+ # <tt>:node_class</tt> -- (Class) CompTree::Node subclass from
40
+ # which nodes are created.
41
+ #
42
+ # Example:
43
+ # CompTree.build do |driver|
44
+ #
45
+ # # Define a function named 'area' taking these two arguments.
46
+ # driver.define(:area, :width, :height) { |width, height|
47
+ # width*height
48
+ # }
49
+ #
50
+ # # Define a function 'width' which takes a 'border' argument.
51
+ # driver.define(:width, :border) { |border|
52
+ # 7 + border
53
+ # }
54
+ #
55
+ # # Ditto for 'height'.
56
+ # driver.define(:height, :border) { |border|
57
+ # 5 + border
58
+ # }
59
+ #
60
+ # #
61
+ # # Define a constant function 'border'.
62
+ # driver.define(:border) {
63
+ # 2
64
+ # }
65
+ #
66
+ # # Compute the area using four parallel threads.
67
+ # puts driver.compute(:area, :threads => 4)
68
+ # # => 63
69
+ #
70
+ # # We've done this computation.
71
+ # puts((7 + 2)*(5 + 2))
72
+ # # => 63
73
+ # end
74
+ #
75
+ def build(opts = nil)
76
+ yield Driver.new(opts)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,51 @@
1
+
2
+ require 'rbconfig'
3
+
4
+ module Jumpstart
5
+ module Ruby
6
+ EXECUTABLE = lambda {
7
+ name = File.join(
8
+ Config::CONFIG["bindir"],
9
+ Config::CONFIG["RUBY_INSTALL_NAME"]
10
+ )
11
+
12
+ if Config::CONFIG["host"] =~ %r!(mswin|cygwin|mingw)! and
13
+ File.basename(name) !~ %r!\.(exe|com|bat|cmd)\Z!i
14
+ name + Config::CONFIG["EXEEXT"]
15
+ else
16
+ name
17
+ end
18
+ }.call
19
+
20
+ class << self
21
+ def run(*args)
22
+ system(EXECUTABLE, *args)
23
+ end
24
+
25
+ def run_or_raise(*args)
26
+ cmd = [EXECUTABLE, *args]
27
+ unless system(*cmd)
28
+ msg = (
29
+ "failed to launch ruby: " +
30
+ "system(*#{cmd.inspect}) failed with status #{$?.exitstatus}"
31
+ )
32
+ raise msg
33
+ end
34
+ end
35
+
36
+ def with_warnings(value = true)
37
+ previous = $VERBOSE
38
+ $VERBOSE = value
39
+ begin
40
+ yield
41
+ ensure
42
+ $VERBOSE = previous
43
+ end
44
+ end
45
+
46
+ def no_warnings(&block)
47
+ with_warnings(false, &block)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -2,13 +2,9 @@
2
2
  require 'rbconfig'
3
3
  require 'fileutils'
4
4
  require 'find'
5
- require 'fileutils'
6
- require 'quix/vars'
7
-
8
- module CompTree
9
- class SimpleInstaller
10
- include CompTree::Vars
11
5
 
6
+ module Jumpstart
7
+ class SimpleInstaller
12
8
  def initialize
13
9
  dest_root = Config::CONFIG["sitelibdir"]
14
10
  sources = []
@@ -49,19 +45,21 @@ module CompTree
49
45
  end
50
46
  }
51
47
 
52
- acc << locals_to_hash {%{source, dest, install, uninstall}}
48
+ acc << {
49
+ :source => source,
50
+ :dest => dest,
51
+ :install => install,
52
+ :uninstall => uninstall,
53
+ }
53
54
  end
54
55
  }
55
56
  end
56
57
 
57
58
  def install_file?(source)
58
- !File.symlink?(source) and
59
- (File.directory?(source) or
60
- (File.file?(source) and File.extname(source) == ".rb"))
59
+ File.directory?(source) or
60
+ (File.file?(source) and File.extname(source) == ".rb")
61
61
  end
62
62
 
63
- attr_accessor :spec
64
-
65
63
  def install
66
64
  @spec.each { |entry|
67
65
  entry[:install].call
@@ -75,7 +73,7 @@ module CompTree
75
73
  end
76
74
 
77
75
  def run(args = ARGV)
78
- if args.empty? or (args.size == 1 and args.first == "install")
76
+ if args.empty?
79
77
  install
80
78
  elsif args.size == 1 and args.first == "--uninstall"
81
79
  uninstall