eventable 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -4,7 +4,7 @@ An incredibly simple way to add events to your classes.
4
4
 
5
5
  ##Description##
6
6
 
7
- Provides an easy to use and understand event model. Other systems did way too much for my needs. I didn't need to monitor network IO ports, I didn't want a central loop that polled IO, I just wanted a simple way to add real, non-polled events to a class and to register other classes to listen for those events.
7
+ Provides an easy to use and understand event model. Other systems did way too much for my needs: I didn't need to monitor network IO ports, I didn't want a central loop that polled IO, I just wanted a simple way to add real, non-polled events to a class and to register other classes to listen for those events.
8
8
 
9
9
  If you want a simple way to add events to your classes without a bunch of other unrelated IO stuff this is the solution for you. (If you want to monitor IO events check out EventMachine)
10
10
 
@@ -16,6 +16,10 @@ Eventable will even automatically remove registered listeners when they get garb
16
16
 
17
17
  Eventable also allows for more fine-grain control than Observable. You can register for specific events instead of just saying, "Hey, let me know when anything changes."
18
18
 
19
+ ##Concurrency considerations##
20
+
21
+ Events and threads do not scale well past a certain point but that's OK; they aren't meant to. They are meant for fast, simple communication beteen processes not large distributed processes like serving websites on massive server farms. If you need a solution that scales well to large distributed systems check out the Actor concurrency model.
22
+
19
23
  ##Install##
20
24
 
21
25
  `$ gem install eventable`
@@ -165,6 +169,17 @@ This example shows you how you might actually use it in a multi-threaded environ
165
169
 
166
170
  ##Version History##
167
171
 
172
+ **2011.06.28**
173
+ Ver: 0.1.3
174
+
175
+ Updates:
176
+
177
+ Callbacks are now threaded:
178
+ This patches one of the last concurrency issues; if a callback takes a long time or hangs it won't affect any other callbacks or events that need to fire.
179
+
180
+ It's your responsiblity to make sure your callback works, as long as it does the callback thread will go out of scope (unless you retain it) and everyone is happy.
181
+
182
+ **2011.06.17**
168
183
  Ver: 0.1.2
169
184
 
170
185
  Design updates/fixes:
@@ -2,7 +2,8 @@
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
  # Kludge for older RubyGems not handling unparsable dates gracefully
4
4
  # (psych 1.2.0 gem wouldn't build on test system so we're stuck with syck)
5
- YAML::ENGINE.yamler = 'syck'
5
+ YAML::ENGINE.yamler = 'syck'
6
+
6
7
  require "rubygems"
7
8
  require "eventable/version"
8
9
 
@@ -14,8 +15,12 @@ Gem::Specification.new do |s|
14
15
  s.homepage = "http://mikbe.tk/projects#eventable"
15
16
  s.summary = %q{An incredibly simple and easy to use event mixin module.}
16
17
  s.description = %q{Provides an easy to use and understand event model. If you want a simple, light-weight way to add events to your classes this is the solution for you.}
18
+ s.license = 'MIT'
19
+
20
+ s.required_ruby_version = ">= 1.9.2"
17
21
 
18
22
  s.add_development_dependency('rspec', "~>2.6")
23
+ s.add_development_dependency('bundler', "~>1.0")
19
24
 
20
25
  s.files = `git ls-files`.split("\n")
21
26
  s.test_files = `git ls-files -- {spec}/*`.split("\n")
