abundance 1.0.9 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: