cloud-crowd 0.2.7 → 0.2.8

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.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'cloud-crowd'
3
- s.version = '0.2.7' # Keep version in sync with cloud-cloud.rb
4
- s.date = '2009-10-19'
3
+ s.version = '0.2.8' # Keep version in sync with cloud-cloud.rb
4
+ s.date = '2009-10-27'
5
5
 
6
6
  s.homepage = "http://wiki.github.com/documentcloud/cloud-crowd"
7
7
  s.summary = "Parallel Processing for the Rest of Us"
@@ -44,7 +44,7 @@ module CloudCrowd
44
44
  autoload :WorkUnit, 'cloud_crowd/models'
45
45
 
46
46
  # Keep this version in sync with the gemspec.
47
- VERSION = '0.2.7'
47
+ VERSION = '0.2.8'
48
48
 
49
49
  # Increment the schema version when there's a backwards incompatible change.
50
50
  SCHEMA_VERSION = 3
@@ -152,20 +152,25 @@ module CloudCrowd
152
152
  # Actions, then install only those into the actions directory.
153
153
  def actions
154
154
  return @actions if @actions
155
- @actions = {}
156
- default_actions = Dir["#{ROOT}/actions/*.rb"]
157
- installed_actions = Dir["#{@config_path}/actions/*.rb"]
158
- custom_actions = Dir["#{CloudCrowd.config[:actions_path]}/*.rb"]
159
- (default_actions + installed_actions + custom_actions).each do |path|
155
+ @actions = action_paths.inject({}) do |memo, path|
160
156
  name = File.basename(path, File.extname(path))
161
157
  require path
162
- @actions[name] = Module.const_get(Inflector.camelize(name))
158
+ memo[name] = Module.const_get(Inflector.camelize(name))
159
+ memo
163
160
  end
164
- @actions
165
161
  rescue NameError => e
166
162
  adjusted_message = "One of your actions failed to load. Please ensure that the name of your action class can be deduced from the name of the file. ex: 'word_count.rb' => 'WordCount'\n#{e.message}"
167
163
  raise NameError.new(adjusted_message, e.name)
168
164
  end
165
+
166
+ # Retrieve the list of every installed Action for this node or server.
167
+ def action_paths
168
+ default_actions = Dir["#{ROOT}/actions/*.rb"]
169
+ installed_actions = Dir["#{@config_path}/actions/*.rb"]
170
+ custom_actions = CloudCrowd.config[:actions_path] ? Dir["#{CloudCrowd.config[:actions_path]}/*.rb"] : []
171
+ default_actions + installed_actions + custom_actions
172
+ end
173
+
169
174
  end
170
175
 
171
176
  end
@@ -100,7 +100,7 @@ module CloudCrowd
100
100
 
101
101
  # This job is splittable if its Action has a +split+ method.
102
102
  def splittable?
103
- self.action_class.public_instance_methods.include? 'split'
103
+ self.action_class.public_instance_methods.map {|m| m.to_sym }.include? :split
104
104
  end
105
105
 
106
106
  # This job is done splitting if it's finished with its splitting work units.
@@ -110,7 +110,7 @@ module CloudCrowd
110
110
 
111
111
  # This job is mergeable if its Action has a +merge+ method.
112
112
  def mergeable?
113
- self.processing? && self.action_class.public_instance_methods.include?('merge')
113
+ self.processing? && self.action_class.public_instance_methods.map {|m| m.to_sym }.include?(:merge)
114
114
  end
115
115
 
116
116
  # Retrieve the class for this Job's Action.
@@ -11,6 +11,10 @@ module CloudCrowd
11
11
  # The size of the maximum signed integer in MySQL -- SQLite has no limit.
12
12
  MAX_RESERVATION = 2147483647
13
13
 
14
+ # We only reserve a certain number of WorkUnits in a single go, to avoid
15
+ # reserving the entire table.
16
+ RESERVATION_LIMIT = 25
17
+
14
18
  belongs_to :job
15
19
  belongs_to :node_record
16
20
 
@@ -28,32 +32,36 @@ module CloudCrowd
28
32
  # distributed to multiple nodes by reserving it first. The algorithm used