@@ -0,0 +1,56 @@
1
+ [1] did some stuff, sleeping
2
+ [1, 932]: do_somestuff
3
+ firing :stuff_happens
4
+ [1] stuff_happened callback: 824
5
+ firing :other_stuff_happens
6
+ [n/a] same_stuff_happened callback: n/a
7
+ [1, 932]: do_somestuff
8
+ firing :stuff_happens
9
+ [1] stuff_happened callback: 407
10
+ [1, 932]: do_somestuff
11
+ firing :stuff_happens
12
+ [1] stuff_happened callback: 841
13
+ firing :other_stuff_happens
14
+ [n/a] same_stuff_happened callback: n/a
15
+ [1, 932]: do_somestuff
16
+ firing :stuff_happens
17
+ [1] stuff_happened callback: 779
18
+ [1] slept
19
+ [2] did some stuff, sleeping
20
+ firing :other_stuff_happens
21
+ [n/a] same_stuff_happened callback: n/a
22
+ [1, 932]: do_somestuff
23
+ firing :stuff_happens
24
+ [1] stuff_happened callback: 252
25
+ [2, 316]: do_somestuff
26
+ firing :stuff_happens
27
+ [2] stuff_happened callback: 174
28
+ firing :other_stuff_happens
29
+ [n/a] same_stuff_happened callback: n/a
30
+ [2, 316]: do_somestuff
31
+ firing :stuff_happens
32
+ [2] stuff_happened callback: 193
33
+ [1, 932]: do_somestuff
34
+ firing :stuff_happens
35
+ [1] stuff_happened callback: 535
36
+ [2] slept
37
+ [3] did some stuff, sleeping
38
+ firing :other_stuff_happens
39
+ [n/a] same_stuff_happened callback: n/a
40
+ [2, 316]: do_somestuff
41
+ firing :stuff_happens
42
+ [2] stuff_happened callback: 420
43
+ [3, 748]: do_somestuff
44
+ firing :stuff_happens
45
+ [3] stuff_happened callback: 545
46
+ [2, 316]: do_somestuff
47
+ firing :stuff_happens
48
+ [2] stuff_happened callback: 471
49
+ [3, 748]: do_somestuff
50
+ firing :stuff_happens
51
+ [2, 316]: do_somestuff
52
+ [3] stuff_happened callback: 266
53
+ firing :stuff_happens
54
+ [2] stuff_happened callback: 481
55
+ [3] slept
56
+ all done
@@ -0,0 +1,2 @@
1
+ firing :stuff_happens
2
+ stuff happened callback: 454
Binary file
@@ -3,40 +3,40 @@ require 'thread'
3
3
  # Incredibly simple framework for adding events
4
4
  module Eventable
5
5
 
6
- # Allows for dynamic discovery of hooked callbacks
7
- attr_reader :callbacks
6
+ module EventableEventMethods
8
7
 
9
- # Add the #event method to the extending class not instances of that class
10
- def self.included(base)
11
- base.extend(EventableClassMethods)
12
- end
13
-
14
- module EventableClassMethods
15
-
16
8
  # register an event
17
9
  def event(event_name)
18
10
  @eventable_events ||= []
19
11
  @eventable_events << event_name unless @eventable_events.include? event_name
20
12
  end
21
-
13
+
22
14
  # returns a list of registered events
23
15
  def events
24
16
  @eventable_events.clone
25
17
  end
26
-
18
+
27
19
  end
28
20
 
29
21
  def events
30
22
  self.class.events
31
23
  end
32
-
24
+
25
+ # Allows for dynamic discovery of hooked callbacks
26
+ attr_reader :callbacks
27
+
28
+ # Add the #event method to the extending class not instances of that class
29
+ def self.included(base)
30
+ base.extend(EventableEventMethods)
31
+ end
32
+
33
33
  def initialize
34
+ super
34
35
  @eventable_mutex = Mutex.new
35
36
  end
36
-
37
+
37
38
  # When the event happens the class where it happens runs this
38
39
  def fire_event(event, *return_value, &block)
39
- # We don't want the callback array being altered when we're trying to read it
40
40
  check_mutex
