witch 0.0.2 → 0.0.3
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.
- 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
|