witch 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/puma/Gemfile +4 -0
- data/examples/puma/config.ru +21 -0
- data/examples/puma/update.rb +8 -0
- data/lib/version.rb +1 -1
- data/lib/witch.rb +50 -18
- data/spec/witch/witch_spec.rb +62 -16
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a844251fad9136572ae168e4ccec604679651caf
|
4
|
+
data.tar.gz: 51f9b573a0bfcb8b59885ed2d88e5c518455d7b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47e2ab33086fa1b4bc303dd5fddd3fdb3abca21d43dd263a4ba7db1990bf23bd2236d61eb37552febae197018277c674f8a8490391ffa11fa41e824e36ae4f5f
|
7
|
+
data.tar.gz: 015e88a9dbb40aaa85f321bb086a3d809c249811252429d0803f7a7a60d26101ed3147f6cc0ce98c173ebe155ef5d426ff3b3683041bd39c526f868e2ebd3638
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'witch'
|
3
|
+
|
4
|
+
class HelloWorld
|
5
|
+
def initialize
|
6
|
+
@response_text = 'Hello World!'
|
7
|
+
|
8
|
+
Witch.init(:logger => Logger.new(STDOUT))
|
9
|
+
|
10
|
+
Witch.on('change_text') do |text|
|
11
|
+
puts 'Got new text: ' + text
|
12
|
+
@response_text = text
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
[200, {"Content-Type" => "text/html"}, [@response_text]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
run HelloWorld.new
|
data/lib/version.rb
CHANGED
data/lib/witch.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'redis'
|
2
|
-
require '
|
2
|
+
require 'logger'
|
3
3
|
|
4
4
|
module Witch
|
5
5
|
|
@@ -10,12 +10,21 @@ module Witch
|
|
10
10
|
def init(options = {})
|
11
11
|
raise 'Already initialized!' if @queue
|
12
12
|
@queue = options.delete(:queue) || 'eventstream'
|
13
|
+
@logger = options.delete(:logger)
|
13
14
|
@options = options
|
14
15
|
@callbacks = {:once => {}, :on => {}}
|
15
16
|
@channels = []
|
17
|
+
@thread = nil
|
16
18
|
@id = Socket.gethostname + '-' + $$.to_s
|
17
19
|
end
|
18
20
|
|
21
|
+
def reset
|
22
|
+
return unless @queue
|
23
|
+
disconnect # clears @callbacks[:once]
|
24
|
+
@callbacks[:on] = {}
|
25
|
+
@queue = nil
|
26
|
+
end
|
27
|
+
|
19
28
|
def once(*events, &block)
|
20
29
|
raise 'Not initialized' unless @callbacks
|
21
30
|
events.each do |event|
|
@@ -43,21 +52,23 @@ module Witch
|
|
43
52
|
end
|
44
53
|
|
45
54
|
def publish(event, data = nil)
|
55
|
+
raise 'Not initialized' unless @queue
|
56
|
+
|
46
57
|
str = event + '___' + data.to_s
|
47
58
|
|
48
59
|
lock.synchronize do
|
49
60
|
# first, send to everyone on the main queue
|
50
|
-
|
61
|
+
logger.debug '[publisher] Publishing on queue ' + @queue
|
51
62
|
connection.publish(@queue, str)
|
52
63
|
|
53
64
|
# if one or more clients are subscribed to the 'once' queue, choose one and notify.
|
54
65
|
if consumer = connection.smembers('subscribers:' + event.to_s).shuffle.first
|
55
66
|
# connection.publish(['events', event, subscriber].join(':'), data)
|
56
|
-
|
67
|
+
logger.info "[publisher] Found consumer for #{event}: #{consumer}"
|
57
68
|
count = connection.publish(consumer, str)
|
58
69
|
|
59
70
|
if count == 0
|
60
|
-
|
71
|
+
logger.warn "[publisher] Consumer #{consumer} disconnected! Removing and retrying..."
|
61
72
|
connection.srem('subscribers:' + event.to_s, consumer)
|
62
73
|
raise ConsumerDisconnected, "name: #{consumer}"
|
63
74
|
end
|
@@ -69,11 +80,23 @@ module Witch
|
|
69
80
|
retry
|
70
81
|
end
|
71
82
|
|
83
|
+
def wait
|
84
|
+
@thread.join
|
85
|
+
end
|
86
|
+
|
72
87
|
def disconnect
|
73
88
|
return unless @id
|
89
|
+
|
74
90
|
@callbacks[:once].keys.each do |event|
|
75
91
|
unregister_from(event)
|
76
92
|
end
|
93
|
+
|
94
|
+
return unless @thread
|
95
|
+
|
96
|
+
logger.warn '[consumer] Disconnecting!'
|
97
|
+
lock.synchronize do
|
98
|
+
@thread.kill
|
99
|
+
end
|
77
100
|
end
|
78
101
|
|
79
102
|
private
|
@@ -81,31 +104,37 @@ module Witch
|
|
81
104
|
def register_for(event)
|
82
105
|
lock.synchronize do
|
83
106
|
count = connection.sadd('subscribers:' + event.to_s, @id)
|
84
|
-
|
107
|
+
logger.debug "[consumer] Registered. Queue contains #{count} listeners."
|
85
108
|
end
|
109
|
+
rescue => e
|
110
|
+
logger.error '[consumer] Register error: ' + e.inspect
|
86
111
|
end
|
87
112
|
|
88
113
|
def unregister_from(event)
|
89
114
|
lock.synchronize do
|
90
|
-
|
115
|
+
logger.debug "[consumer] Unregistering from #{event} processing queue."
|
91
116
|
connection.srem('subscribers:' + event.to_s, @id)
|
92
117
|
end
|
93
118
|
rescue => e
|
94
|
-
|
119
|
+
logger.error '[consumer] Unregister error: ' + e.inspect
|
95
120
|
end
|
96
121
|
|
97
122
|
def listen(type, queue)
|
98
123
|
lock.synchronize do
|
99
|
-
|
124
|
+
logger.debug "[consumer] Subscribing to queue #{queue} on new thread..."
|
100
125
|
@channels.push(queue)
|
101
|
-
Thread.new do
|
126
|
+
@thread = Thread.new do
|
102
127
|
# connection.subscribe(['events' + event + id].join(':')) do |on|
|
103
128
|
connection.subscribe(queue) do |on|
|
104
129
|
on.subscribe do |channel, subscriptions|
|
105
|
-
|
106
|
-
puts "[consumer] Subscribed to #{channel} (#{subscriptions} subscriptions)"
|
130
|
+
logger.info "[consumer] Subscribed to #{channel} (#{subscriptions} subscriptions)"
|
107
131
|
raise "Existing subscribers!" if subscriptions > 1
|
108
132
|
end
|
133
|
+
|
134
|
+
on.unsubscribe do |channel, subscriptions|
|
135
|
+
logger.info "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)"
|
136
|
+
end
|
137
|
+
|
109
138
|
on.message do |channel, msg|
|
110
139
|
process(type, msg)
|
111
140
|
end
|
@@ -114,26 +143,25 @@ module Witch
|
|
114
143
|
end
|
115
144
|
rescue => e
|
116
145
|
@channels.delete(queue)
|
117
|
-
|
146
|
+
logger.error '[consumer] ' + e.message
|
118
147
|
end
|
119
148
|
|
120
149
|
def process(type, msg)
|
121
150
|
parts = msg.split('___')
|
122
|
-
|
123
|
-
fire_callbacks(type, parts[0], data)
|
151
|
+
fire_callbacks(type, parts[0], parts[1])
|
124
152
|
rescue => e
|
125
|
-
|
153
|
+
logger.error "[consumer] Error parsing message: #{e.message}"
|
126
154
|
end
|
127
155
|
|
128
156
|
def fire_callbacks(type, event, data)
|
129
157
|
return if @callbacks.nil?
|
130
|
-
lock.synchronize do
|
158
|
+
# lock.synchronize do
|
131
159
|
(@callbacks[type][event.to_sym] || []).each do |callback|
|
132
160
|
callback.call(data)
|
133
161
|
end
|
134
|
-
end
|
162
|
+
# end
|
135
163
|
rescue => e
|
136
|
-
|
164
|
+
logger.error "[consumer] Error firing callback: #{e.message}"
|
137
165
|
end
|
138
166
|
|
139
167
|
def lock
|
@@ -149,6 +177,10 @@ module Witch
|
|
149
177
|
@channels.include?(queue)
|
150
178
|
end
|
151
179
|
|
180
|
+
def logger
|
181
|
+
@logger || Logger.new(nil)
|
182
|
+
end
|
183
|
+
|
152
184
|
end
|
153
185
|
|
154
186
|
end
|
data/spec/witch/witch_spec.rb
CHANGED
@@ -2,43 +2,89 @@ require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
2
|
|
3
3
|
describe 'witch' do
|
4
4
|
|
5
|
-
before do
|
6
|
-
|
5
|
+
before(:each) do
|
6
|
+
Witch.reset
|
7
7
|
end
|
8
8
|
|
9
9
|
after(:all) do
|
10
10
|
# Witch.disconnect
|
11
11
|
end
|
12
12
|
|
13
|
-
def register(type)
|
14
|
-
Witch.
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
def register(type, event_name)
|
14
|
+
Witch.send(type, event_name) do
|
15
|
+
@called[event_name] = {} unless @called[event_name]
|
16
|
+
@called[event_name][$$] = true
|
17
|
+
sleep 0.1
|
18
|
+
puts Witch.disconnect
|
18
19
|
end
|
19
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'init' do
|
23
|
+
|
24
|
+
it 'works if called once' do
|
25
|
+
expect do
|
26
|
+
Witch.init
|
27
|
+
end.not_to raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fires error if called twice' do
|
31
|
+
expect do
|
32
|
+
Witch.init
|
33
|
+
Witch.init
|
34
|
+
end.to raise_error('Already initialized!')
|
35
|
+
end
|
36
|
+
|
20
37
|
end
|
21
38
|
|
22
39
|
describe 'once' do
|
23
40
|
|
41
|
+
before do
|
42
|
+
Witch.init(:logger => Logger.new(STDOUT))
|
43
|
+
@called = {}
|
44
|
+
end
|
45
|
+
|
24
46
|
describe 'single process' do
|
25
47
|
|
26
|
-
|
27
|
-
|
48
|
+
describe 'single listener' do
|
49
|
+
|
50
|
+
before do
|
51
|
+
register(:once, 'some_event')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'fires event once' do
|
55
|
+
Witch.publish 'some_event'
|
56
|
+
Witch.wait
|
57
|
+
expect(@called['some_event'].keys.count).to be(1)
|
58
|
+
expect(@called['some_event'].keys.first).to be($$)
|
59
|
+
end
|
60
|
+
|
28
61
|
end
|
29
62
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
63
|
+
describe 'multiple listeners' do
|
64
|
+
|
65
|
+
before do
|
66
|
+
register(:once, 'some_other_event')
|
67
|
+
register(:once, 'some_other_event')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'fires event once' do
|
71
|
+
Witch.publish 'some_other_event'
|
72
|
+
Witch.wait
|
73
|
+
expect(@called['some_other_event'].keys.count).to be(1)
|
74
|
+
expect(@called['some_other_event'].keys.first).to be($$)
|
75
|
+
end
|
76
|
+
|
35
77
|
end
|
36
78
|
|
37
79
|
end
|
38
80
|
|
39
81
|
describe 'multi process' do
|
40
82
|
|
41
|
-
|
83
|
+
describe 'single listener per process' do
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'multiple listeners per process' do
|
42
88
|
|
43
89
|
end
|
44
90
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: witch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomás Pollak
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,6 +68,9 @@ files:
|
|
68
68
|
- Gemfile
|
69
69
|
- README.md
|
70
70
|
- Rakefile
|
71
|
+
- examples/puma/Gemfile
|
72
|
+
- examples/puma/config.ru
|
73
|
+
- examples/puma/update.rb
|
71
74
|
- lib/version.rb
|
72
75
|
- lib/witch.rb
|
73
76
|
- sandbox/api.rb
|