ruby_nsq 0.0.3 → 0.0.4
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/History.md +6 -0
- data/README.md +8 -11
- data/lib/nsq.rb +3 -12
- data/lib/nsq/connection.rb +41 -60
- data/lib/nsq/error.rb +8 -0
- data/lib/nsq/logger.rb +18 -0
- data/lib/nsq/message.rb +0 -1
- data/lib/nsq/publisher.rb +46 -18
- data/lib/nsq/reader.rb +51 -73
- data/lib/nsq/subscriber.rb +12 -17
- data/lib/nsq/util.rb +3 -3
- metadata +63 -35
- data/lib/nsq/loggable.rb +0 -23
- data/lib/nsq/queue_subscriber.rb +0 -51
- data/test/sync_connection_test.rb.tbd +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52265e899f8f90847c965d85f1ebad0ecc76994c
|
4
|
+
data.tar.gz: e12d1e4f0ac44dd1faf07905ff505c22cb0facd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0890442271e47716b204b4412fa06b7294b9f70a9273ba22058f7844bd9dd1948d9e06fc94ce65be0658ca6f8bc0dbe9ea2ef19e3cdcf146bc86ae68fab82f9
|
7
|
+
data.tar.gz: ff7f950a74306eece1c5c77e34a8ab78869bbf1a651aef66cc568870c6600b1c11a0f5a6452f478843b56a034f18639dfc459e562c4297f85c98d2300eec3f50
|
data/History.md
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
https://github.com/ClarityServices/ruby_nsq
|
4
|
-
|
5
|
-
## Description
|
1
|
+
# Ruby NSQ
|
6
2
|
|
7
3
|
Ruby client for the [NSQ](https://github.com/bitly/nsq) realtime message processing system.
|
8
4
|
|
@@ -15,7 +11,7 @@ Ruby client for the [NSQ](https://github.com/bitly/nsq) realtime message process
|
|
15
11
|
See [examples](https://github.com/ClarityServices/ruby_nsq/tree/master/examples)
|
16
12
|
|
17
13
|
Simple example for synchronous message handling:
|
18
|
-
```
|
14
|
+
```rb
|
19
15
|
require 'nsq'
|
20
16
|
|
21
17
|
reader = NSQ::Reader.new(:nsqd_tcp_addresses => '127.0.0.1:4150')
|
@@ -29,14 +25,14 @@ puts 'Reader stopped'
|
|
29
25
|
```
|
30
26
|
|
31
27
|
Advanced example demonstrating asynchronous handling of messages on multiple threads:
|
32
|
-
```
|
28
|
+
```rb
|
33
29
|
require 'nsq'
|
34
30
|
|
35
31
|
foo_worker_count = 50
|
36
32
|
bar_worker_count = 30
|
37
33
|
baz_worker_count = 20
|
38
34
|
|
39
|
-
reader = NSQ::Reader.new(:
|
35
|
+
reader = NSQ::Reader.new(nsqd_tcp_addresses: '127.0.0.1:4150')
|
40
36
|
|
41
37
|
foo_subscriber = reader.subscribe('test', 'foo', :max_in_flight => foo_worker_count)
|
42
38
|
bar_subscriber = reader.subscribe('test2', 'bar', :max_in_flight => bar_worker_count)
|
@@ -72,9 +68,9 @@ baz_threads.each(&:join)
|
|
72
68
|
|
73
69
|
## Meta
|
74
70
|
|
75
|
-
* Code: `git clone git://github.com/
|
76
|
-
* Home: <https://github.com/
|
77
|
-
* Bugs: <http://github.com/
|
71
|
+
* Code: `git clone git://github.com/daddye/ruby_nsq.git`
|
72
|
+
* Home: <https://github.com/daddye/ruby_nsq>
|
73
|
+
* Bugs: <http://github.com/daddye/ruby_nsq/issues>
|
78
74
|
* Gems: <http://rubygems.org/gems/ruby_nsq>
|
79
75
|
|
80
76
|
This project uses [Semantic Versioning](http://semver.org/).
|
@@ -82,3 +78,4 @@ This project uses [Semantic Versioning](http://semver.org/).
|
|
82
78
|
## Authors
|
83
79
|
|
84
80
|
Brad Pardee :: bradpardee@gmail.com
|
81
|
+
Davide D'Agostino (@DAddYE) :: info@daddye.it
|
data/lib/nsq.rb
CHANGED
@@ -1,25 +1,16 @@
|
|
1
|
-
require 'nsq/
|
1
|
+
require 'nsq/util'
|
2
|
+
require 'nsq/logger'
|
2
3
|
require 'nsq/message'
|
3
4
|
require 'nsq/reader'
|
4
5
|
require 'nsq/subscriber'
|
5
6
|
require 'nsq/publisher'
|
6
|
-
require 'nsq/queue_subscriber'
|
7
7
|
require 'nsq/connection'
|
8
8
|
require 'nsq/backoff_timer'
|
9
9
|
require 'nsq/timer'
|
10
|
-
require 'nsq/util'
|
11
10
|
|
12
11
|
module NSQ
|
13
|
-
|
14
|
-
|
15
|
-
MAGIC_V2 = " V2"
|
16
|
-
|
12
|
+
MAGIC_V2 = " V2"
|
17
13
|
FRAME_TYPE_RESPONSE = 0
|
18
14
|
FRAME_TYPE_ERROR = 1
|
19
15
|
FRAME_TYPE_MESSAGE = 2
|
20
|
-
|
21
|
-
def self.create_reader(options, &block) #:nodoc:
|
22
|
-
NSQ.logger.info('NSQ#create_reader has been deprecated, please use NSQ::Reader#new instead')
|
23
|
-
Reader.new(options, &block)
|
24
|
-
end
|
25
16
|
end
|
data/lib/nsq/connection.rb
CHANGED
@@ -4,7 +4,7 @@ require 'thread' #Mutex
|
|
4
4
|
module NSQ
|
5
5
|
# Represents a single subscribed connection to an nsqd server.
|
6
6
|
class Connection
|
7
|
-
|
7
|
+
include NSQ::Logger
|
8
8
|
|
9
9
|
def initialize(reader, subscriber, host, port)
|
10
10
|
@reader = reader
|
@@ -12,7 +12,6 @@ module NSQ
|
|
12
12
|
@selector = reader.selector
|
13
13
|
@host = host
|
14
14
|
@port = port
|
15
|
-
@name = "#{subscriber.name}:#{host}:#{port}"
|
16
15
|
@write_monitor = Monitor.new
|
17
16
|
@ready_mutex = Mutex.new
|
18
17
|
@sending_ready = false
|
@@ -20,18 +19,16 @@ module NSQ
|
|
20
19
|
# Connect states :init, :interval, :connecting, :connected, :closed
|
21
20
|
@connect_state = :init
|
22
21
|
|
23
|
-
@next_connection_time = nil
|
24
|
-
@next_ready_time = nil
|
25
22
|
@connection_backoff_timer = nil
|
26
23
|
@ready_backoff_timer = @subscriber.create_ready_backoff_timer
|
27
24
|
|
28
25
|
connect
|
29
26
|
end
|
30
27
|
|
31
|
-
def send_init(topic, channel
|
28
|
+
def send_init(topic, channel) #:nodoc:
|
32
29
|
write NSQ::MAGIC_V2
|
33
|
-
write "SUB #{topic} #{channel}
|
34
|
-
|
30
|
+
write "SUB #{topic} #{channel}\n"
|
31
|
+
send_ready
|
35
32
|
end
|
36
33
|
|
37
34
|
def send_ready #:nodoc:
|
@@ -73,10 +70,8 @@ module NSQ
|
|
73
70
|
interval = @connection_backoff_timer.interval
|
74
71
|
if interval > 0
|
75
72
|
@connect_state = :interval
|
76
|
-
|
77
|
-
@reader.add_timeout(interval)
|
78
|
-
connect
|
79
|
-
end
|
73
|
+
logger.debug("Reattempting connection in #{interval} seconds")
|
74
|
+
@reader.add_timeout(interval, &method(:connect))
|
80
75
|
else
|
81
76
|
connect
|
82
77
|
end
|
@@ -84,14 +79,14 @@ module NSQ
|
|
84
79
|
end
|
85
80
|
|
86
81
|
def close(permanent=true) #:nodoc:
|
87
|
-
|
82
|
+
logger.debug "Closing..."
|
88
83
|
@write_monitor.synchronize do
|
89
84
|
begin
|
90
85
|
@selector.deregister(@socket)
|
91
86
|
# Use straight socket to write otherwise we need to use Monitor instead of Mutex
|
92
87
|
@socket.write "CLS\n"
|
93
88
|
@socket.close
|
94
|
-
rescue Exception
|
89
|
+
rescue Exception
|
95
90
|
ensure
|
96
91
|
@connect_state = permanent ? :closed : :init
|
97
92
|
@socket = nil
|
@@ -101,21 +96,18 @@ module NSQ
|
|
101
96
|
|
102
97
|
def connect #:nodoc:
|
103
98
|
return unless verify_connect_state?(:init, :interval)
|
104
|
-
|
99
|
+
logger.debug {"#{self}: Beginning connect"}
|
105
100
|
@connect_state = :connecting
|
106
101
|
@buffer = ''
|
107
102
|
@ready_count = 0
|
108
103
|
@socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
109
104
|
@sockaddr = Socket.pack_sockaddr_in(@port, @host)
|
105
|
+
@socket.set_encoding 'UTF-8'
|
110
106
|
@monitor = @selector.register(@socket, :w)
|
111
|
-
@monitor.value =
|
107
|
+
@monitor.value = method(:do_connect)
|
112
108
|
do_connect
|
113
109
|
end
|
114
110
|
|
115
|
-
def to_s #:nodoc:
|
116
|
-
@name
|
117
|
-
end
|
118
|
-
|
119
111
|
private
|
120
112
|
|
121
113
|
def do_connect
|
@@ -124,14 +116,14 @@ module NSQ
|
|
124
116
|
begin
|
125
117
|
@socket.connect_nonblock(@sockaddr)
|
126
118
|
# Apparently we always throw an exception here
|
127
|
-
|
119
|
+
logger.debug {"#{self}: do_connect fell thru without throwing an exception"}
|
128
120
|
rescue Errno::EINPROGRESS
|
129
|
-
|
121
|
+
logger.debug {"#{self}: do_connect - connect in progress"}
|
130
122
|
rescue Errno::EISCONN
|
131
|
-
|
123
|
+
logger.debug {"#{self}: do_connect - connection complete"}
|
132
124
|
@selector.deregister(@socket)
|
133
125
|
monitor = @selector.register(@socket, :r)
|
134
|
-
monitor.value =
|
126
|
+
monitor.value = method(:read_messages)
|
135
127
|
@connect_state = :connected
|
136
128
|
# The assumption for connections is that a good connection means the server is good, no ramping back up like ready counts
|
137
129
|
@connection_backoff_timer = nil
|
@@ -148,11 +140,9 @@ module NSQ
|
|
148
140
|
if interval == 0.0
|
149
141
|
send_ready
|
150
142
|
else
|
151
|
-
|
143
|
+
logger.debug {"#{self}: Delaying READY for #{interval} seconds"}
|
152
144
|
@sending_ready = true
|
153
|
-
@reader.add_timeout(interval)
|
154
|
-
send_ready
|
155
|
-
end
|
145
|
+
@reader.add_timeout(interval, method(:send_ready))
|
156
146
|
end
|
157
147
|
end
|
158
148
|
end
|
@@ -162,29 +152,29 @@ module NSQ
|
|
162
152
|
while @buffer.length >= 8
|
163
153
|
size, frame = @buffer.unpack('NN')
|
164
154
|
break if @buffer.length < 4+size
|
155
|
+
|
165
156
|
case frame
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
raise "Unrecognized message frame: #{frame} buffer=#{@buffer.inspect}"
|
157
|
+
when NSQ::FRAME_TYPE_RESPONSE
|
158
|
+
if @buffer[8,11] == "_heartbeat_"
|
159
|
+
send_nop
|
160
|
+
@subscriber.handle_heartbeat(self)
|
161
|
+
elsif @buffer[8, 2] != "OK"
|
162
|
+
logger.error("I don't know what to do with the rest of this buffer: #{@buffer[8,size-4].inspect}") if @buffer.length > 8
|
163
|
+
end
|
164
|
+
@buffer = @buffer[(4+size)..-1]
|
165
|
+
when NSQ::FRAME_TYPE_ERROR
|
166
|
+
@subscriber.handle_frame_error(self, @buffer[8, size-4])
|
167
|
+
@buffer = @buffer[(4+size)..-1]
|
168
|
+
when NSQ::FRAME_TYPE_MESSAGE
|
169
|
+
raise "Bad message: #{@buffer.inspect}" if size < 30
|
170
|
+
ts_hi, ts_lo, attempts, id = @buffer.unpack('@8NNna16')
|
171
|
+
body = @buffer[34, size-30].force_encoding('UTF-8')
|
172
|
+
message = Message.new(self, id, ts_hi, ts_lo, attempts, body)
|
173
|
+
@buffer = @buffer[(4+size)..-1]
|
174
|
+
logger.debug("Read message=#{message}")
|
175
|
+
@subscriber.handle_message(self, message)
|
176
|
+
else
|
177
|
+
raise "Unrecognized message frame: #{frame} buffer=#{@buffer.inspect}"
|
188
178
|
end
|
189
179
|
end
|
190
180
|
rescue Exception => e
|
@@ -196,7 +186,7 @@ module NSQ
|
|
196
186
|
end
|
197
187
|
|
198
188
|
def write(msg)
|
199
|
-
|
189
|
+
logger.debug("Sending #{msg.inspect}")
|
200
190
|
# We should only ever have one reader but we can have multiple writers
|
201
191
|
@write_monitor.synchronize do
|
202
192
|
@socket.write(msg) if verify_connect_state?(:connected)
|
@@ -204,16 +194,7 @@ module NSQ
|
|
204
194
|
end
|
205
195
|
|
206
196
|
def verify_connect_state?(*states)
|
207
|
-
|
208
|
-
NSQ.logger.error("Unexpected connect state of #{@connect_state}, expected to be in #{states.inspect}\n\t#{caller[0]}")
|
209
|
-
if @connect_state != :closed
|
210
|
-
# Likely in a bug state.
|
211
|
-
# I don't want to get in an endless loop of exceptions. Is this a good idea or bad? Maybe close to deregister first
|
212
|
-
# Attempt recovery
|
213
|
-
@connect_state = :init
|
214
|
-
connect
|
215
|
-
end
|
216
|
-
return false
|
197
|
+
states.include?(@connect_state)
|
217
198
|
end
|
218
199
|
end
|
219
200
|
end
|
data/lib/nsq/error.rb
ADDED
data/lib/nsq/logger.rb
ADDED
data/lib/nsq/message.rb
CHANGED
data/lib/nsq/publisher.rb
CHANGED
@@ -1,41 +1,69 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'nsq/error'
|
2
3
|
|
3
4
|
module NSQ
|
4
5
|
class Publisher
|
6
|
+
|
7
|
+
SIZE_BYTES = 4
|
8
|
+
|
5
9
|
def initialize(host, port, options={}, &block)
|
6
10
|
@socket = TCPSocket.open(host, port)
|
7
11
|
@socket.write(MAGIC_V2)
|
8
|
-
|
9
|
-
yield self if block_given?
|
12
|
+
block[self] if block_given?
|
10
13
|
ensure
|
11
14
|
close if block_given?
|
12
15
|
end
|
13
16
|
|
14
17
|
def publish(topic, message)
|
15
|
-
buf = ['PUB ', topic, "\n", message.
|
18
|
+
buf = ['PUB ', topic, "\n", message.bytesize, message].pack('a*a*a*Na*')
|
16
19
|
@socket.write(buf)
|
20
|
+
|
17
21
|
response = ''
|
22
|
+
have_received_ok = false
|
18
23
|
loop do
|
19
|
-
response
|
20
|
-
|
21
|
-
if response.length == size+4
|
22
|
-
case msg
|
23
|
-
when 'OK' then return
|
24
|
-
when '_heartbeat_' then response = ""
|
25
|
-
when 'E_INVALID' then raise 'Invalid message'
|
26
|
-
when 'E_BAD_TOPIC' then raise 'Bad topic'
|
27
|
-
when 'E_BAD_MESSAGE' then raise 'Bad message'
|
28
|
-
when 'E_PUT_FAILED' then raise 'Put failed'
|
29
|
-
else raise "Unknown PUB response: #{msg}"
|
30
|
-
end
|
31
|
-
elsif response.length > size+4
|
32
|
-
raise "Unexpected PUB response - Expected size = #{size} actual size = #{response.length-4}: message=#{msg}"
|
24
|
+
until response.size >= expected_size(response)
|
25
|
+
response += @socket.recv(4096)
|
33
26
|
end
|
27
|
+
|
28
|
+
size = expected_size(response)
|
29
|
+
|
30
|
+
# Extract the first message from `response`.
|
31
|
+
first_message = response.slice!(0, size)
|
32
|
+
_, _, data = first_message.unpack("NNa#{size}")
|
33
|
+
have_received_ok ||= handle(data)
|
34
|
+
|
35
|
+
# If the message was "OK", we can return successfully when the buffer
|
36
|
+
# is empty.
|
37
|
+
return if response.empty? && have_received_ok
|
38
|
+
|
39
|
+
# We are now in a situation where we have processed a message, but we
|
40
|
+
# must process more. Either it was an OK but we have partially read
|
41
|
+
# another message that we must finish, or it was a heartbeat and we
|
42
|
+
# must read again until we get an OK or an error code.
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
37
46
|
def close
|
38
|
-
@socket.close
|
47
|
+
@socket.close if @socket
|
39
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def expected_size(response)
|
52
|
+
return 8 if response.size < 8
|
53
|
+
SIZE_BYTES + response.unpack('N')[0]
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle(msg)
|
57
|
+
case msg
|
58
|
+
when 'OK' ; return true
|
59
|
+
when '_heartbeat_' ; return false
|
60
|
+
when 'E_INVALID' ; raise NSQ::Error::Invalid
|
61
|
+
when 'E_BAD_TOPIC' ; raise NSQ::Error::BadTopic
|
62
|
+
when 'E_BAD_MESSAGE' ; raise NSQ::Error::BadMessage
|
63
|
+
when 'E_PUT_FAILED' ; raise NSQ::Error::PutFailed
|
64
|
+
else raise NSQ::Error, "Unknown PUB response: #{msg}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
40
68
|
end
|
41
69
|
end
|
data/lib/nsq/reader.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'thread'
|
3
|
-
require 'monitor'
|
4
1
|
require 'nio'
|
2
|
+
require 'thread_safe'
|
3
|
+
require 'atomic'
|
5
4
|
|
6
5
|
module NSQ
|
7
6
|
# Maintains a collection of subscribers to topics and channels.
|
8
7
|
class Reader
|
9
|
-
|
8
|
+
include NSQ::Util
|
9
|
+
include NSQ::Logger
|
10
|
+
|
11
|
+
attr_reader :selector, :options
|
10
12
|
|
11
13
|
# Create a new NSQ Reader
|
12
14
|
#
|
@@ -15,45 +17,26 @@ module NSQ
|
|
15
17
|
# Array of nsqd servers to connect to with port numbers
|
16
18
|
# ['server1:4150', 'server2:4150']
|
17
19
|
#
|
18
|
-
# :
|
19
|
-
#
|
20
|
-
# ['server1:4160', 'server2:4160']
|
21
|
-
#
|
22
|
-
# :lookupd_poll_interval [Float] (Not implemented)
|
23
|
-
# How often to poll the lookupd_tcp_addresses for new nsqd servers
|
24
|
-
# Default: 120
|
25
|
-
#
|
26
|
-
# :long_id [String]
|
27
|
-
# The identifier used as a long-form descriptor
|
28
|
-
# Default: fully-qualified hostname
|
20
|
+
# :logger [Logger]
|
21
|
+
# The Logger class
|
29
22
|
#
|
30
|
-
# :
|
31
|
-
# The
|
32
|
-
# Default: short hostname
|
23
|
+
# :logger_level [Symbol]
|
24
|
+
# The Logger Level [:info, :debug, :warn, :error]
|
33
25
|
#
|
34
26
|
def initialize(options={})
|
35
|
-
@options
|
36
|
-
@nsqd_tcp_addresses
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@
|
45
|
-
@
|
46
|
-
|
47
|
-
@
|
48
|
-
@subscriber_mutex = Monitor.new
|
49
|
-
@name = "#{@long_id}:#{@short_id}"
|
50
|
-
|
51
|
-
raise 'Must pass either option :nsqd_tcp_addresses or :lookupd_http_addresses' if @nsqd_tcp_addresses.empty? && @lookupd_http_addresses.empty?
|
52
|
-
|
53
|
-
@conns = {}
|
54
|
-
@last_lookup = nil
|
55
|
-
|
56
|
-
@logger.info("starting reader for topic '%s'..." % self.topic) if @logger
|
27
|
+
@options = options
|
28
|
+
@nsqd_tcp_addresses = Array(options[:nsqd_tcp_addresses])
|
29
|
+
|
30
|
+
logger = options[:logger] if options[:logger]
|
31
|
+
logger.level = options[:logger_level] if options[:logger_level]
|
32
|
+
|
33
|
+
@selector = ::NIO::Selector.new
|
34
|
+
@timer = Timer.new(@selector)
|
35
|
+
@subscribers = ThreadSafe::Cache.new
|
36
|
+
@stop = Atomic.new(false)
|
37
|
+
@running = Atomic.new(false)
|
38
|
+
|
39
|
+
raise 'Must pass either option :nsqd_tcp_addresses' if @nsqd_tcp_addresses.empty?
|
57
40
|
end
|
58
41
|
|
59
42
|
# Subscribes to a given topic and channel.
|
@@ -67,68 +50,63 @@ module NSQ
|
|
67
50
|
# Refer to Subscriber::new for the options that can be passed to this method.
|
68
51
|
#
|
69
52
|
def subscribe(topic, channel, options={}, &block)
|
70
|
-
|
53
|
+
assert_topic_and_channel_valid(topic, channel)
|
71
54
|
subscriber = nil
|
72
|
-
name
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
subscriber = @subscribers[name] = subscriber_class.new(self, topic, channel, options, &block)
|
77
|
-
end
|
55
|
+
name = "#{topic}:#{channel}"
|
56
|
+
|
57
|
+
raise "Already subscribed to #{name}" if @subscribers[name]
|
58
|
+
subscriber = @subscribers[name] = Subscriber.new(self, topic, channel, options, &block)
|
78
59
|
|
79
60
|
@nsqd_tcp_addresses.each do |addr|
|
80
61
|
address, port = addr.split(':')
|
81
62
|
subscriber.add_connection(address, port.to_i)
|
82
63
|
end
|
64
|
+
|
83
65
|
subscriber
|
84
66
|
end
|
85
67
|
|
86
68
|
# Unsubscribe a given topic and channel.
|
87
69
|
def unsubscribe(topic, channel)
|
88
70
|
name = "#{topic}:#{channel}"
|
89
|
-
@
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
71
|
+
subscriber = @subscribers[name]
|
72
|
+
return unless subscriber
|
73
|
+
subscriber.stop
|
74
|
+
@subscribers.delete(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def stopped?
|
78
|
+
@stop.value
|
79
|
+
end
|
80
|
+
|
81
|
+
def running?
|
82
|
+
@running.value
|
95
83
|
end
|
96
84
|
|
97
85
|
# Processes all the messages from the subscribed connections. This will not return until #stop
|
98
86
|
# has been called in a separate thread.
|
99
87
|
def run
|
100
|
-
@
|
101
|
-
until
|
102
|
-
if (Time.now.to_i - @last_lookup.to_i) > @lookupd_poll_interval
|
103
|
-
# Do lookupd
|
104
|
-
end
|
88
|
+
@running.value = true # we can't run from multiple threads so this is fine
|
89
|
+
until stopped?
|
105
90
|
@selector.select(@timer.next_interval) { |m| m.value.call }
|
106
91
|
end
|
92
|
+
ensure
|
93
|
+
@running.value = false
|
107
94
|
end
|
108
95
|
|
109
96
|
# Stop this reader which will gracefully exit the run method after all current messages are processed.
|
110
97
|
def stop
|
111
|
-
|
112
|
-
@
|
98
|
+
logger.debug("#{self}: Reader stopping...")
|
99
|
+
@stop.try_update { |m| m = true }
|
100
|
+
@running.try_update { |m| m = false }
|
113
101
|
@selector.wakeup
|
114
|
-
@
|
115
|
-
|
116
|
-
|
102
|
+
@subscribers.each_value(&:stop)
|
103
|
+
rescue Atomic::ConcurrentUpdateError
|
104
|
+
retry
|
117
105
|
end
|
118
106
|
|
119
107
|
# Call the given block from within the #run thread when the given interval has passed.
|
120
108
|
def add_timeout(interval, &block)
|
121
109
|
@timer.add(interval, &block)
|
122
110
|
end
|
123
|
-
|
124
|
-
def to_s #:nodoc:
|
125
|
-
@name
|
126
|
-
end
|
127
|
-
|
128
|
-
private
|
129
|
-
|
130
|
-
def s_to_a(val)
|
131
|
-
val.kind_of?(String) ? [val] : val
|
132
|
-
end
|
133
111
|
end
|
134
112
|
end
|
data/lib/nsq/subscriber.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module NSQ
|
2
2
|
class Subscriber
|
3
|
-
|
3
|
+
include NSQ::Logger
|
4
|
+
|
5
|
+
attr_reader :selector
|
4
6
|
attr_accessor :max_in_flight
|
5
7
|
|
6
8
|
# Creates a new subscriber which maintain connections to all the nsqd instances which publish
|
@@ -52,7 +54,6 @@ module NSQ
|
|
52
54
|
#
|
53
55
|
def initialize(reader, topic, channel, options, &block)
|
54
56
|
options = reader.options.merge(options)
|
55
|
-
@name = "#{reader.name}:#{topic}:#{channel}"
|
56
57
|
@reader = reader
|
57
58
|
@selector = reader.selector
|
58
59
|
@topic = topic
|
@@ -118,9 +119,7 @@ module NSQ
|
|
118
119
|
# Stop this subscriber
|
119
120
|
def stop
|
120
121
|
@stopped = true
|
121
|
-
@connection_hash.each_value
|
122
|
-
connection.close
|
123
|
-
end
|
122
|
+
@connection_hash.each_value(&:close)
|
124
123
|
@connection_hash.clear
|
125
124
|
end
|
126
125
|
|
@@ -130,7 +129,7 @@ module NSQ
|
|
130
129
|
end
|
131
130
|
|
132
131
|
def handle_connection(connection) #:nodoc:
|
133
|
-
connection.send_init(@topic, @channel
|
132
|
+
connection.send_init(@topic, @channel)
|
134
133
|
end
|
135
134
|
|
136
135
|
def handle_heartbeat(connection) #:nodoc:
|
@@ -141,30 +140,26 @@ module NSQ
|
|
141
140
|
end
|
142
141
|
|
143
142
|
def process_message(connection, message, &block) #:nodoc:
|
144
|
-
|
143
|
+
block[message]
|
145
144
|
connection.send_finish(message.id, true)
|
146
145
|
rescue Exception => e
|
147
|
-
|
148
|
-
if @max_tries && attempts >= @max_tries
|
149
|
-
|
146
|
+
logger.error("Exception during handle_message: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
|
147
|
+
if @max_tries && message.attempts >= @max_tries
|
148
|
+
logger.warning("Giving up on message after #{@max_tries} tries: #{message.body.inspect}")
|
150
149
|
connection.send_finish(message.id, false)
|
151
150
|
else
|
152
|
-
connection.send_requeue(message.id, attempts * @requeue_delay)
|
151
|
+
connection.send_requeue(message.id, message.attempts * @requeue_delay)
|
153
152
|
end
|
154
153
|
end
|
155
154
|
|
156
155
|
def handle_frame_error(connection, error_message) #:nodoc:
|
157
|
-
|
156
|
+
logger.error("Received error from nsqd: #{error_message.inspect}")
|
158
157
|
connection.reset
|
159
158
|
end
|
160
159
|
|
161
160
|
def handle_io_error(connection, exception) #:nodoc:
|
162
|
-
|
161
|
+
logger.error("Socket error: #{exception.message}\n\t#{exception.backtrace[0,2].join("\n\t")}")
|
163
162
|
connection.reset
|
164
163
|
end
|
165
|
-
|
166
|
-
def to_s #:nodoc:
|
167
|
-
@name
|
168
|
-
end
|
169
164
|
end
|
170
165
|
end
|
data/lib/nsq/util.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module NSQ
|
2
2
|
module Util
|
3
3
|
|
4
|
-
def
|
4
|
+
def assert_topic_and_channel_valid(topic, channel) #:nodoc:
|
5
5
|
raise "Invalid topic #{topic}" unless valid_topic_name?(topic)
|
6
6
|
raise "Invalid channel #{channel}" unless valid_channel_name?(channel)
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def valid_topic_name?(topic) #:nodoc:
|
10
10
|
!!topic.match(/^[\.a-zA-Z0-9_-]+$/)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def valid_channel_name?(channel) #:nodoc:
|
14
14
|
!!channel.match(/^[\.a-zA-Z0-9_-]+(#ephemeral)?$/)
|
15
15
|
end
|
16
16
|
end
|
metadata
CHANGED
@@ -1,145 +1,174 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_nsq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brad Pardee
|
8
|
+
- DAddYE (Davide D'Agostino)
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2014-07-25 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: nio4r
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
16
17
|
requirements:
|
17
|
-
- -
|
18
|
+
- - ">="
|
18
19
|
- !ruby/object:Gem::Version
|
19
20
|
version: '0'
|
20
21
|
type: :runtime
|
21
22
|
prerelease: false
|
22
23
|
version_requirements: !ruby/object:Gem::Requirement
|
23
24
|
requirements:
|
24
|
-
- -
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: thread_safe
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: atomic
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
25
54
|
- !ruby/object:Gem::Version
|
26
55
|
version: '0'
|
27
56
|
- !ruby/object:Gem::Dependency
|
28
57
|
name: rdoc
|
29
58
|
requirement: !ruby/object:Gem::Requirement
|
30
59
|
requirements:
|
31
|
-
- -
|
60
|
+
- - ">="
|
32
61
|
- !ruby/object:Gem::Version
|
33
62
|
version: '0'
|
34
63
|
type: :development
|
35
64
|
prerelease: false
|
36
65
|
version_requirements: !ruby/object:Gem::Requirement
|
37
66
|
requirements:
|
38
|
-
- -
|
67
|
+
- - ">="
|
39
68
|
- !ruby/object:Gem::Version
|
40
69
|
version: '0'
|
41
70
|
- !ruby/object:Gem::Dependency
|
42
71
|
name: rake
|
43
72
|
requirement: !ruby/object:Gem::Requirement
|
44
73
|
requirements:
|
45
|
-
- -
|
74
|
+
- - ">="
|
46
75
|
- !ruby/object:Gem::Version
|
47
76
|
version: '0'
|
48
77
|
type: :development
|
49
78
|
prerelease: false
|
50
79
|
version_requirements: !ruby/object:Gem::Requirement
|
51
80
|
requirements:
|
52
|
-
- -
|
81
|
+
- - ">="
|
53
82
|
- !ruby/object:Gem::Version
|
54
83
|
version: '0'
|
55
84
|
- !ruby/object:Gem::Dependency
|
56
85
|
name: minitest
|
57
86
|
requirement: !ruby/object:Gem::Requirement
|
58
87
|
requirements:
|
59
|
-
- -
|
88
|
+
- - ">="
|
60
89
|
- !ruby/object:Gem::Version
|
61
90
|
version: '0'
|
62
91
|
type: :development
|
63
92
|
prerelease: false
|
64
93
|
version_requirements: !ruby/object:Gem::Requirement
|
65
94
|
requirements:
|
66
|
-
- -
|
95
|
+
- - ">="
|
67
96
|
- !ruby/object:Gem::Version
|
68
97
|
version: '0'
|
69
98
|
- !ruby/object:Gem::Dependency
|
70
99
|
name: turn
|
71
100
|
requirement: !ruby/object:Gem::Requirement
|
72
101
|
requirements:
|
73
|
-
- -
|
102
|
+
- - ">="
|
74
103
|
- !ruby/object:Gem::Version
|
75
104
|
version: '0'
|
76
105
|
type: :development
|
77
106
|
prerelease: false
|
78
107
|
version_requirements: !ruby/object:Gem::Requirement
|
79
108
|
requirements:
|
80
|
-
- -
|
109
|
+
- - ">="
|
81
110
|
- !ruby/object:Gem::Version
|
82
111
|
version: '0'
|
83
112
|
- !ruby/object:Gem::Dependency
|
84
113
|
name: wirble
|
85
114
|
requirement: !ruby/object:Gem::Requirement
|
86
115
|
requirements:
|
87
|
-
- -
|
116
|
+
- - ">="
|
88
117
|
- !ruby/object:Gem::Version
|
89
118
|
version: '0'
|
90
119
|
type: :development
|
91
120
|
prerelease: false
|
92
121
|
version_requirements: !ruby/object:Gem::Requirement
|
93
122
|
requirements:
|
94
|
-
- -
|
123
|
+
- - ">="
|
95
124
|
- !ruby/object:Gem::Version
|
96
125
|
version: '0'
|
97
126
|
- !ruby/object:Gem::Dependency
|
98
127
|
name: hirb
|
99
128
|
requirement: !ruby/object:Gem::Requirement
|
100
129
|
requirements:
|
101
|
-
- -
|
130
|
+
- - ">="
|
102
131
|
- !ruby/object:Gem::Version
|
103
132
|
version: '0'
|
104
133
|
type: :development
|
105
134
|
prerelease: false
|
106
135
|
version_requirements: !ruby/object:Gem::Requirement
|
107
136
|
requirements:
|
108
|
-
- -
|
137
|
+
- - ">="
|
109
138
|
- !ruby/object:Gem::Version
|
110
139
|
version: '0'
|
111
140
|
description: Ruby client for the NSQ realtime message processing system
|
112
141
|
email:
|
113
142
|
- bradpardee@gmail.com
|
143
|
+
- info@daddye.it
|
114
144
|
executables: []
|
115
145
|
extensions: []
|
116
146
|
extra_rdoc_files: []
|
117
147
|
files:
|
148
|
+
- History.md
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- examples/async/README
|
153
|
+
- examples/async/publisher.rb
|
154
|
+
- examples/async/reader.rb
|
155
|
+
- examples/simple/README
|
156
|
+
- examples/simple/publisher.rb
|
157
|
+
- examples/simple/reader.rb
|
158
|
+
- lib/nsq.rb
|
118
159
|
- lib/nsq/backoff_timer.rb
|
119
160
|
- lib/nsq/connection.rb
|
120
|
-
- lib/nsq/
|
161
|
+
- lib/nsq/error.rb
|
162
|
+
- lib/nsq/logger.rb
|
121
163
|
- lib/nsq/message.rb
|
122
164
|
- lib/nsq/publisher.rb
|
123
|
-
- lib/nsq/queue_subscriber.rb
|
124
165
|
- lib/nsq/reader.rb
|
125
166
|
- lib/nsq/subscriber.rb
|
126
167
|
- lib/nsq/timer.rb
|
127
168
|
- lib/nsq/util.rb
|
128
|
-
- lib/nsq.rb
|
129
169
|
- lib/ruby_nsq.rb
|
130
|
-
- examples/async/publisher.rb
|
131
|
-
- examples/async/reader.rb
|
132
|
-
- examples/async/README
|
133
|
-
- examples/simple/publisher.rb
|
134
|
-
- examples/simple/reader.rb
|
135
|
-
- examples/simple/README
|
136
|
-
- LICENSE.txt
|
137
|
-
- Rakefile
|
138
|
-
- History.md
|
139
|
-
- README.md
|
140
170
|
- test/backoff_timer_test.rb
|
141
|
-
|
142
|
-
homepage: http://github.com/ClarityServices/ruby_nsq
|
171
|
+
homepage: https://github.com/bpardee/ruby_nsq.git
|
143
172
|
licenses: []
|
144
173
|
metadata: {}
|
145
174
|
post_install_message:
|
@@ -148,20 +177,19 @@ require_paths:
|
|
148
177
|
- lib
|
149
178
|
required_ruby_version: !ruby/object:Gem::Requirement
|
150
179
|
requirements:
|
151
|
-
- -
|
180
|
+
- - ">="
|
152
181
|
- !ruby/object:Gem::Version
|
153
182
|
version: '0'
|
154
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
184
|
requirements:
|
156
|
-
- -
|
185
|
+
- - ">="
|
157
186
|
- !ruby/object:Gem::Version
|
158
187
|
version: '0'
|
159
188
|
requirements: []
|
160
189
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.
|
190
|
+
rubygems_version: 2.2.2
|
162
191
|
signing_key:
|
163
192
|
specification_version: 4
|
164
193
|
summary: Ruby client for NSQ
|
165
194
|
test_files:
|
166
195
|
- test/backoff_timer_test.rb
|
167
|
-
- test/sync_connection_test.rb.tbd
|
data/lib/nsq/loggable.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module NSQ
|
2
|
-
module Loggable
|
3
|
-
def logger
|
4
|
-
@logger ||= (rails_logger || default_logger)
|
5
|
-
end
|
6
|
-
|
7
|
-
def rails_logger
|
8
|
-
(defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
|
9
|
-
(defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
|
10
|
-
end
|
11
|
-
|
12
|
-
def default_logger
|
13
|
-
require 'logger'
|
14
|
-
l = Logger.new($stdout)
|
15
|
-
l.level = Logger::INFO
|
16
|
-
l
|
17
|
-
end
|
18
|
-
|
19
|
-
def logger=(logger)
|
20
|
-
@logger = logger
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/lib/nsq/queue_subscriber.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'thread' #Mutex
|
2
|
-
|
3
|
-
module NSQ
|
4
|
-
# An asynchronous subscriber that can be run on multiple threads for reading messages from a subscribed channel.
|
5
|
-
class QueueSubscriber < Subscriber
|
6
|
-
def initialize(reader, topic, channel, options) #:nodoc:
|
7
|
-
super
|
8
|
-
@queue = Queue.new
|
9
|
-
@run_mutex = Mutex.new
|
10
|
-
@run_count = 0
|
11
|
-
end
|
12
|
-
|
13
|
-
def ready_count #:nodoc:
|
14
|
-
# Return the minimum of Subscriber#ready_count and the amount of space left in the queue
|
15
|
-
[super, self.max_in_flight - @queue.size].min
|
16
|
-
end
|
17
|
-
|
18
|
-
def handle_message(connection, message) #:nodoc:
|
19
|
-
@queue << [connection, message]
|
20
|
-
end
|
21
|
-
|
22
|
-
# Processes messages from the subscribed connections. This will not return until #stop
|
23
|
-
# has been called in a separate thread. This can be called from multiple threads if you
|
24
|
-
# want multiple workers handling the incoming messages.
|
25
|
-
def run(&block)
|
26
|
-
@run_mutex.synchronize { @run_count += 1}
|
27
|
-
until @stopped
|
28
|
-
pair = @queue.pop
|
29
|
-
if pair == :stop
|
30
|
-
# Give the next thread something to pop
|
31
|
-
@queue << :stop
|
32
|
-
return
|
33
|
-
end
|
34
|
-
connection, message = pair
|
35
|
-
process_message(connection, message, &block)
|
36
|
-
end
|
37
|
-
ensure
|
38
|
-
@run_mutex.synchronize { @run_count -= 1}
|
39
|
-
end
|
40
|
-
|
41
|
-
# Stop this subscriber once all the queued messages have been handled.
|
42
|
-
def stop
|
43
|
-
@stopped = true
|
44
|
-
# Give the threads something to pop
|
45
|
-
@queue << :stop
|
46
|
-
# TODO: Put a max time on this so we don't potentially hang
|
47
|
-
sleep 1 while @run_count > 0
|
48
|
-
super
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,12 +0,0 @@
|
|
1
|
-
|
2
|
-
c = SyncConn()
|
3
|
-
c.connect("127.0.0.1", 4150)
|
4
|
-
c.send(nsq.subscribe('test', 'ch', 'a', 'b'))
|
5
|
-
10.times do
|
6
|
-
c.send(nsq.ready(1))
|
7
|
-
resp = c.read_response()
|
8
|
-
unpacked = nsq.unpack_response(resp)
|
9
|
-
msg = nsq.decode_message(unpacked[1])
|
10
|
-
print msg.id, msg.body
|
11
|
-
c.send(nsq.finish(msg.id))
|
12
|
-
end
|