abundance 1.0.9 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/abundance.rb +6 -5
- data/lib/garden.rb +1 -1
- data/lib/gardener.rb +7 -4
- data/lib/toolshed.rb +68 -6
- data/test/tc_high_api.rb +147 -0
- data/test/tc_robustness.rb +67 -0
- metadata +4 -2
data/lib/abundance.rb
CHANGED
@@ -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
|
4
|
-
# *
|
4
|
+
# Its characteristics are:
|
5
|
+
# * concurrent
|
5
6
|
# * non-blocking
|
6
7
|
# * simple
|
7
8
|
# * pure ruby
|
8
|
-
# *
|
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
|
-
# * :
|
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={:
|
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
|
|
data/lib/garden.rb
CHANGED
@@ -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
|
data/lib/gardener.rb
CHANGED
@@ -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
|
-
# * :
|
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({:
|
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[:
|
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.
|
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]
|
data/lib/toolshed.rb
CHANGED
@@ -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
|
-
|
71
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/test/tc_high_api.rb
ADDED
@@ -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
|
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-
|
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:
|