41
41
  @eventable_mutex.synchronize {
42
42
 
@@ -45,7 +45,9 @@ module Eventable
45
45
  @callbacks[event].each do |listener_id, callbacks|
46
46
  begin
47
47
  listener = ObjectSpace._id2ref(listener_id)
48
- callbacks.each {|callback| listener.send callback, *return_value, &block}
48
+ callbacks.each do |callback|
49
+ Thread.new {listener.send callback, *return_value, &block}
50
+ end
49
51
  rescue RangeError => re
50
52
  # Don't bubble up a missing recycled object, I don't care if it's not there, I just won't call it
51
53
  raise re unless re.message.match(/is recycled object/)
@@ -61,19 +63,19 @@ module Eventable
61
63
  raise ArgumentError, "Missing parameter :#{parameter}" unless args[parameter]
62
64
  end
63
65
 
64
- event = args[:event]
65
- raise Errors::UnknownEvent unless events.include? event
66
-
67
- # Make access to the callback cache threadsafe
66
+ # Make access to the callback array threadsafe
68
67
  check_mutex
69
68
  @eventable_mutex.synchronize {
69
+ event = args[:event]
70
+ raise Errors::UnknownEvent unless events.include? event
71
+
70
72
  @callbacks ||= {}
71
73
  @callbacks[event] ||= {}
72
-
74
+
73
75
  listener = args[:listener]
74
76
  listener_id = listener.object_id
75
77
  callback = args[:callback]
76
-
78
+
77
79
  # save the callback info without creating a reference to the object
78
80
  @callbacks[event][listener_id] ||= []
79
81
  @callbacks[event][listener_id] << callback
@@ -92,7 +94,7 @@ module Eventable
92
94
  @eventable_mutex.synchronize {
93
95
  event = args[:event]
94
96
  return unless @callbacks && @callbacks[event]
95
-
97
+
96
98
  listener_id = args[:listener_id] || args[:listener].object_id
97
99
  callback = args[:callback]
98
100
  @callbacks[event].delete_if do |listener, callbacks|
@@ -103,7 +105,7 @@ module Eventable
103
105
  end
104
106
 
105
107
  private
106
-
108
+
107
109
  def check_mutex
108
110
  raise Errors::SuperNotCalledInInitialize, "You must include super in your class's initialize method" unless @eventable_mutex
109
111
  end
@@ -113,7 +115,5 @@ module Eventable
113
115
  def unregister_finalizer(event, listener_id, callback)
114
116
  proc {unregister_for_event(event: event, listener_id: listener_id, callback: callback)}
115
117
  end
116
-
117
- end
118
-
119
118
 
119
+ end
@@ -1,3 +1,3 @@
1
1
  module Eventable
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
Binary file
@@ -1,5 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
+ # Callbacks are threaded to avoid blocking so tests need to wait for the callback to be scheduled and run
4
+ CALLBACK_WAIT = 0.01
5
+
3
6
  describe Eventable do
4
7
 
5
8
  before(:each) do
@@ -15,8 +18,8 @@ describe Eventable do
15
18
  include Eventable
16
19
  event :do_stuff
17
20
  end
18
- f = Foo.new
19
- f.fire_event(:do_stuff)
21
+ f = Foo.new
22
+ f.fire_event(:do_stuff)
20
23
  }.should_not raise_error
21
24
  end
22
25
 
@@ -33,7 +36,7 @@ describe Eventable do
33
36
  f.fire_event(:do_stuff)
34
37
  }.should_not raise_error
35
38
  end
36
-
39
+
37
40
  it "should raise an error if super is not called in initialize" do
38
41
  lambda{
39
42
  class Foo
@@ -46,7 +49,7 @@ describe Eventable do
46
49
  f.fire_event(:do_stuff)
47
50
  }.should raise_error(Eventable::Errors::SuperNotCalledInInitialize)
48
51
  end
49
-
52
+
50
53
  end
51
54
 
52
55
  context "when specifiying an event" do
@@ -54,7 +57,7 @@ describe Eventable do
54
57
  it 'should list the event from the class' do
55
58
  EventClass.events.should include(:stuff_happens)
56
59
  end
57
-
60
+
58
61
  it 'should list more than one event' do
59
62
  EventClass.events.should include(:other_stuff_happens)
60
63
  end
@@ -62,42 +65,39 @@ describe Eventable do
62
65
  it 'should list the event from an instance of the class' do
63
66
  @evented.events.should include(:stuff_happens)
64
67
  end
65
-
68
+
66
69
  it 'should list more than one event from an instance' do
67
70
  @evented.events.should include(:other_stuff_happens)
68
71
  end
69
72
 
70
73
  it "should not add an event that's already been added" do
71
74
  eval %{
72
- class EventClass
73
- include Eventable
74
- event :stuff_happens
75
- end
75
+ class EventClass
76
+ include Eventable
77
+ event :stuff_happens
78
+ end
76
79
  }
77
80
  EventClass.events.count.should == 2
78
81
  end
79
82
 
80
83
  it "should not add events to other classes" do
81
84
  eval %{
82
- class EventClass2
83
- include Eventable
84
- event :some_other_event
85
- end
85
+ class EventClass2
86
+ include Eventable
87
+ event :some_other_event
88
+ end
86
89
  }
87
90
  EventClass.events.should_not include(:some_other_event)
88
91
  end
89
-
92
+
90
93
  it "should not allow its event list to be altered external" do
91
94
  events = EventClass.events
92
95
  events.pop
93
96
  events.should_not == EventClass.events
94
97
  end
95
-
96
- it "should allow multiple classes to use the mixin" do
97
- end
98
-
98
+
99
99
  end
100
-
100
+
101
101
  context "when registering for an event" do
102
102
 
103
103
  context "and there is a missing parameter" do
@@ -141,7 +141,7 @@ describe Eventable do
141
141
  @evented.register_for_event(event: :other_stuff_happens, listener: @listener, callback: :callback)
142
142
  @evented.callbacks.count.should == 2
143
143
  end
144
-
144
+
145
145
  it "should not add a callback that's already been added" do
146
146
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
147
147
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
@@ -159,6 +159,7 @@ describe Eventable do
159
159
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
160
160
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback2)
161
161
  @evented.do_event
162
+ sleep(CALLBACK_WAIT)
162
163
  @listener.callback?.should be_true
163
164
  @listener.callback2?.should be_true
164
165
  end
@@ -167,6 +168,7 @@ describe Eventable do
167
168
  # should be a no brainer because a class is an object too, but just to be sure
168
169
  @evented.register_for_event(event: :stuff_happens, listener: ListenClass, callback: :class_callback)
169
170
  @evented.do_event
171
+ sleep(CALLBACK_WAIT)
170
172
  ListenClass.class_callback?.should be_true
171
173
  end
172
174
 
@@ -180,6 +182,7 @@ describe Eventable do
180
182
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
181
183
  another_evented.register_for_event(event: :stuff_happens, listener: another_listener, callback: :callback)
182
184
  @evented.do_event
185
+ sleep(CALLBACK_WAIT)
183
186
  @listener.callback?.should be_true
184
187
  another_listener.callback?.should_not be_true
185
188
  end
@@ -191,6 +194,7 @@ describe Eventable do
191
194
  another_evented.register_for_event(event: :stuff_happens, listener: another_listener, callback: :callback2)
192
195
  @evented.do_event
193
196
  another_evented.do_event
197
+ sleep(CALLBACK_WAIT)
194
198
  @listener.callback?.should be_true
195
199
  another_listener.callback2?.should be_true
196
200
  end
@@ -198,28 +202,28 @@ describe Eventable do
198
202
  end
199
203
 
200
204
  end
201
-
205
+
202
206
  context "when unregistering for an event" do
203
207
 
204
208
  it "should not throw an error if unregistering for an event you weren't registered for" do
205
209
  # Is this supporting sloppy programming(bad) or lazy programming(good)?
206
210
  lambda{@evented.unregister_for_event(event: :stuff_happens, listener: @listener, callback: :callback)}.should_not raise_error
207
211
  end
208
-
212
+
209
213
  it "should remove a callback" do
210
214
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
211
215
  @evented.callbacks[:stuff_happens].keys.should include(@listener.object_id) # <= just to be sure...
212
216
  @evented.unregister_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
213
217
  @evented.callbacks[:stuff_happens].keys.should_not include(@listener.object_id)
214
218
  end
215
-
219
+
216
220
  it "should not call a callback that has been removed" do
217
221
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
218
222
  @evented.unregister_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
219
223
  @evented.do_event
220
224
  @listener.callback?.should_not be_true
221
225
  end
222
-
226
+
223
227
  it "should automatically remove callbacks to objects that are garbage collected" do
224
228
  listener_object_id = nil
225
229
  (0..1).each do
@@ -230,7 +234,7 @@ describe Eventable do
230
234
  GC.start
231
235
  @evented.callbacks[:stuff_happens].keys.should_not include(listener_object_id)
232
236
  end
233
-
237
+
234
238
  end
235
239
 
236
240
  context "when an event is fired" do
@@ -249,39 +253,40 @@ describe Eventable do
249
253
  @evented.do_event
250
254
  lambda{@evented.do_event}.should_not raise_error
251
255
  end
252
-
256
+
253
257
  it "should call back the specified method when the event is fired" do
254
258
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
255
259
  @evented.do_event
260
+ sleep(CALLBACK_WAIT)
256
261
  @listener.callback?.should be_true
257
262
  end
258
-
263
+
259
264
  it "should not call back the wrong method when the event is fired" do
260
265
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
261
266
  @evented.do_event
262
267
  @listener.callback2?.should_not be_true
263
268
  end
264
-
269
+
265
270
  it "should call back more than one class" do
266
271
  listener2 = ListenClass.new
267
-
272
+
268
273
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
269
274
  @evented.register_for_event(event: :stuff_happens, listener: listener2, callback: :callback2)
270
-
275
+
271
276
  @evented.do_event
272
-
277
+ sleep(CALLBACK_WAIT)
273
278
  @listener.callback?.should be_true
274
279
  listener2.callback2?.should be_true
275
280
  end
276
281
 
277
282
  it "should not call back the wrong method when using multiple classes" do
278
283
  listener2 = ListenClass.new
279
-
284
+
280
285
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback)
281
286
  @evented.register_for_event(event: :stuff_happens, listener: listener2, callback: :callback2)
282
-
287
+
283
288
  @evented.do_event
284
-
289
+ sleep(CALLBACK_WAIT)
285
290
  @listener.callback2?.should_not be_true
286
291
  listener2.callback?.should_not be_true
287
292
  end
@@ -292,6 +297,7 @@ describe Eventable do
292
297
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_args)
293
298
  a, b, c = rand(100), rand(100), rand(100)
294
299
  @evented.do_event_with_args(a,b,c)
300
+ sleep(CALLBACK_WAIT)
295
301
  @listener.callback_with_args?.should == [a,b,c]
296
302
  end
297
303
 
@@ -299,6 +305,7 @@ describe Eventable do
299
305
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_block)
300
306
  block = proc {"a block"}
301
307
  @evented.do_event_with_block(&block)
308
+ sleep(CALLBACK_WAIT)
302
309
  @listener.callback_with_block?.should == block
303
310
  end
304
311
 
@@ -306,17 +313,42 @@ describe Eventable do
306
313
  @evented.register_for_event(event: :stuff_happens, listener: @listener, callback: :callback_with_args_and_block)
307
314
  a, b, c, block = rand(100), rand(100), rand(100), proc {"a block"}
308
315
  @evented.do_event_with_args_and_block(a,b,c,&block)
316
+ sleep(CALLBACK_WAIT)
309
317
  @listener.callback_with_args_and_block?.should == [a,b,c,block]
310
318
  end
311
319
 
312
320
  end
313
-
321
+
322
+ context "when multithreading" do
323
+
324
+ it "should not block on callbacks" do
325
+ bc = BlockingClass.new
326
+ @evented.register_for_event(event: :stuff_happens, listener: bc, callback: :blocking_call)
327
+ start_time = Time.now
328
+ @evented.do_event
329
+ sleep(CALLBACK_WAIT * 2) # Just to make sure the event cycle starts
330
+ Time.now.should_not > start_time + 1
331
+ end
332
+
333
+ end
334
+
314
335
  end
315
336
 
316
337
  end
317
338
 
318
339
  # Test classes
319
340
 
341
+ class BlockingClass
342
+ def blocking_call
343
+ # I don't know how long this will take but it's a lot longer than 1 second
344
+ 1000000000.times { Math.sqrt(98979872938749283749729384792734927) }
345
+ non_blocking_call
346
+ end
347
+ def non_blocking_call
348
+ "all done"
349
+ end
350
+ end
351
+
320
352
  class EventClass
321
353
  include Eventable
322
354
  event :stuff_happens
@@ -339,7 +371,7 @@ class AnotherEventClass
339
371
  include Eventable
340
372
  event :stuff_happens
341
373
  event :different_happens
342
-
374
+
343
375
  def do_event(event=:stuff_happens)
344
376
  fire_event(event)
345
377
  end
@@ -391,7 +423,7 @@ class AnotherListenClass
391
423
  def callback
392
424
  @callback = true
393
425
  end
394
- def callback2?
426
+ def callback2?
395
427
  @callback2
396
428
  end
397
429
  def callback2
Binary file
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: eventable
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.2
5
+ version: 0.1.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Mike Bethany
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-18 00:00:00 Z
13
+ date: 2011-06-29 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -23,6 +23,17 @@ dependencies:
23
23
  version: "2.6"
24
24
  type: :development
25
25
  version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ prerelease: false
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: "1.0"
35
+ type: :development
36
+ version_requirements: *id002
26
37
  description: Provides an easy to use and understand event model. If you want a simple, light-weight way to add events to your classes this is the solution for you.
27
38
  email:
28
39
  - mikbe.tk@gmail.com
@@ -33,6 +44,7 @@ extensions: []
33
44
  extra_rdoc_files: []
34
45
 
35
46
  files:
47
+ - .DS_Store
36
48
  - .gitignore
37
49
  - Gemfile
38
50
  - LICENSE.txt
@@ -41,16 +53,21 @@ files:
41
53
  - autotest/discover.rb
42
54
  - eventable.gemspec
43
55
  - example/example.rb
56
+ - example/example_output.txt
44
57
  - example/simple_example.rb
58
+ - example/simple_output.txt
59
+ - lib/.DS_Store
45
60
  - lib/eventable.rb
46
61
  - lib/eventable/errors.rb
47
62
  - lib/eventable/eventable.rb
48
63
  - lib/eventable/version.rb
64
+ - spec/.DS_Store
49
65
  - spec/eventable/eventable_spec.rb
50
66
  - spec/spec_helper.rb
67
+ - vendor/.DS_Store
51
68
  homepage: http://mikbe.tk/projects#eventable
52
- licenses: []
53
-
69
+ licenses:
70
+ - MIT
54
71
  post_install_message:
55
72
  rdoc_options: []
56
73
 
@@ -61,7 +78,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
78
  requirements:
62
79
  - - ">="
63
80
  - !ruby/object:Gem::Version
64
- version: "0"
81
+ version: 1.9.2
65
82
  required_rubygems_version: !ruby/object:Gem::Requirement
66
83
  none: false
67
84
  requirements: