abundance 1.0.9 → 1.1.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.
@@ -1,11 +1,12 @@
1
1
  # This class provides a mean to parallelize the execution of your program processes.
2
+ # It is a process queue, named the Garden, with concurrent workers, named Rows, all this getting orchestrated by a Gardener.
2
3
  #
3
- # Its caracteristics are:
4
- # * concurent
4
+ # Its characteristics are:
5
+ # * concurrent
5
6
  # * non-blocking
6
7
  # * simple
7
8
  # * pure ruby
8
- # * no dependency installation
9
+ # * serialization friendly
9
10
  #
10
11
  # It:
11
12
  # * scales to multi core
@@ -50,7 +51,7 @@ class Abundance
50
51
  # with its garden supplied as a block. The invocation block must include
51
52
  # the +grow+ class method and a preceeding optional initialisation section that may include and +init_status+ return message.
52
53
  # === Parameters
53
- # * :seed_size = allowed seed size in bytes
54
+ # * :wheelbarrow = the socket size for the garden communication packets, in bytes, range from 1024 to 8192, defaults to 8192
54
55
  # * :rows = garden rows number, the number of concurent threads
55
56
  # * :init_timeout = allow to pause execution to allow for larger gardens to initialize
56
57
  # === Example
@@ -81,7 +82,7 @@ class Abundance
81
82
  #
82
83
  # gardener.close
83
84
 
84
- def Abundance.gardener(options={:seed_size => 8192, :rows => 2, :init_timeout => 2},&gardener_block)
85
+ def Abundance.gardener(options={:wheelbarrow => 8192, :rows => 2, :init_timeout => 2},&gardener_block)
85
86
  return Gardener.new(options,gardener_block)
86
87
  end
87
88
 
@@ -220,8 +220,8 @@ class Garden
220
220
  @pids = []
221
221
  rows.times do
222
222
  row_port = Toolshed.available_port
223
- @socket_client_perm = Toolshed.socket_client_perm
224
223
  @pids << fork do
224
+ @socket_client_perm = Toolshed.socket_client_perm
225
225
  @seed_all = false
226
226
  @socket_server = Toolshed.socket_server(row_port)
227
227
  t1 = Thread.new do
@@ -23,15 +23,18 @@ class Gardener
23
23
  # As part of the Abundance lib, Gardener is not initialized directly,
24
24
  # but rather through Abundance.gardener.
25
25
  # === Parameters
26
- # * :seed_size = allowed seed size in bytes
26
+ # * :wheelbarrow = the socket size for the garden communication packets, in bytes, range from 1024 to 8192, defaults to 8192
27
27
  # * :rows = garden rows number, the number of concurent threads
28
28
  # * :init_timeout = allow to pause execution to allow for larger gardens to initialize
29
29
  # === Example
30
- # gardener = Gardener.new({:seed_size => 1024, :rows => 6, :init_timeout}) { your_special_garden function }
30
+ # gardener = Gardener.new({:wheelbarrow => 1024, :rows => 6, :init_timeout}) { your_special_garden function }
31
31
  #
32
32
 
33
33
  def initialize(options,gardener_block)
34
- Toolshed::block_size = options[:seed_size]
34
+ Toolshed::block_size = if options[:wheelbarrow].nil? || options[:wheelbarrow] > 8192 then 8192
35
+ elsif options[:wheelbarrow] < 1024 then 1024
36
+ else options[:wheelbarrow]
37
+ end
35
38
  Toolshed::garden_port = Toolshed.available_port
36
39
 
37
40
  @garden = Garden.new
@@ -69,7 +72,7 @@ class Gardener
69
72
  # === Parameter
70
73
  # * _command_ = a ruby expression or object
71
74
  # === Example
72
- # result = gardener.seed("pref local") # => [{:success=>true, :message=>["row pref changed to local"], :seed=>"pref local", :pid=>14915},
75
+ # result = gardener.seed_all("pref local") # => [{:success=>true, :message=>["row pref changed to local"], :seed=>"pref local", :pid=>14915},
73
76
  # {:success=>true, :message=>["row pref changed to local"], :seed=>"pref local", :pid=>14913}]
74
77
  def seed_all(command)
75
78
  seed = [@garden_rows.pids.size, command]
@@ -14,7 +14,9 @@ module Toolshed
14
14
  require 'socket'
15
15
  UDP_HOST = 'localhost'
16
16
  @@start_port = 50000
17
+ @@sessions = []
17
18
 
19
+ # The Toolshed.available_port method scans for a an available UDP port and returns its value.
18
20
  def Toolshed.available_port
19
21
  port = @@start_port + 1
20
22
  catch :scan_port do
@@ -34,62 +36,122 @@ module Toolshed
34
36
  return port
35
37
  end
36
38
 
39
+ # The Toolshed.socket_client_perm method creates a client socket permanently connected to the main server socket. Returns the client socket object.
37
40
  def Toolshed.socket_client_perm
38
41
  socket = UDPSocket.new
39
42
  socket.connect(UDP_HOST,@@garden_port)
40
43
  return socket
41
44
  end
42
45
 
46
+ # The Toolshed.socket_client_temp method creates a client socket available for changing connections to multiple socket servers. Returns the client socket object.
43
47
  def Toolshed.socket_client_temp
44
48
  UDPSocket.new
45
49
  end
46
50
 
51
+ # The Toolshed.socket_server method creates a permanent main server socket. Returns the server socket object.
47
52
  def Toolshed.socket_server(port)
48
53
  socket_server = UDPSocket.new
49
54
  socket_server.bind(nil,port)
50
55
  return socket_server
51
56
  end
52
57
 
58
+ # The Toolshed::block_size= method sets the UDP block size for socket operations.
53
59
  def Toolshed::block_size=(block_size)
54
60
  @@block_size = block_size
55
61
  end
56
62
 
63
+ # The Toolshed::block_size method gets and returns the UDP block size for socket operations.
57
64
  def Toolshed::block_size
58
65
  @@block_size
59
66
  end
60
67
 
68
+ # The Toolshed::garden_port= sets the UDP socket port for the garden server.
61
69
  def Toolshed::garden_port=(garden_port)
62
70
  @@garden_port = garden_port
63
71
  end
64
72
 
73
+ # The Toolshed::garden_port gets the UDP socket port of the garden server.
65
74
  def Toolshed::garden_port
66
75
  @@garden_port
67
76
  end
68
77
 
78
+ # The +socket_client_perm_duplex+ method is used as the main Row loop send/receive method and for all gardener's send/receive
69
79
  def socket_client_perm_duplex(command,data)
70
- @socket_client_perm.send(Marshal.dump([command,data]),0)
71
- recv_block,address = @socket_client_perm.recvfrom(@@block_size)
80
+ block_splitter([command,data]) do |block|
81
+ @socket_client_perm.send(block,0)
82
+ end
83
+ recv_block,address = block_filter { @socket_client_perm.recvfrom(@@block_size) }
72
84
  return Marshal.load(recv_block)
73
85
  end
74
86
 
75
87
  def socket_client_perm_send(command,data)
76
- @socket_client_perm.send(Marshal.dump([command,data]),0)
88
+ block_splitter([command,data]) do |block|
89
+ @socket_client_perm.send(block,0)
90
+ end
77
91
  end
78
92
 
93
+ # The +socket_client_temp+ method is used by the Garden for connecting with the Row idle socket for quick messages.
79
94
  def socket_client_temp(command,data,port)
80
95
  @socket_client_temp.connect(UDP_HOST,port)
81
- @socket_client_temp.send(Marshal.dump([command,data]),0)
96
+ block_splitter([command,data]) do |block|
97
+ @socket_client_temp.send(block,0)
98
+ end
82
99
  end
83
100
 
101
+ # The +socket_server_recv+ method is used by the Garden permanent socket and by the Row idle socket to wait for incomming messages.
84
102
  def socket_server_recv
85
- block,address = @socket_server.recvfrom(@@block_size)
103
+ block,address = block_filter { @socket_server.recvfrom(@@block_size) }
86
104
  clientport = address[1]; clientname = address[2]; clientaddr = address[3]
87
105
  command, data = Marshal.load(block)
88
106
  return command, data, clientport, clientname, clientaddr
89
107
  end
90
108
 
109
+ # The +socket_server_send+ method is used by the Garden to answer back to socket clients.
91
110
  def socket_server_send(command,data,clientaddr,clientport)
92
- @socket_server.send(Marshal.dump([command,data]), 0, clientaddr, clientport)
111
+ block_splitter([command,data]) do |block|
112
+ @socket_server.send(block, 0, clientaddr, clientport)
113
+ end
114
+ end
115
+
116
+ private
117
+ # The +block_splitter+ method is used internally by the Toolshed method to split message into acceptable UDP block size. Its operating as a block method, sending a message chunk on each iteration.
118
+ def block_splitter(data)
119
+ data_string = Marshal.dump(data)
120
+ if data_string.size >= @@block_size
121
+ parts = data_string.size / (@@block_size - @@block_size/32) + 1
122
+ yield Marshal.dump([:block_head,Process.pid,parts])
123
+ parts.times do |num|
124
+ part = [data_string[0,@@block_size - @@block_size/32]]; data_string[0,@@block_size - @@block_size/32] = ''
125
+ yield Marshal.dump([:block_part,Process.pid,num, part])
126
+ end
127
+ else
128
+ yield data_string
129
+ end
130
+ end
131
+
132
+ # The +block_filter+ method is used internally by the Toolshed method to filter splitted messages blocks, buffering them until the full message can be reconstructed.
133
+ def block_filter
134
+ loop do
135
+ block,address = yield
136
+ block_array = Marshal.load(block)
137
+ if block_array[0] == :block_head
138
+ @@sessions[block_array[1]] = {} if @@sessions[block_array[1]].nil?
139
+ @@sessions[block_array[1]][:size] = block_array[2]
140
+ @@sessions[block_array[1]][:address] = address
141
+ elsif block_array[0] == :block_part
142
+ @@sessions[block_array[1]] = {} if @@sessions[block_array[1]].nil?
143
+ @@sessions[block_array[1]][:data] = [] if @@sessions[block_array[1]][:data].nil?
144
+ @@sessions[block_array[1]][:data][block_array[2]] = block_array[3]
145
+ else
146
+ return block,address
147
+ end
148
+ if ! @@sessions[block_array[1]].nil? && ! @@sessions[block_array[1]][:data].nil? && @@sessions[block_array[1]][:data].size == @@sessions[block_array[1]][:size]
149
+ block = @@sessions[block_array[1]][:data].join
150
+ address = @@sessions[block_array[1]][:address]
151
+ @@sessions[block_array[1]] = nil
152
+ return block,address
153
+ end
154
+ end
93
155
  end
94
156
 
95
157
  end
