tusk 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ === 1.1.0 / 2012-10-04
2
+
3
+ * Redis supports sending a payload with `notify_observers`
4
+ Thanks Collin Miller!
5
+
6
+ * DRb observer added
7
+
1
8
  === 1.0.0 / 2012-07-18
2
9
 
3
10
  * 1 major enhancement
@@ -5,9 +5,11 @@ README.markdown
5
5
  Rakefile
6
6
  lib/tusk.rb
7
7
  lib/tusk/latch.rb
8
+ lib/tusk/observable/drb.rb
8
9
  lib/tusk/observable/pg.rb
9
10
  lib/tusk/observable/redis.rb
10
11
  test/helper.rb
12
+ test/observable/test_drb.rb
11
13
  test/observable/test_pg.rb
12
14
  test/observable/test_redis.rb
13
15
  test/redis-test.conf
@@ -15,6 +15,7 @@ Currently, Tusk supports Redis and PostgreSQL as message bus back ends.
15
15
  * Send message across processes
16
16
  * Supports Redis as a message bus
17
17
  * Supports PostgreSQL as a message bus
18
+ * Supports DRb as a message bus
18
19
 
19
20
  ## SYNOPSIS:
20
21
 
@@ -3,7 +3,8 @@
3
3
  #
4
4
  # Tusk::Observers::Redis offers an Observer API with Redis as the
5
5
  # message bus. Tusk::Observers::PG offers and Observer API with
6
- # PostgreSQL as the message bus.
6
+ # PostgreSQL as the message bus. Tusk::Observers::DRb offers an
7
+ # Observer API with DRb as the message bus.
7
8
  module Tusk
8
- VERSION = '1.0.0'
9
+ VERSION = '1.1.0'
9
10
  end
@@ -0,0 +1,178 @@
1
+ require 'drb'
2
+ require 'digest/md5'
3
+
4
+ module Tusk
5
+ module Observable
6
+ ###
7
+ # An observer implementation for DRb. This module requires that
8
+ # you start a DRb server, which can be done via Server.start
9
+ #
10
+ # This observer works across processes.
11
+ #
12
+ # Example:
13
+ #
14
+ # require 'tusk/observable/drb'
15
+ #
16
+ # class Timer
17
+ # include Tusk::Observable::DRb
18
+ #
19
+ # # Start the DRb server. Do this once
20
+ # Thread.new { Server.start }
21
+ #
22
+ # def tick
23
+ # changed
24
+ # notify_observers
25
+ # end
26
+ # end
27
+ #
28
+ # class Listener
29
+ # def update
30
+ # puts "got update"
31
+ # end
32
+ # end
33
+ #
34
+ # timer = Timer.new
35
+ #
36
+ # fork do
37
+ # timer.add_observer Listener.new
38
+ # sleep # put the process to sleep so it doesn't exit
39
+ # end
40
+ #
41
+ # loop do
42
+ # timer.tick
43
+ # sleep 1
44
+ # end
45
+ module DRb
46
+ class Server
47
+ URI = 'druby://localhost:8787'
48
+
49
+ def self.start
50
+ ::DRb.start_service URI, new
51
+ end
52
+
53
+ def self.stop
54
+ ::DRb.stop_service
55
+ end
56
+
57
+ def initialize
58
+ @channels = Hash.new { |h,k| h[k] = {} }
59
+ end
60
+
61
+ def watch channel, proxy
62
+ @channels[channel][proxy] = proxy
63
+ end
64
+
65
+ def signal channel, args
66
+ @channels[channel].each { |proxy,|
67
+ proxy.notify args
68
+ }
69
+ end
70
+
71
+ def delete_observer channel, o
72
+ @channels[channel].delete o
73
+ end
74
+
75
+ def delete channel
76
+ @channels.delete channel
77
+ end
78
+ end
79
+
80
+ class Proxy # :nodoc:
81
+ include ::DRb::DRbUndumped
82
+
83
+ def initialize d, func
84
+ @delegate = d
85
+ @func = func
86
+ end
87
+
88
+ def notify args
89
+ @delegate.send(@func, *args)
90
+ end
91
+ end
92
+
93
+ def self.extended klass
94
+ super
95
+
96
+ klass.instance_eval do
97
+ @bus = DRbObject.new_with_uri uri
98
+ @observer_state = false
99
+ @subscribers = {}
100
+ end
101
+ end
102
+
103
+ def initialize *args
104
+ super
105
+
106
+ @bus = DRbObject.new_with_uri uri
107
+ @observer_state = false
108
+ @subscribers = {}
109
+ end
110
+
111
+ # Add +observer+ as an observer to this object. The +object+ will
112
+ # receive a notification when #changed? returns true and #notify_observers
113
+ # is called.
114
+ #
115
+ # +func+ method is called on +object+ when notifications are sent.
116
+ def add_observer object, func = :update
117
+ unless ::DRb.thread && ::DRb.thread.alive?
118
+ ::DRb.start_service
119
+ end
120
+
121
+ proxy = Proxy.new object, func
122
+ @subscribers[object] = proxy
123
+ @bus.watch channel, proxy
124
+ end
125
+
126
+ # If this object's #changed? state is true, this method will notify
127
+ # observing objects.
128
+ def notify_observers(*args)
129
+ return unless changed?
130
+ @bus.signal channel, args
131
+ changed false
132
+ end
133
+
134
+ # Remove all observers associated with this object *in the current
135
+ # process*. This method will not impact observers of this object in
136
+ # other processes.
137
+ def delete_observers
138
+ @bus.delete channel
139
+ @subscribers.clear
140
+ end
141
+
142
+ # Remove +observer+ so that it will no longer receive notifications.
143
+ def delete_observer o
144
+ proxy = @subscribers.delete o
145
+ @bus.delete_observer channel, proxy
146
+ end
147
+
148
+ # Returns true if this object's state has been changed since the last
149
+ # call to #notify_observers.
150
+ def changed?
151
+ @observer_state
152
+ end
153
+
154
+ # Set the changed state of this object. Notifications will be sent only
155
+ # if the changed +state+ is a truthy object.
156
+ def changed state = true
157
+ @observer_state = state
158
+ end
159
+
160
+ # Returns the number of observers associated with this object *in the
161
+ # current process*. If the object is observed across multiple processes,
162
+ # the returned count will not reflect the other processes.
163
+ def count_observers
164
+ @subscribers.length
165
+ end
166
+
167
+ private
168
+
169
+ def uri
170
+ Server::URI
171
+ end
172
+
173
+ def channel
174
+ "a" + Digest::MD5.hexdigest("#{self.class.name}#{object_id}")
175
+ end
176
+ end
177
+ end
178
+ end
@@ -15,33 +15,33 @@ module Tusk
15
15
  #
16
16
  # require 'redis'
17
17
  # require 'tusk/observable/redis'
18
- #
18
+ #
19
19
  # class Timer
20
20
  # include Tusk::Observable::Redis
21
- #
21
+ #
22
22
  # def tick
23
23
  # changed
24
24
  # notify_observers
25
25
  # end
26
- #
26
+ #
27
27
  # def connection
28
28
  # Thread.current[:conn] ||= ::Redis.new
29
29
  # end
30
30
  # end
31
- #
31
+ #
32
32
  # class Listener
33
33
  # def update
34
34
  # puts "got update"
35
35
  # end
36
36
  # end
37
- #
37
+ #
38
38
  # timer = Timer.new
39
- #
39
+ #
40
40
  # fork do
41
41
  # timer.add_observer Listener.new
42
42
  # sleep # put the process to sleep so it doesn't exit
43
43
  # end
44
- #
44
+ #
45
45
  # loop do
46
46
  # timer.tick
47
47
  # sleep 1
@@ -100,9 +100,9 @@ module Tusk
100
100
 
101
101
  # If this object's #changed? state is true, this method will notify
102
102
  # observing objects.
103
- def notify_observers
103
+ def notify_observers(*args)
104
104
  return unless changed?
105
- connection.publish channel, nil
105
+ connection.publish channel, payload_coder.dump(args)
106
106
  changed false
107
107
  end
108
108
 
@@ -148,6 +148,10 @@ module Tusk
148
148
  "a" + Digest::MD5.hexdigest("#{self.class.name}#{object_id}")
149
149
  end
150
150
 
151
+ def payload_coder
152
+ Marshal
153
+ end
154
+
151
155
  def start_listener latch
152
156
  connection.subscribe(channel, control_channel) do |on|
153
157
  on.subscribe { |c| latch.release }
@@ -158,7 +162,7 @@ module Tusk
158
162
  else
159
163
  @sub_lock.synchronize do
160
164
  subscribers.fetch(c, {}).each do |object,m|
161
- object.send m
165
+ object.send m, *payload_coder.load(message)
162
166
  end
163
167
  end
164
168
  end
@@ -13,6 +13,12 @@ module Tusk
13
13
  end
14
14
  end
15
15
 
16
+ class PayloadQueueingObserver < QueueingObserver
17
+ def update(*args)
18
+ @q.push(args)
19
+ end
20
+ end
21
+
16
22
  def test_changed?
17
23
  o = build_observable
18
24
  refute o.changed?
@@ -69,6 +75,18 @@ module Tusk
69
75
  assert_equal :foo, q.pop
70
76
  end
71
77
 
78
+ def test_notification_payload
79
+ o = build_observable
80
+ q = Queue.new
81
+
82
+ o.add_observer PayloadQueueingObserver.new q
83
+
84
+ o.changed
85
+ o.notify_observers :payload
86
+
87
+ assert_equal [:payload], q.pop
88
+ end
89
+
72
90
  def test_multiple_observers
73
91
  o = build_observable
74
92
  q = Queue.new
@@ -0,0 +1,76 @@
1
+ require 'helper'
2
+ require 'tusk/observable/drb'
3
+
4
+ module Tusk
5
+ module Observable
6
+ class TestDRb < TestCase
7
+ include ObserverTests
8
+
9
+ class Timer
10
+ include Tusk::Observable::DRb
11
+
12
+ def tick
13
+ changed
14
+ notify_observers
15
+ end
16
+ end
17
+
18
+ def setup
19
+ super
20
+ DRb::Server.start
21
+ end
22
+
23
+ def teardown
24
+ super
25
+ DRb::Server.stop
26
+ end
27
+
28
+ def test_no_connection
29
+ skip "not implementing for now"
30
+ end
31
+
32
+ private
33
+
34
+ def build_observable
35
+ Timer.new
36
+ end
37
+
38
+ def observer_module
39
+ Tusk::Observable::DRb
40
+ end
41
+ end
42
+
43
+ class TestClassDRb < TestCase
44
+ include ObserverTests
45
+
46
+ def setup
47
+ super
48
+ DRb::Server.start
49
+ end
50
+
51
+ def teardown
52
+ super
53
+ DRb::Server.stop
54
+ end
55
+
56
+ def build_observable
57
+ Class.new {
58
+ extend Tusk::Observable::DRb
59
+
60
+ def self.tick
61
+ changed
62
+ notify_observers
63
+ end
64
+ }
65
+ end
66
+
67
+ def test_no_connection
68
+ skip "not implementing for now"
69
+ end
70
+
71
+ def observer_module
72
+ Tusk::Observable::DRb
73
+ end
74
+ end
75
+ end
76
+ end
metadata CHANGED
@@ -1,100 +1,97 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: tusk
3
- version: !ruby/object:Gem::Version
4
- hash: 23
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 0
9
- - 0
10
- version: 1.0.0
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Aaron Patterson
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-07-22 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-10-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: minitest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '4.0'
22
+ type: :development
22
23
  prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rdoc
32
+ requirement: !ruby/object:Gem::Requirement
24
33
  none: false
25
- requirements:
34
+ requirements:
26
35
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 21
29
- segments:
30
- - 2
31
- - 11
32
- version: "2.11"
36
+ - !ruby/object:Gem::Version
37
+ version: '3.10'
33
38
  type: :development
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
36
- name: pg
37
39
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '3.10'
46
+ - !ruby/object:Gem::Dependency
47
+ name: pg
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
- requirements:
50
+ requirements:
41
51
  - - ~>
42
- - !ruby/object:Gem::Version
43
- hash: 39
44
- segments:
45
- - 0
46
- - 14
47
- - 0
52
+ - !ruby/object:Gem::Version
48
53
  version: 0.14.0
49
54
  type: :development
50
- version_requirements: *id002
51
- - !ruby/object:Gem::Dependency
52
- name: rdoc
53
55
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
55
57
  none: false
56
- requirements:
58
+ requirements:
57
59
  - - ~>
58
- - !ruby/object:Gem::Version
59
- hash: 19
60
- segments:
61
- - 3
62
- - 10
63
- version: "3.10"
64
- type: :development
65
- version_requirements: *id003
66
- - !ruby/object:Gem::Dependency
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.0
62
+ - !ruby/object:Gem::Dependency
67
63
  name: hoe
68
- prerelease: false
69
- requirement: &id004 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
70
65
  none: false
71
- requirements:
66
+ requirements:
72
67
  - - ~>
73
- - !ruby/object:Gem::Version
74
- hash: 25
75
- segments:
76
- - 2
77
- - 13
78
- version: "2.13"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
79
70
  type: :development
80
- version_requirements: *id004
81
- description: |-
82
- Tusk is a minimal pub / sub system with multiple observer strategies.
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ description: ! 'Tusk is a minimal pub / sub system with multiple observer strategies.
79
+
83
80
  Tusk builds upon the Observer API from stdlib in order to provide a mostly
81
+
84
82
  consistent API for building cross thread or process pub / sub systems.
85
-
86
- Currently, Tusk supports Redis and PostgreSQL as message bus back ends.
87
- email:
83
+
84
+
85
+ Currently, Tusk supports Redis and PostgreSQL as message bus back ends.'
86
+ email:
88
87
  - aaron@tenderlovemaking.com
89
88
  executables: []
90
-
91
89
  extensions: []
92
-
93
- extra_rdoc_files:
94
- - Manifest.txt
90
+ extra_rdoc_files:
95
91
  - CHANGELOG.rdoc
92
+ - Manifest.txt
96
93
  - README.markdown
97
- files:
94
+ files:
98
95
  - .autotest
99
96
  - CHANGELOG.rdoc
100
97
  - Manifest.txt
@@ -102,47 +99,42 @@ files:
102
99
  - Rakefile
103
100
  - lib/tusk.rb
104
101
  - lib/tusk/latch.rb
102
+ - lib/tusk/observable/drb.rb
105
103
  - lib/tusk/observable/pg.rb
106
104
  - lib/tusk/observable/redis.rb
107
105
  - test/helper.rb
106
+ - test/observable/test_drb.rb
108
107
  - test/observable/test_pg.rb
109
108
  - test/observable/test_redis.rb
110
109
  - test/redis-test.conf
111
110
  - .gemtest
112
111
  homepage: http://github.com/tenderlove/tusk
113
112
  licenses: []
114
-
115
113
  post_install_message:
116
- rdoc_options:
114
+ rdoc_options:
117
115
  - --main
118
116
  - README.markdown
119
- require_paths:
117
+ require_paths:
120
118
  - lib
121
- required_ruby_version: !ruby/object:Gem::Requirement
119
+ required_ruby_version: !ruby/object:Gem::Requirement
122
120
  none: false
123
- requirements:
124
- - - ">="
125
- - !ruby/object:Gem::Version
126
- hash: 3
127
- segments:
128
- - 0
129
- version: "0"
130
- required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
126
  none: false
132
- requirements:
133
- - - ">="
134
- - !ruby/object:Gem::Version
135
- hash: 3
136
- segments:
137
- - 0
138
- version: "0"
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
139
131
  requirements: []
140
-
141
132
  rubyforge_project: tusk
142
- rubygems_version: 1.8.22
133
+ rubygems_version: 1.8.24
143
134
  signing_key:
144
135
  specification_version: 3
145
136
  summary: Tusk is a minimal pub / sub system with multiple observer strategies
146
- test_files:
137
+ test_files:
138
+ - test/observable/test_drb.rb
147
139
  - test/observable/test_pg.rb
148
140
  - test/observable/test_redis.rb