29
33
  # should be lock-free.
30
34
  #
31
- # We loop over the WorkUnits reserved by this process and try to match them
32
- # to Nodes that are capable of handling the Action. WorkUnits get removed
33
- # from the availability list when they are successfully sent, and Nodes get
34
- # removed when they are busy or have the action in question disabled.
35
+ # We reserve WorkUnits for this process in chunks of RESERVATION_LIMIT size,
36
+ # and try to match them to Nodes that are capable of handling the Action.
37
+ # WorkUnits get removed from the availability list when they are
38
+ # successfully sent, and Nodes get removed when they are busy or have the
39
+ # action in question disabled.
35
40
  def self.distribute_to_nodes
36
- return unless reservation_number = WorkUnit.reserve_available
37
- work_units = WorkUnit.reserved(reservation_number)
38
- available_nodes = NodeRecord.available
39
- while node = available_nodes.shift and unit = work_units.shift do
40
- if node.actions.include? unit.action
41
- if node.send_work_unit(unit)
42
- available_nodes.push(node) unless node.busy?
43
- next
41
+ begin
42
+ return unless reservation_number = WorkUnit.reserve_available(:limit => RESERVATION_LIMIT)
43
+ work_units = WorkUnit.reserved(reservation_number)
44
+ available_nodes = NodeRecord.available
45
+ while node = available_nodes.shift and unit = work_units.shift do
46
+ if node.actions.include? unit.action
47
+ if node.send_work_unit(unit)
48
+ available_nodes.push(node) unless node.busy?
49
+ next
50
+ end
44
51
  end
52
+ work_units.push(unit)
45
53
  end
46
- work_units.push(unit)
54
+ retry if work_units.empty? && !available_nodes.empty?
55
+ ensure
56
+ WorkUnit.cancel_reservations(reservation_number) if reservation_number
47
57
  end
48
- ensure
49
- WorkUnit.cancel_reservations(reservation_number) if reservation_number
50
58
  end
51
59
 
52
60
  # Reserves all available WorkUnits for this process. Returns false if there
53
61
  # were none available.
54
- def self.reserve_available
62
+ def self.reserve_available(options={})
55
63
  reservation_number = ActiveSupport::SecureRandom.random_number(MAX_RESERVATION)
56
- any = WorkUnit.available.update_all("reservation = #{reservation_number}") > 0
64
+ any = WorkUnit.available.update_all("reservation = #{reservation_number}", nil, options) > 0
57
65
  any && reservation_number
58
66
  end
59
67
 
@@ -3,7 +3,9 @@ require 'test_helper'
3
3
  class ConfigurationTest < Test::Unit::TestCase
4
4
 
5
5
  context "CloudCrowd Configuration" do
6
-
6
+
7
+ setup { CloudCrowd.instance_variable_set("@actions", nil) }
8
+
7
9
  should "have read in config.yml" do
8
10
  assert CloudCrowd.config[:max_workers] == 10
9
11
  assert CloudCrowd.config[:storage] == 'filesystem'
@@ -22,6 +24,17 @@ class ConfigurationTest < Test::Unit::TestCase
22
24
  assert CloudCrowd.actions['process_pdfs'] == ProcessPdfs
23
25
  assert CloudCrowd.actions['graphics_magick'] == GraphicsMagick
24
26
  end
27
+
28
+ should "not find custom actions unless 'actions_path' is set" do
29
+ CloudCrowd.config[:actions_path] = nil
30
+ assert CloudCrowd.actions.keys.length == 4
31
+ end
32
+
33
+ should "find custom actions when 'actions_path' is set" do
34
+ CloudCrowd.config[:actions_path] = "#{CloudCrowd::ROOT}/test/config/actions/custom"
35
+ assert CloudCrowd.actions['echo_action'] == EchoAction
36
+ assert CloudCrowd.actions.keys.length == 5
37
+ end
25
38
 
26
39
  end
27
40
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloud-crowd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Ashkenas
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-19 00:00:00 -04:00
12
+ date: 2009-10-27 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency