group_delegator 0.1.1

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.
@@ -0,0 +1,62 @@
1
+ class GroupDelegatorKlasses
2
+ include SourceHelper
3
+ #unload these methods so the proxy object will handle them
4
+ [:to_s,:inspect,:=~,:!~,:===].each do |m|
5
+ undef_method m
6
+ end
7
+
8
+ #source_classes is the container the holds the classes that will be proxied
9
+ class << self
10
+ #unload these methods so the proxy class will handle them
11
+ [:to_s,:inspect,:=~,:!~,:===].each do |m|
12
+ undef_method m
13
+ end
14
+
15
+ attr_accessor :__class_source_group , :__all_class_methods, :__concurrency_model
16
+
17
+ #sets the classes that will be proxied
18
+ def __set_source_classes(classes_to_proxy, concurrency_model = :iterative)
19
+ @__concurrency_model = concurrency_model
20
+ sources_data = SourceHelper.set_sources_data(classes_to_proxy)
21
+ @source_obj_methods = sources_data[:source_methods]
22
+ @sources = sources_data[:source_objs]
23
+ @__all_class_methods = @source_obj_methods.keys
24
+ @__class_source_group = SourceGroup.new(@sources, concurrency_model) if @sources.size > 0
25
+ end
26
+
27
+ def __source_classes
28
+ @sources
29
+ end
30
+
31
+ end #class<<self
32
+
33
+ #initializing class instance variables
34
+ self.__set_source_classes([])
35
+
36
+ def self.method_missing(m, *args, &block)
37
+ if self.__all_class_methods.include? m
38
+ resp = self.__class_source_group.forward(m, *args, &block)
39
+ else
40
+ raise NoMethodError, "#{self.class} can't find the class method #{m} in any of its sources"
41
+ end
42
+ end
43
+
44
+ def initialize(*args)
45
+ #changed self to self.class
46
+ concurrency_model = self.class.__concurrency_model
47
+ raise "No Source Classes set" unless self.class.__source_classes.size > 0
48
+ proxied_objs = self.class.__source_classes.map {|klass| klass.new(*args) }
49
+ sources_data = SourceHelper.set_sources_data(proxied_objs)
50
+ @source_obj_methods = sources_data[:source_methods]
51
+ @source_objects = sources_data[:source_objs]
52
+ @instance_source_group = SourceGroup.new(@source_objects, concurrency_model)
53
+ end
54
+
55
+ def method_missing(m, *args, &block)
56
+ if @source_obj_methods.include? m
57
+ resp = @instance_source_group.forward(m, *args, &block)
58
+ else
59
+ raise NoMethodError, "#{self.class} object can't find the method #{m} in any of its sources"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,161 @@
1
+ require 'thread'
2
+
3
+ #This class is the container for the objects that will receive common method calls
4
+ # It also manages the concurrency model to be used when performing the method calls
5
+ class SourceGroup
6
+ attr_accessor :concurrency_model #:sources, :valid_response_trace, :invalid_response_list
7
+
8
+ #Built-in concurrency models for delegating methods
9
+ ##execute forwarding in an iterative fashion
10
+ IterativeBlock = lambda{ |sources, m, *args, &block|
11
+ all_resps = {}
12
+ sources.each do |source|
13
+ this_resp = {}
14
+ begin
15
+ #collect valid responses
16
+ all_resps[:valid] ||= {}
17
+ all_resps[:valid][source] = source.__send__(m, *args, &block)
18
+ rescue NoMethodError
19
+ #oops we have some invalid responses, collect those too
20
+ all_resps[:invalid] ||= []
21
+ all_resps[:invalid] << source
22
+ end
23
+ end
24
+ all_resps
25
+ }
26
+
27
+ ##If we like speed (with a dash of danger) we can thread the requests rather than iterate
28
+ ThreadedBlock= lambda{ |sources, m, *args, &block|
29
+ all_resps = {}
30
+ threads = []
31
+ sources.each do |source|
32
+ threads << Thread.new(source) do |src|
33
+ Thread.current[:src] = src
34
+ begin
35
+ Thread.current[:resp] = src.__send__(m, *args, &block)
36
+ rescue
37
+ Thread.current[:err] = src
38
+ end
39
+ end
40
+ end
41
+
42
+ threads.each do |t|
43
+ t.join
44
+ src = t[:src] #proxied object
45
+ if t[:resp]
46
+ #valid response
47
+ all_resps[:valid] ||= {}
48
+ all_resps[:valid][src] = t[:resp]
49
+ elsif t[:err]
50
+ #oops error
51
+ all_resps[:invalid] ||= []
52
+ all_resps[:invalid] << t[:err]
53
+ else
54
+ raise "source returned an invalid responseto its thread."\
55
+ "Response thread: #{t} source: #{source.inspect}"
56
+ end
57
+ end
58
+ all_resps
59
+ }
60
+
61
+ ##More speed (with more danger) we can use the first valid response (note the change in t.join)
62
+ ##How to react to the first response, maybe fibers?
63
+ ThreadedFirstResponseBlock= lambda{ |sources, m, *args, &block|
64
+ t0 = Time.now
65
+ first_resp = {}
66
+ source_threads = []
67
+ queue = Queue.new
68
+ sources.each do |source|
69
+ source_threads << Thread.new do
70
+ begin
71
+ queue << { source => source.__send__(m, *args, &block)}
72
+ rescue
73
+ Thread.current[:err] = source #these errored out before a valid entry in queue
74
+ end
75
+ end
76
+ end
77
+
78
+ valid_response = nil
79
+ #limit the time for responses
80
+ check_queue = Thread.new do
81
+ until valid_response do
82
+ sleep 0.01 #don't consume all available resources on a silly event loop
83
+ valid_response = queue.shift #shift not pop in case more than one response in queue
84
+ end
85
+ Thread.current[:q_response] = valid_response
86
+ end
87
+
88
+ #Continue if all source_threads finish, but if check_queue finishes first, continue regardless of source_threads status
89
+ any_thread_running = true
90
+ while any_thread_running do
91
+ sleep 0.01
92
+ any_src_thr_alive = source_threads.inject(false) {|alive, thr| alive || thr.status}
93
+ any_thread_running = check_queue.status && any_src_thr_alive
94
+ end
95
+
96
+ t1 = Time.now
97
+
98
+ if (t1-t0) < 0.1
99
+ sleep 0.05 #give time fot things to stabilize
100
+ end
101
+ #puts "Response from queue: #{check_queue[:q_response].inspect}"
102
+
103
+
104
+ invalid_resps = []
105
+ source_threads.each do |t|
106
+ invalid_resps << t[:err] if t[:err]
107
+ end
108
+ #returning source_threads so that the caller can join them if needed (i.e. ending in a know state)
109
+ #invalid_responses only contains responsed from threads that completed prior to the first valid response
110
+ first_resp = { :valid => valid_response, :invalid => invalid_resps, :threads => source_threads }
111
+ }
112
+
113
+ #
114
+ def initialize(sources, concurrency_model = :iterative)
115
+ @sources = sources
116
+ @concurrency_model = concurrency_model
117
+ end
118
+
119
+ def forward(m, *args, &block)
120
+ forward_custom(@concurrency_model, m, *args, &block)
121
+ end
122
+
123
+ def forward_custom(forward_method, m, *args, &block)
124
+ forward_block = case forward_method
125
+ when :iterative
126
+ IterativeBlock
127
+ when :threaded
128
+ ThreadedBlock
129
+ when :first_response
130
+ ThreadedFirstResponseBlock
131
+ when Proc
132
+ forward_method
133
+ else
134
+ raise "Invalid parameter: #{forward_method.inspect}"
135
+ end
136
+
137
+ @valid_response_trace = {}
138
+ @invalid_response_list = []
139
+ sources = @sources
140
+ if sources.size > 0
141
+ resp = forward_block.call(sources, m, *args, &block)
142
+ @valid_response_trace = resp[:valid]
143
+ else
144
+ raise "No sources assigned"
145
+ end
146
+ @valid_response_trace
147
+ end
148
+
149
+ #just some sugar
150
+ def forward_iterative(m, *args, &block)
151
+ forward_custom(:iterative, m, *args, & block)
152
+ end
153
+
154
+ def forward_threaded(m, *args, &block)
155
+ forward_custom(:threaded, m, *args, & block)
156
+ end
157
+
158
+ def forward_first_resp(m, *args, &block)
159
+ forward_custom(:first_response, m, *args, &block)
160
+ end
161
+ end
@@ -0,0 +1,17 @@
1
+ module SourceHelper
2
+ def self.set_sources_data(proxied_objs)
3
+ source_obj_methods = {} #map of all methods to the objects that use them
4
+ proxied_objs.each do |proxied_obj|
5
+ proxied_obj.methods.each do |proxy_method|
6
+ source_obj_methods[proxy_method] ||= [proxied_obj]
7
+ source_obj_methods[proxy_method] << proxied_obj
8
+ end
9
+ end
10
+ {:source_methods => source_obj_methods, :source_objs => proxied_objs}
11
+ end
12
+
13
+ def __set_sources_data(proxied_objs)
14
+ raise "No source instances set" unless proxied_objs.size > 0
15
+ SourceHelper.set_sources_data(proxied_objs)
16
+ end
17
+ end
@@ -0,0 +1,150 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'benchmark'
3
+
4
+ #classes for testing
5
+ class A
6
+ def common_method
7
+ :a
8
+ end
9
+ def some_method
10
+ :aa
11
+ end
12
+ def a_method
13
+ :aaa
14
+ end
15
+ end
16
+
17
+ class B
18
+ def common_method
19
+ :b
20
+ end
21
+ end
22
+
23
+ class C
24
+ def common_method
25
+ :c
26
+ end
27
+ def some_method
28
+ :cc
29
+ end
30
+ end
31
+
32
+ shared_examples_for "instance delegator - entire collection" do
33
+ it "passes methods to the underlying instances of the group" do
34
+ group_obj.common_method.should == {@a_obj => :a, @b_obj => :b, @c_obj => :c}
35
+ end
36
+ end
37
+
38
+ shared_examples_for "instance delegator - first response" do
39
+ it "passes methods to the first responing instance" do
40
+ expected_result_set = {@a_obj=>:a, @b_obj=>:b, @c_obj=>:c}
41
+ result = group_obj.common_method
42
+ result_key = result.keys.first
43
+ expected_result_set.keys.should include result_key
44
+ result_value = result.values.first
45
+ expected_result_set.values.should include result_value
46
+ end
47
+ end
48
+
49
+ describe "delegating to a group of instance objects" do
50
+ before(:each) do
51
+ @a_obj = A.new; @b_obj = B.new; @c_obj = C.new
52
+ end
53
+
54
+ describe "default concurrency model (iterative)" do
55
+ it_should_behave_like "instance delegator - entire collection" do
56
+ let(:group_obj) { GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj]) }
57
+ end
58
+ end
59
+
60
+ describe "iterative concurrency model" do
61
+ it_should_behave_like "instance delegator - entire collection" do
62
+ let(:group_obj) { GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :iterative) }
63
+ end
64
+ end
65
+
66
+ describe "threaded concurrency model" do
67
+ it_should_behave_like "instance delegator - entire collection" do
68
+ let(:group_obj) { GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :threaded) }
69
+ end
70
+ end
71
+
72
+ describe "first response concurrency model" do
73
+ it_should_behave_like "instance delegator - first response" do
74
+ let(:group_obj) { GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :first_response) }
75
+ end
76
+ end
77
+ end
78
+
79
+
80
+
81
+ class BenchA
82
+ def common_method
83
+ sleep 0.3
84
+ :a
85
+ end
86
+ end
87
+
88
+ class BenchB
89
+ def common_method
90
+ sleep 0.2
91
+ :b
92
+ end
93
+ end
94
+
95
+ class BenchC
96
+ def common_method
97
+ sleep 0.1
98
+ :c
99
+ end
100
+ end
101
+
102
+ describe "benchmarks" do
103
+ before(:all) do
104
+ @execution_times = {}
105
+ end
106
+
107
+ after(:all) do
108
+ p @execution_times
109
+ end
110
+
111
+ describe "iterative" do
112
+ before(:each) do
113
+ @a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
114
+ @group_obj = GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :iterative)
115
+ end
116
+
117
+ it "executes" do
118
+ @execution_times[:iterative] = Benchmark.realtime { @group_obj.common_method }
119
+ end
120
+ end
121
+
122
+ describe "threaded" do
123
+ before(:each) do
124
+ @a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
125
+ @group_obj = GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :threaded)
126
+ end
127
+
128
+ it "executes" do
129
+ @execution_times[:threaded] = Benchmark.realtime { @group_obj.common_method }
130
+ end
131
+ end
132
+
133
+ describe "first response" do
134
+ before(:each) do
135
+ @a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
136
+ @group_obj = GroupDelegatorInstances.new([@a_obj, @b_obj, @c_obj], :first_response)
137
+ end
138
+
139
+ it "executes" do
140
+ @execution_times[:first_response] = Benchmark.realtime { @group_obj.common_method }
141
+ end
142
+ end
143
+
144
+ describe "results" do
145
+ it "checks the order of results" do
146
+ @execution_times[:first_response].should < @execution_times[:threaded]
147
+ @execution_times[:threaded].should < @execution_times[:iterative]
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,270 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'benchmark'
3
+
4
+ #classes for testing
5
+ class A
6
+ attr_accessor :iv
7
+ def self.klass_method
8
+ :A
9
+ end
10
+ def self.a_klass_method
11
+ :AAA
12
+ end
13
+ def initialize(params)
14
+ @iv = params
15
+ end
16
+ def common_method
17
+ :a
18
+ end
19
+ def a_method
20
+ :aaa
21
+ end
22
+ end
23
+
24
+ class B
25
+ attr_accessor :iv
26
+ def self.klass_method
27
+ :B
28
+ end
29
+ def initialize(params)
30
+ @iv = params
31
+ end
32
+ def common_method
33
+ :b
34
+ end
35
+ end
36
+
37
+ class C
38
+ attr_accessor :iv
39
+ def self.klass_method
40
+ :C
41
+ end
42
+ def initialize(params)
43
+ @iv = params
44
+ end
45
+ def common_method
46
+ :c
47
+ end
48
+ end
49
+
50
+
51
+ shared_examples_for "class delegator - entire collection" do
52
+ it "passes methods to the source classes in the group" do
53
+ group_klass.klass_method.should == {A=>:A, B=>:B, C=>:C}
54
+ end
55
+ end
56
+
57
+ shared_examples_for "class delegator - first response" do
58
+ it "passes methods to the source classes in the group" do
59
+ expected_result_set = {A=>:A, B=>:B, C=>:C}
60
+ result = group_klass.klass_method
61
+ result_key = result.keys.first
62
+ expected_result_set.keys.should include result_key
63
+ result_value = result.values.first
64
+ expected_result_set.values.should include result_value
65
+ end
66
+ end
67
+
68
+ shared_examples_for "class delegator - initializing objects" do
69
+ it "should initialize a group of objects based on the group of classes" do
70
+ source_classes = [ A, B, C ]
71
+ objs_iv = group_obj.iv
72
+ objs_iv.each_with_index do |obj_inst_var, i|
73
+ obj = obj_inst_var[0]
74
+ inst_var = obj_inst_var[1]
75
+ obj.class.should == source_classes[i]
76
+ inst_var.should == :some_init_params
77
+ end
78
+ end
79
+ end
80
+
81
+ shared_examples_for "instance delegator - entire collection" do
82
+ it "passes methods to the underlying instances of the group" do
83
+ group_obj.common_method.values == [:a, :b, :c]
84
+ end
85
+ end
86
+
87
+ shared_examples_for "instance delegator - first response" do
88
+ it "passes methods to the first responing instance" do
89
+ expected_result_set = [:a, :b, :c]
90
+ result = group_obj.common_method
91
+ result_value = result.values.first
92
+ expected_result_set.should include result_value
93
+ end
94
+ end
95
+
96
+ describe "delegating to a group of classes" do
97
+ before(:each) do
98
+
99
+ end
100
+
101
+ describe "default concurrency model (iterative)" do
102
+ it_should_behave_like "class delegator - entire collection" do
103
+ #One line inheritance to prevent clobbering GroupDelegatorKlasses class inst var
104
+ DefaultGDK = Class.new(GroupDelegatorKlasses)
105
+ DefaultGDK.__set_source_classes( [A, B, C] )
106
+ let(:group_klass) { DefaultGDK }
107
+ end
108
+ end
109
+
110
+ describe "iterative concurrency model" do
111
+ it_should_behave_like "class delegator - entire collection" do
112
+ IterGDK = Class.new(GroupDelegatorKlasses)
113
+ IterGDK.__set_source_classes( [A, B, C], :iterative )
114
+ let(:group_klass) { IterGDK }
115
+ end
116
+ end
117
+
118
+ describe "threaded concurrency model" do
119
+ it_should_behave_like "class delegator - entire collection" do
120
+ ThreadGDK = Class.new(GroupDelegatorKlasses)
121
+ ThreadGDK.__set_source_classes( [A, B, C], :threaded )
122
+ let(:group_klass) { ThreadGDK }
123
+ end
124
+ end
125
+
126
+ describe "first response concurrency model" do
127
+ it_should_behave_like "class delegator - first response" do
128
+ FirstRespGDK = Class.new(GroupDelegatorKlasses)
129
+ FirstRespGDK.__set_source_classes( [A, B, C], :first_response )
130
+ let(:group_klass) { FirstRespGDK }
131
+ end
132
+ end
133
+
134
+ describe "default group initialization" do
135
+ it_should_behave_like "class delegator - initializing objects" do
136
+ DefObjGDK = Class.new(GroupDelegatorKlasses)
137
+ DefObjGDK.__set_source_classes( [A, B, C] )
138
+ let(:group_obj) { DefObjGDK.new(:some_init_params) }
139
+ end
140
+ end
141
+
142
+ describe "group initialization, iterative" do
143
+ it_should_behave_like "class delegator - initializing objects" do
144
+ IterObjGDK = Class.new(GroupDelegatorKlasses)
145
+ IterObjGDK.__set_source_classes( [A, B, C], :iterative )
146
+ let(:group_obj) { IterObjGDK.new(:some_init_params) }
147
+ end
148
+ end
149
+
150
+ describe "group initialization, threaded" do
151
+ it_should_behave_like "class delegator - initializing objects" do
152
+ ThreadObjGDK = Class.new(GroupDelegatorKlasses)
153
+ ThreadObjGDK.__set_source_classes( [A, B, C], :iterative )
154
+ let(:group_obj) { ThreadObjGDK.new(:some_init_params) }
155
+ end
156
+ end
157
+
158
+ describe "group initialization, first response" do
159
+ it_should_behave_like "instance delegator - first response" do
160
+ FirstRespObjGDK = Class.new(GroupDelegatorKlasses)
161
+ FirstRespObjGDK.__set_source_classes( [A, B, C], :first_response )
162
+ let(:group_obj) { FirstRespObjGDK.new(:some_init_params) }
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "newly created instances should behave as grouped delegates" do
168
+ describe "default concurrency model (iterative)" do
169
+ it_should_behave_like "instance delegator - entire collection" do
170
+ let(:group_obj) { DefObjGDK.new(:some_other_params) }
171
+ end
172
+ end
173
+
174
+ describe "iterative concurrency model" do
175
+ it_should_behave_like "instance delegator - entire collection" do
176
+ let(:group_obj) { IterObjGDK.new(:some_other_params) }
177
+ end
178
+ end
179
+
180
+ describe "threaded concurrency model" do
181
+ it_should_behave_like "instance delegator - entire collection" do
182
+ let(:group_obj) { ThreadObjGDK.new(:some_other_params) }
183
+ end
184
+ end
185
+
186
+ describe "first response concurrency model" do
187
+ it_should_behave_like "instance delegator - first response" do
188
+ let(:group_obj) { FirstRespObjGDK.new(:some_other_params) }
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ class BenchA
195
+ def common_method
196
+ sleep 0.3
197
+ :a
198
+ end
199
+ end
200
+
201
+ class BenchB
202
+ def common_method
203
+ sleep 0.2
204
+ :b
205
+ end
206
+ end
207
+
208
+ class BenchC
209
+ def common_method
210
+ sleep 0.1
211
+ :c
212
+ end
213
+ end
214
+
215
+ describe "benchmarks" do
216
+ before(:all) do
217
+ @execution_times = {}
218
+ end
219
+
220
+ after(:all) do
221
+ p @execution_times
222
+ end
223
+
224
+ describe "iterative" do
225
+ before(:each) do
226
+ #@a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
227
+ BenchObjGDK = Class.new(GroupDelegatorKlasses)
228
+ BenchObjGDK.__set_source_classes( [BenchA, BenchB, BenchC], :iterative )
229
+ @bench_obj = BenchObjGDK.new
230
+ end
231
+
232
+ it "executes" do
233
+ @execution_times[:iterative] = Benchmark.realtime { @bench_obj.common_method }
234
+ end
235
+ end
236
+
237
+ describe "threaded" do
238
+ before(:each) do
239
+ #@a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
240
+ BenchObjGDK = Class.new(GroupDelegatorKlasses)
241
+ BenchObjGDK.__set_source_classes( [BenchA, BenchB, BenchC], :threaded )
242
+ @bench_obj = BenchObjGDK.new
243
+ end
244
+
245
+ it "executes" do
246
+ @execution_times[:threaded] = Benchmark.realtime { @bench_obj.common_method }
247
+ end
248
+ end
249
+
250
+ describe "first response" do
251
+ before(:each) do
252
+ #@a_obj = BenchA.new; @b_obj = BenchB.new; @c_obj = BenchC.new
253
+ BenchObjGDK = Class.new(GroupDelegatorKlasses)
254
+ BenchObjGDK.__set_source_classes( [BenchA, BenchB, BenchC], :first_response )
255
+ @bench_obj = BenchObjGDK.new
256
+ end
257
+
258
+ it "executes" do
259
+ @execution_times[:first_response] = Benchmark.realtime { @bench_obj.common_method }
260
+ end
261
+ end
262
+
263
+
264
+ describe "results" do
265
+ it "checks the order of results" do
266
+ @execution_times[:first_response].should < @execution_times[:threaded]
267
+ @execution_times[:threaded].should < @execution_times[:iterative]
268
+ end
269
+ end
270
+ end