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.
- 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:
|