ruby_nsq 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|