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 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