group_delegator 0.1.1

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