@@ -0,0 +1,147 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+ require 'test/unit'
3
+ require 'abundance'
4
+
5
+ class TestHighAPI < Test::Unit::TestCase
6
+
7
+ def test_abundance_monothread
8
+ @rows = 1
9
+ set_gardener
10
+ reality_check
11
+ end
12
+
13
+ def test_abundance_quadthread
14
+ @rows = 4
15
+ set_gardener
16
+ reality_check
17
+ end
18
+
19
+ def test_abundance_hexthread
20
+ @rows = 16
21
+ set_gardener
22
+ reality_check
23
+ end
24
+
25
+ def teardown
26
+ final = @g.close
27
+ assert_kind_of(Hash,final)
28
+ assert_equal(3,final.size)
29
+ assert_not_nil(final[:seeds])
30
+ assert_not_nil(final[:sprouts])
31
+ assert_not_nil(final[:crops])
32
+ end
33
+
34
+ private
35
+
36
+ def set_gardener
37
+ @g = Abundance.gardener(:wheelbarrow => 8192, :rows => @rows, :init_timeout => 3) do
38
+ Abundance.init_status(true,Process.pid)
39
+ Abundance.grow do |seed|
40
+ seed.crop(true, "gardener: #{seed.sprout}")
41
+ end
42
+ end
43
+ end
44
+
45
+ def reality_check
46
+ assert_instance_of(Gardener,@g)
47
+
48
+ check_init
49
+ check_seed_harvest # leaves no crops in the queue
50
+ check_full_harvest # also leaves no crops in the queue
51
+ check_deep_harvest # may leave crap behind, so needs to come last of the harvest
52
+ check_growth
53
+ check_seed_all
54
+
55
+ end
56
+
57
+ def check_init
58
+ @g.init_status.each do |init|
59
+ assert_not_nil(init[:message])
60
+ assert_not_nil(init[:success])
61
+ assert_not_nil(init[:pid])
62
+
63
+ assert_not_equal(Process.pid,init[:message])
64
+ assert_equal(init[:message],init[:pid])
65
+ end
66
+ end
67
+
68
+ def check_seed_harvest
69
+ id = @g.seed(Process.pid)
70
+ assert_kind_of(Integer,id)
71
+
72
+ answer = @g.harvest(id)
73
+ assert_kind_of(Hash,answer)
74
+ assert_equal(Process.pid,answer[:seed])
75
+ assert_equal(id,answer[:id])
76
+ assert_equal(true,answer[:success])
77
+ assert_equal("gardener: #{Process.pid}",answer[:message])
78
+ end
79
+
80
+ def check_full_harvest
81
+ queue_items = {}
82
+ 25.times do |num|
83
+ queue_items[num] = @g.seed(num)
84
+ end
85
+ full_crop = @g.harvest(:full_crop)
86
+ assert_equal(25,full_crop.size)
87
+ results = []
88
+ queue_items.each do |num,id|
89
+ success = false
90
+ full_crop.each do |crop|
91
+ success = true if crop[:id] == id && crop[:seed] == num && crop[:message] == "gardener: #{num}"
92
+ end
93
+ results << success
94
+ end
95
+ assert(results[0] == true && results.uniq.size == 1)
96
+ end
97
+
98
+ def check_growth
99
+ progress = @g.growth(:progress)
100
+ assert_kind_of(String,progress)
101
+ assert(progress.to_f >= 0 && progress.to_f <= 1)
102
+
103
+ seeds_growth = @g.growth(:seed)
104
+ assert_kind_of(Integer,seeds_growth)
105
+ sprouts_growth = @g.growth(:sprout)
106
+ assert_kind_of(Integer,sprouts_growth)
107
+ crops_growth = @g.growth(:crop)
108
+ assert_kind_of(Integer,crops_growth)
109
+ end
110
+
111
+ def check_deep_harvest
112
+ queue_items = {}
113
+ 25.times do |num|
114
+ queue_items[num] = @g.seed(num)
115
+ end
116
+
117
+ all = @g.harvest(:all)
118
+ assert_kind_of(Hash,all)
119
+ assert_equal(25,all[:seeds].size + all[:sprouts].size + all[:crops].size)
120
+ seeds_harvest = @g.harvest(:seed); assert_kind_of(Array,seeds_harvest)
121
+ sprouts_harvest = @g.harvest(:sprout); assert_kind_of(Array,sprouts_harvest)
122
+ crops_harvest = @g.harvest(:crop); assert_kind_of(Array,crops_harvest)
123
+ results = []
124
+ queue_items.each do |num,id|
125
+ success = false
126
+ seeds_harvest.each do |seed|
127
+ success = true if seed[:id] == id && seed[:seed] == num
128
+ end
129
+ sprouts_harvest.each do |sprout|
130
+ success = true if sprout[:id] == id && sprout[:seed] == num
131
+ end
132
+ crops_harvest.each do |crop|
133
+ success = true if crop[:id] == id && crop[:seed] == num && crop[:message] == "gardener: #{num}"
134
+ end
135
+ results << success
136
+ end
137
+ assert(results[0] == true && results.uniq.size == 1)
138
+ end
139
+
140
+ def check_seed_all
141
+ all = @g.seed_all("all")
142
+ assert_equal(@rows,all.size)
143
+ all.map! { |seed| seed[:message] == "gardener: all" }
144
+ assert( all.uniq.size == 1 && all[0] == true )
145
+ end
146
+
147
+ end
@@ -0,0 +1,67 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), "..", "lib")
2
+ require 'test/unit'
3
+ require 'abundance'
4
+
5
+ class TestHighAPI < Test::Unit::TestCase
6
+
7
+ def test_abundance_robust
8
+ @seed_size = 8192
9
+ @rows = 8
10
+ @init_timeout = 3
11
+ hyper_gardener
12
+ seed_3000
13
+ seed_1000x10
14
+ end
15
+
16
+ def teardown
17
+ @g.close
18
+ end
19
+
20
+ private
21
+
22
+ def seed_3000
23
+ seed = {:jo => 'ker', :lo => 'ver'}
24
+ 3000.times do
25
+ @g.seed(seed)
26
+ end
27
+ crop = @g.harvest(:full_crop)
28
+ assert_crop(crop)
29
+ end
30
+
31
+ def seed_1000x10
32
+ (1..1000).each do |num1|
33
+ (1..10).each do |num2|
34
+ @g.seed([num1,num2])
35
+ end
36
+ end
37
+ crop = @g.harvest(:full_crop)
38
+ assert_crop(crop)
39
+ end
40
+
41
+ def assert_crop(crop)
42
+ assert_kind_of(Array,crop)
43
+ assert_kind_of(Hash,crop[0])
44
+ assert( ! crop[0][:success].nil? && (crop[0][:success] == true || crop[0][:success] == false))
45
+ assert(crop[0][:seed].class == Array || crop[0][:seed].class == Hash)
46
+ assert_kind_of(String,crop[0][:message])
47
+ assert_kind_of(Numeric,crop[0][:id])
48
+ end
49
+
50
+ def hyper_gardener
51
+ @g = Abundance.gardener(:wheelbarrow => @seed_size, :rows => @rows, :init_timeout => @init_timeout) do
52
+ Abundance.init_status(true,Process.pid)
53
+ Abundance.grow do |seed|
54
+ sprout = seed.sprout
55
+ if sprout.is_a?(Hash)
56
+ seed.crop(true, "gardener: #{sprout[:jo]} - #{sprout[:lo]}")
57
+ elsif sprout.is_a?(Array)
58
+ result = sprout[0] ** sprout[1]
59
+ seed.crop(true, "gardener: #{result.to_s}")
60
+ else
61
+ seed.crop(true, "????????????????")
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abundance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis-Philippe Perron
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-15 00:00:00 -05:00
12
+ date: 2008-12-23 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -27,6 +27,8 @@ files:
27
27
  - lib/gardener.rb
28
28
  - lib/seed.rb
29
29
  - lib/toolshed.rb
30
+ - test/tc_high_api.rb
31
+ - test/tc_robustness.rb
30
32
  has_rdoc: true
31
33
  homepage: http://abundance.rubyforge.org/
32
34
  post_install_message: