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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9579c1a3a9b590d345635b1ad0118dc33e5fbd97
4
- data.tar.gz: c2a3fe655159c07d68e8e9a05212f71050ec7457
3
+ metadata.gz: 52265e899f8f90847c965d85f1ebad0ecc76994c
4
+ data.tar.gz: e12d1e4f0ac44dd1faf07905ff505c22cb0facd4
5
5
  SHA512:
6
- metadata.gz: d28cb948f57d9606e1301f460ee3dd6f7ac948e6c3c7f537fa63568097f6009ec7cb8101e33286f1c6ab4336321e1112f7fe9a498c8e03e94e0d7e143503ede3
7
- data.tar.gz: 9f2ea7faedaecd1d4ea0f4568dfe38173c0e6f53b008874605cb3269362bda7a4ca33e0d225cff17302a16ae372c6dd2f86f933fb164dc866a35b9d481735d40
6
+ metadata.gz: f0890442271e47716b204b4412fa06b7294b9f70a9273ba22058f7844bd9dd1948d9e06fc94ce65be0658ca6f8bc0dbe9ea2ef19e3cdcf146bc86ae68fab82f9
7
+ data.tar.gz: ff7f950a74306eece1c5c77e34a8ab78869bbf1a651aef66cc568870c6600b1c11a0f5a6452f478843b56a034f18639dfc459e562c4297f85c98d2300eec3f50
data/History.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ 0.0.4
5
+ -----
6
+
7
+ - Refactoring
8
+ - Bug fixes
9
+
4
10
  0.0.3
5
11
  -----
6
12
 
data/README.md CHANGED
@@ -1,8 +1,4 @@
1
- # ruby_nsq
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(:nsqd_tcp_addresses => '127.0.0.1:4150')
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/ClarityServices/ruby_nsq.git`
76
- * Home: <https://github.com/ClarityServices/ruby_nsq>
77
- * Bugs: <http://github.com/ClarityServices/ruby_nsq/issues>
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/loggable'
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
- extend NSQ::Loggable
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
@@ -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
- attr_reader :name
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, short_id, long_id) #:nodoc:
28
+ def send_init(topic, channel) #:nodoc:
32
29
  write NSQ::MAGIC_V2
33
- write "SUB #{topic} #{channel} #{short_id} #{long_id}\n"
34
- self.send_ready
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
- NSQ.logger.debug {"#{self}: Reattempting connection in #{interval} seconds"}
77
- @reader.add_timeout(interval) do
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
- NSQ.logger.debug {"#{@name}: Closing..."}
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 => e
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
- NSQ.logger.debug {"#{self}: Beginning connect"}
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 = proc { do_connect }
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
- NSQ.logger.debug {"#{self}: do_connect fell thru without throwing an exception"}
119
+ logger.debug {"#{self}: do_connect fell thru without throwing an exception"}
128
120
  rescue Errno::EINPROGRESS
129
- NSQ.logger.debug {"#{self}: do_connect - connect in progress"}
121
+ logger.debug {"#{self}: do_connect - connect in progress"}
130
122
  rescue Errno::EISCONN
131
- NSQ.logger.debug {"#{self}: do_connect - connection complete"}
123
+ logger.debug {"#{self}: do_connect - connection complete"}
132
124
  @selector.deregister(@socket)
133
125
  monitor = @selector.register(@socket, :r)
134
- monitor.value = proc { read_messages }
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
- NSQ.logger.debug {"#{self}: Delaying READY for #{interval} seconds"}
143
+ logger.debug {"#{self}: Delaying READY for #{interval} seconds"}
152
144
  @sending_ready = true
153
- @reader.add_timeout(interval) do
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
- when NSQ::FRAME_TYPE_RESPONSE
167
- if @buffer[8,11] == "_heartbeat_"
168
- send_nop
169
- @subscriber.handle_heartbeat(self)
170
- @buffer = @buffer[(4+size)..-1]
171
- else
172
- NSQ.logger.error("I don't know what to do with the rest of this buffer: #{@buffer[8,size-4].inspect}") if @buffer.length > 8
173
- @buffer = @buffer[(4+size)..-1]
174
- end
175
- when NSQ::FRAME_TYPE_ERROR
176
- @subscriber.handle_frame_error(self, @buffer[8, size-4])
177
- @buffer = @buffer[(4+size)..-1]
178
- when NSQ::FRAME_TYPE_MESSAGE
179
- raise "Bad message: #{@buffer.inspect}" if size < 30
180
- ts_hi, ts_lo, attempts, id = @buffer.unpack('@8NNna16')
181
- body = @buffer[34, size-30]
182
- message = Message.new(self, id, ts_hi, ts_lo, attempts, body)
183
- @buffer = @buffer[(4+size)..-1]
184
- NSQ.logger.debug {"#{self}: Read message=#{message}"}
185
- @subscriber.handle_message(self, message)
186
- else
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
- NSQ.logger.debug {"#{@name}: Sending #{msg.inspect}"}
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
- return true if states.include?(@connect_state)
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
@@ -0,0 +1,8 @@
1
+ module NSQ
2
+ class Error < StandardError
3
+ Invalid = Class.new(self)
4
+ BadTopic = Class.new(self)
5
+ BadMessage = Class.new(self)
6
+ PutFailed = Class.new(self)
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module NSQ
4
+ module Logger
5
+ def logger
6
+ @_logger ||=
7
+ begin
8
+ l = ::Logger.new(STDOUT)
9
+ l.level = ::Logger::INFO
10
+ l
11
+ end
12
+ end
13
+
14
+ def logger=(logger)
15
+ @_logger = logger
16
+ end
17
+ end
18
+ end
@@ -5,7 +5,6 @@ module NSQ
5
5
  def initialize(connection, id, timestamp_high, timestamp_low, attempts, body)
6
6
  @connection = connection
7
7
  @id = id
8
- @timestamp_high = timestamp_high
9
8
  @timestamp_low = timestamp_low
10
9
  @attempts = attempts
11
10
  @body = body
@@ -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
- @response_timeout = options[:response_timeout] || 5
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.length, message].pack('a*a*a*Na*')
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 += @socket.recv(4096)
20
- size, frame, msg = response.unpack('NNa*')
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
@@ -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
- attr_reader :name, :long_id, :short_id, :selector, :options
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
- # :lookupd_tcp_addresses [String or Array of Strings] (Not implemented)
19
- # Array of nsq_lookupd servers to connect to with port numbers
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
- # :short_id [String]
31
- # The identifier used as a short-form descriptor
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 = options
36
- @nsqd_tcp_addresses = s_to_a(options[:nsqd_tcp_addresses])
37
- @lookupd_tcp_addresses = s_to_a(options[:lookupd_tcp_addresses])
38
- @lookupd_poll_interval = options[:lookupd_poll_interval] || 120
39
- @long_id = options[:long_id] || Socket.gethostname
40
- @short_id = options[:short_id] || @long_id.split('.')[0]
41
- NSQ.logger = options[:logger] if options[:logger]
42
- NSQ.logger.level = options[:logger_level] if options[:logger_level]
43
-
44
- @selector = ::NIO::Selector.new
45
- @timer = Timer.new(@selector)
46
- @topic_count = Hash.new(0)
47
- @subscribers = {}
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
- Util.assert_topic_and_channel_valid(topic, channel)
53
+ assert_topic_and_channel_valid(topic, channel)
71
54
  subscriber = nil
72
- name = "#{topic}:#{channel}"
73
- @subscriber_mutex.synchronize do
74
- raise "Already subscribed to #{name}" if @subscribers[name]
75
- subscriber_class = block_given? ? Subscriber : QueueSubscriber
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
- @subscriber_mutex.synchronize do
90
- subscriber = @subscribers[name]
91
- return unless subscriber
92
- subscriber.stop
93
- @subscribers.delete(name)
94
- end
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
- @stopped = false
101
- until @stopped do
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
- NSQ.logger.info("#{self}: Reader stopping...")
112
- @stopped = true
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
- @subscriber_mutex.synchronize do
115
- @subscribers.each_value {|subscriber| subscriber.stop}
116
- end
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
@@ -1,6 +1,8 @@
1
1
  module NSQ
2
2
  class Subscriber
3
- attr_reader :selector, :name
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 do |connection|
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, @reader.short_id, @reader.long_id)
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
- yield message
143
+ block[message]
145
144
  connection.send_finish(message.id, true)
146
145
  rescue Exception => e
147
- NSQ.logger.error("#{connection.name}: Exception during handle_message: #{e.message}\n\t#{e.backtrace.join("\n\t")}")
148
- if @max_tries && attempts >= @max_tries
149
- NSQ.logger.warning("#{connection.name}: Giving up on message after #{@max_tries} tries: #{body.inspect}")
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
- NSQ.logger.error("Received error from nsqd: #{error_message.inspect}")
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
- NSQ.logger.error("Socket error: #{exception.message}\n\t#{exception.backtrace[0,2].join("\n\t")}")
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
@@ -1,16 +1,16 @@
1
1
  module NSQ
2
2
  module Util
3
3
 
4
- def self.assert_topic_and_channel_valid(topic, channel) #:nodoc:
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 self.valid_topic_name?(topic) #:nodoc:
9
+ def valid_topic_name?(topic) #:nodoc:
10
10
  !!topic.match(/^[\.a-zA-Z0-9_-]+$/)
11
11
  end
12
12
 
13
- def self.valid_channel_name?(channel) #:nodoc:
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.3
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: 2013-07-02 00:00:00.000000000 Z
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/loggable.rb
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
- - test/sync_connection_test.rb.tbd
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.0.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
@@ -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
@@ -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