fastly_nsq 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +25 -10
- data/fastly_nsq.gemspec +1 -0
- data/lib/fastly_nsq.rb +76 -0
- data/lib/fastly_nsq/cli.rb +14 -0
- data/lib/fastly_nsq/consumer.rb +73 -3
- data/lib/fastly_nsq/feeder.rb +22 -1
- data/lib/fastly_nsq/http.rb +10 -0
- data/lib/fastly_nsq/http/nsqd.rb +5 -0
- data/lib/fastly_nsq/http/nsqlookupd.rb +5 -0
- data/lib/fastly_nsq/launcher.rb +9 -0
- data/lib/fastly_nsq/listener.rb +80 -2
- data/lib/fastly_nsq/manager.rb +43 -1
- data/lib/fastly_nsq/message.rb +32 -1
- data/lib/fastly_nsq/messenger.rb +25 -0
- data/lib/fastly_nsq/testing.rb +49 -0
- data/lib/fastly_nsq/tls_options.rb +3 -0
- data/lib/fastly_nsq/version.rb +1 -1
- data/spec/fastly_nsq_spec.rb +16 -0
- data/spec/launcher_spec.rb +44 -4
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e9dbcfdeefbc796bb32482bd3ab9806b548284d4
|
4
|
+
data.tar.gz: 7d0257332788312bbbbad212e3060fe3ae6a79ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0e7305d476a86045538ffe42ebda32a6998edd64df5535487c2acec9a003a2d5b1bff4720cb3d13fa34d68fcb94750b1a1b82bd550086a3f08b51efaa9d8353
|
7
|
+
data.tar.gz: 0715abca959a7d08400aeb4f5f233c37c61912e172cccd0cab7792d0e4763193f53751716a346b240ea6ffb16cd133da307df0e72f7e97b43d23767401f5cd64
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -24,6 +24,7 @@ Please use [GitHub Issues] to report bugs.
|
|
24
24
|
|
25
25
|
[GitHub Issues]: https://github.com/fastly/fastly_nsq/issues
|
26
26
|
|
27
|
+
**[Documentation](https://www.rubydoc.info/gems/fastly_nsq)**
|
27
28
|
|
28
29
|
## Install
|
29
30
|
|
@@ -243,19 +244,33 @@ FastlyNsq::Http::Nsqlookupd.nodes
|
|
243
244
|
FastlyNsq::Http::Nsqlookupd.lookup(topic: 'foo')
|
244
245
|
```
|
245
246
|
|
246
|
-
###
|
247
|
+
### Testing
|
247
248
|
|
248
|
-
|
249
|
-
creates a connection
|
250
|
-
to `nsq-ruby`'s
|
251
|
-
`Nsq::Producer` and `Nsq::Consumer` classes.
|
249
|
+
`FastlyNsq` provides a test mode and a helper class to make testing easier.
|
252
250
|
|
253
|
-
|
254
|
-
|
255
|
-
to NSQ for testing purposes.
|
256
|
-
It adheres to the same API
|
257
|
-
as the real adapter.
|
251
|
+
In order to test classes that use FastlyNsq without having real connections
|
252
|
+
to NSQ:
|
258
253
|
|
254
|
+
```ruby
|
255
|
+
require 'fastly_nsq/testing'
|
256
|
+
|
257
|
+
RSpec.configure do |config|
|
258
|
+
config.before(:each) do
|
259
|
+
FastlyNsq::Testing.fake!
|
260
|
+
FastlyNsq::Testing.reset!
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
To test processor classes you can create test messages:
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
test_message = FastlyNsq::Testing.message(data: { 'count' => 123 })
|
269
|
+
|
270
|
+
My::ProcessorKlass.call(test_message)
|
271
|
+
|
272
|
+
expect(some_result)
|
273
|
+
```
|
259
274
|
|
260
275
|
## Configuration
|
261
276
|
|
data/fastly_nsq.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.add_development_dependency 'rspec-eventually', '0.2'
|
29
29
|
gem.add_development_dependency 'timecop'
|
30
30
|
gem.add_development_dependency 'webmock', '~> 3.0'
|
31
|
+
gem.add_development_dependency 'yard'
|
31
32
|
|
32
33
|
gem.add_dependency 'concurrent-ruby', '~> 1.0'
|
33
34
|
gem.add_dependency 'nsq-ruby', '~> 2.3'
|
data/lib/fastly_nsq.rb
CHANGED
@@ -12,12 +12,37 @@ module FastlyNsq
|
|
12
12
|
NotConnectedError = Class.new(StandardError)
|
13
13
|
ConnectionFailed = Class.new(StandardError)
|
14
14
|
|
15
|
+
LIFECYCLE_EVENTS = %i[startup shutdown heartbeat].freeze
|
16
|
+
|
15
17
|
class << self
|
18
|
+
# @return [String] NSQ Channel
|
16
19
|
attr_accessor :channel
|
20
|
+
|
21
|
+
# @return [Proc] global preprocessor
|
17
22
|
attr_accessor :preprocessor
|
23
|
+
|
24
|
+
# Maximum number of times an NSQ message will be attempted.
|
25
|
+
# When set to +nil+, the number of attempts will be unlimited.
|
26
|
+
# @return [Integer]
|
18
27
|
attr_accessor :max_attempts
|
28
|
+
|
29
|
+
# @return [Logger]
|
19
30
|
attr_writer :logger
|
20
31
|
|
32
|
+
##
|
33
|
+
# Map of lifecycle events
|
34
|
+
# @return [Hash]
|
35
|
+
def events
|
36
|
+
@events ||= LIFECYCLE_EVENTS.each_with_object({}) { |e, a| a[e] = [] }
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Create a FastlyNsq::Listener
|
41
|
+
#
|
42
|
+
# @param topic [String] NSQ topic on which to listen
|
43
|
+
# @param processor [Proc] processor that will be +call+ed per message
|
44
|
+
# @param options [Hash] additional options that are passed to FastlyNsq::Listener's constructor
|
45
|
+
# @return FastlyNsq::Listener
|
21
46
|
def listen(topic, processor, **options)
|
22
47
|
FastlyNsq::Listener.new(topic: topic, processor: processor, **options)
|
23
48
|
end
|
@@ -26,22 +51,73 @@ module FastlyNsq
|
|
26
51
|
@logger ||= Logger.new(nil)
|
27
52
|
end
|
28
53
|
|
54
|
+
##
|
55
|
+
# Configuration for FastlyNsq
|
56
|
+
# @example
|
57
|
+
# FastlyNsq.configure do |config|
|
58
|
+
# config.channel = 'Z'
|
59
|
+
# config.logger = Logger.new
|
60
|
+
# end
|
61
|
+
# @example
|
62
|
+
# FastlyNsq.configure do |config|
|
63
|
+
# config.channel = 'fnsq'
|
64
|
+
# config.logger = Logger.new
|
65
|
+
# config.preprocessor = ->(_) { FastlyNsq.logger.info 'PREPROCESSESES' }
|
66
|
+
# lc.listen 'posts', ->(m) { puts "posts: #{m.body}" }
|
67
|
+
# lc.listen 'blogs', ->(m) { puts "blogs: #{m.body}" }, priority: 3
|
68
|
+
# end
|
29
69
|
def configure
|
30
70
|
yield self
|
31
71
|
end
|
32
72
|
|
73
|
+
##
|
74
|
+
# Returns a new FastlyNsq::Manager or the memoized
|
75
|
+
# instance +@manager+.
|
76
|
+
# @return [FastlyNsq::Manager]
|
33
77
|
def manager
|
34
78
|
@manager ||= FastlyNsq::Manager.new
|
35
79
|
end
|
36
80
|
|
81
|
+
##
|
82
|
+
# Set a new manager and transfer listeners to the new manager.
|
83
|
+
# @param manager [FastlyNsq::Manager]
|
84
|
+
# @return [FastlyNsq::Manager]
|
37
85
|
def manager=(manager)
|
38
86
|
@manager&.transfer(manager)
|
39
87
|
@manager = manager
|
40
88
|
end
|
41
89
|
|
90
|
+
##
|
91
|
+
# Return an array of NSQ lookupd http addresses sourced from ENV['NSQLOOKUPD_HTTP_ADDRESS']
|
92
|
+
# @return [Array<String>] list of nsqlookupd http addresses
|
42
93
|
def lookupd_http_addresses
|
43
94
|
ENV.fetch('NSQLOOKUPD_HTTP_ADDRESS').split(',').map(&:strip)
|
44
95
|
end
|
96
|
+
|
97
|
+
# Register a block to run at a point in the lifecycle.
|
98
|
+
# :startup, :heartbeat or :shutdown are valid events.
|
99
|
+
#
|
100
|
+
# FastlyNsq.configure do |config|
|
101
|
+
# config.on(:shutdown) do
|
102
|
+
# puts "Goodbye cruel world!"
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
def on(event, &block)
|
106
|
+
event = event.to_sym
|
107
|
+
raise ArgumentError, "Invalid event name: #{event}" unless LIFECYCLE_EVENTS.include?(event)
|
108
|
+
events[event] << block
|
109
|
+
end
|
110
|
+
|
111
|
+
def fire_event(event)
|
112
|
+
blocks = FastlyNsq.events.fetch(event)
|
113
|
+
blocks.each do |block|
|
114
|
+
begin
|
115
|
+
block.call
|
116
|
+
rescue => e
|
117
|
+
logger.error "[#{event}] #{e.inspect}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
45
121
|
end
|
46
122
|
end
|
47
123
|
|
data/lib/fastly_nsq/cli.rb
CHANGED
@@ -10,6 +10,20 @@ require 'fileutils'
|
|
10
10
|
require 'optparse'
|
11
11
|
require 'singleton'
|
12
12
|
|
13
|
+
# Provides functionality to support running FastlyNsq as a command line
|
14
|
+
# application that listens and processes NSQ messages.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# begin
|
18
|
+
# cli = FastlyNsq::CLI.instance
|
19
|
+
# cli.parse_options
|
20
|
+
# cli.run
|
21
|
+
# rescue => e
|
22
|
+
# raise e if $DEBUG
|
23
|
+
# STDERR.puts e.message
|
24
|
+
# STDERR.puts e.backtrace.join("\n")
|
25
|
+
# exit 1
|
26
|
+
# end
|
13
27
|
class FastlyNsq::CLI
|
14
28
|
include Singleton
|
15
29
|
|
data/lib/fastly_nsq/consumer.rb
CHANGED
@@ -1,15 +1,82 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Provides an adapter to an Nsq::Consumer
|
4
|
+
# and used to read messages off the queue.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# consumer = FastlyNsq::Consumer.new(
|
8
|
+
# topic: 'topic',
|
9
|
+
# channel: 'channel'
|
10
|
+
# )
|
11
|
+
# consumer.size #=> 1
|
12
|
+
# message = consumer.pop
|
13
|
+
# message.body #=> "{ 'data': { 'key': 'value' } }"
|
14
|
+
# message.finish
|
15
|
+
# consumer.size #=> 0
|
16
|
+
# consumer.terminate
|
17
|
+
|
3
18
|
class FastlyNsq::Consumer
|
4
19
|
extend Forwardable
|
5
20
|
|
6
|
-
|
21
|
+
# Default NSQ connection timeout in seconds
|
22
|
+
DEFAULT_CONNECTION_TIMEOUT = 5
|
23
|
+
|
24
|
+
# @return [String] NSQ Channel
|
25
|
+
attr_reader :channel
|
26
|
+
|
27
|
+
# @return [String] NSQ Topic
|
28
|
+
attr_reader :topic
|
29
|
+
|
30
|
+
# @return [Nsq::Consumer]
|
31
|
+
attr_reader :connection
|
32
|
+
|
33
|
+
# @return [Integer] connection timeout in seconds
|
34
|
+
attr_reader :connect_timeout
|
7
35
|
|
8
|
-
|
36
|
+
# @return [Integer] maximum number of times an NSQ message will be attempted
|
9
37
|
attr_reader :max_attempts
|
10
38
|
|
11
|
-
|
39
|
+
# @!method connected?
|
40
|
+
# Delegated to +self.connection+
|
41
|
+
# @return [Nsq::Consumer#connected?]
|
42
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq/ClientBase#connected%3F-instance_method Nsq::ClientBase#connected?
|
43
|
+
# @!method pop
|
44
|
+
# Delegated to +self.connection+
|
45
|
+
# @return [Nsq::Consumer#pop]
|
46
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq%2FConsumer:pop Nsq::Consumer#pop
|
47
|
+
# @!method pop_without_blocking
|
48
|
+
# Delegated to +self.connection+
|
49
|
+
# @return [Nsq::Consumer#pop_without_blocking]
|
50
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq%2FConsumer:pop_without_blocking Nsq::Consumer#pop_without_blocking
|
51
|
+
# @!method size
|
52
|
+
# Delegated to +self.connection+
|
53
|
+
# @return [Nsq::Consumer#size]
|
54
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq%2FConsumer:size Nsq::Consumer#size
|
55
|
+
# @!method terminate
|
56
|
+
# Delegated to +self.connection+
|
57
|
+
# @return [Nsq::Consumer#terminate]
|
58
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq%2FConsumer:terminate Nsq::Consumer#terminate
|
59
|
+
def_delegators :connection, :connected?, :pop, :pop_without_blocking, :size, :terminate
|
12
60
|
|
61
|
+
##
|
62
|
+
# Create a FastlyNsq::Consumer
|
63
|
+
#
|
64
|
+
# @param topic [String] NSQ topic from which to consume
|
65
|
+
# @param channel [String] NSQ channel from which to consume
|
66
|
+
# @param queue [#pop, #size] Queue object, most likely an instance of {FastlyNsq::Feeder}
|
67
|
+
# @param tls_options [Hash] Hash of TSL options passed the connection.
|
68
|
+
# In most cases this should be nil unless you need to override the
|
69
|
+
# default values set in ENV.
|
70
|
+
# @param connect_timeout [Integer] NSQ connection timeout in seconds
|
71
|
+
# @param max_attempts [Integer] maximum number of times an NSQ message will be attemped
|
72
|
+
# When set to +nil+, attempts will be unlimited
|
73
|
+
# @param options [Hash] addtional options forwarded to the connection contructor
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# consumer = FastlyNsq::Consumer.new(
|
77
|
+
# topic: 'topic',
|
78
|
+
# channel: 'channel'
|
79
|
+
# )
|
13
80
|
def initialize(topic:, channel:, queue: nil, tls_options: nil, connect_timeout: DEFAULT_CONNECTION_TIMEOUT, max_attempts: FastlyNsq.max_attempts, **options)
|
14
81
|
@topic = topic
|
15
82
|
@channel = channel
|
@@ -20,6 +87,9 @@ class FastlyNsq::Consumer
|
|
20
87
|
@connection = connect(queue, **options)
|
21
88
|
end
|
22
89
|
|
90
|
+
##
|
91
|
+
# Is the message queue empty?
|
92
|
+
# @return [Boolean]
|
23
93
|
def empty?
|
24
94
|
size.zero?
|
25
95
|
end
|
data/lib/fastly_nsq/feeder.rb
CHANGED
@@ -1,15 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# FastlyNsq::Feeder is a queue interface wrapper for the manager's thread pool.
|
5
|
+
# This allows a consumer read loop to post a message directly to a
|
6
|
+
# processor (FastlyNsq::Listener) with a specified priority.
|
3
7
|
class FastlyNsq::Feeder
|
4
8
|
attr_reader :processor, :priority
|
5
9
|
|
10
|
+
##
|
11
|
+
# Create a FastlyNsq::Feeder
|
12
|
+
# @param processor [FastlyNsq::Listener]
|
13
|
+
# @param priority [Numeric]
|
6
14
|
def initialize(processor, priority)
|
7
15
|
@processor = processor
|
8
16
|
@priority = priority
|
9
17
|
end
|
10
18
|
|
19
|
+
##
|
20
|
+
# Send a message to the processor with specified priority
|
21
|
+
#
|
22
|
+
# This will +post+ to the FastlyNsq.manager.pool with a queue priority and block
|
23
|
+
# that will +call+ed. FastlyNsq.manager.pool is a PriorityThreadPool which is a
|
24
|
+
# Concurrent::ThreadPoolExecutor that has @queue which in turn is a priority queue
|
25
|
+
# that manages job priority
|
26
|
+
#
|
27
|
+
# The ThreadPoolExecutor is what actually works the @queue and sends +call+ to the queued Proc.
|
28
|
+
# When that code is exec'ed +processer.call(message)+ is run. Processor in this context is
|
29
|
+
# a FastlyNsq::Listener
|
30
|
+
#
|
31
|
+
# @param message [Nsq::Message]
|
11
32
|
# @see http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html#post-instance_method
|
12
|
-
# @see
|
33
|
+
# @see Nsq::Connection#read_loop
|
13
34
|
def push(message)
|
14
35
|
FastlyNsq.manager.pool.post(priority) { processor.call(message) }
|
15
36
|
end
|
data/lib/fastly_nsq/http.rb
CHANGED
@@ -4,6 +4,16 @@ require 'net/https'
|
|
4
4
|
require 'fastly_nsq/http/nsqd'
|
5
5
|
require 'fastly_nsq/http/nsqlookupd'
|
6
6
|
|
7
|
+
##
|
8
|
+
# Adapter class for HTTP requests to NSQD
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# uri = URI.join(nsqd_url, '/info')
|
12
|
+
# client = FastlyNsq::Http.new(uri: uri)
|
13
|
+
# client.use_ssl
|
14
|
+
#
|
15
|
+
# @see FastlyNsq::Http::Nsqd
|
16
|
+
# @see FastlyNsq::Http::Nsqlookupd
|
7
17
|
class FastlyNsq::Http
|
8
18
|
def initialize(uri:, cert_filename: ENV['NSQ_SSL_CERTIFICATE'], key_filename: ENV['NSQ_SSL_KEY'])
|
9
19
|
@uri = uri.is_a?(URI) ? uri : URI.parse(uri)
|
data/lib/fastly_nsq/http/nsqd.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
require 'fastly_nsq/http'
|
4
4
|
|
5
5
|
class FastlyNsq::Http
|
6
|
+
##
|
7
|
+
# Provides an interface to the the functionality provided by
|
8
|
+
# the nsqd HTTP interface
|
9
|
+
#
|
10
|
+
# @see https://nsq.io/components/nsqd.html
|
6
11
|
class Nsqd
|
7
12
|
extend Forwardable
|
8
13
|
def_delegator :client, :get
|
@@ -3,6 +3,11 @@
|
|
3
3
|
require 'fastly_nsq/http'
|
4
4
|
|
5
5
|
class FastlyNsq::Http
|
6
|
+
##
|
7
|
+
# Provides an interface to the functionality exposed by
|
8
|
+
# the nsqlookupd HTTP interface
|
9
|
+
#
|
10
|
+
# @see https://nsq.io/components/nsqlookupd.html
|
6
11
|
class Nsqlookupd
|
7
12
|
extend Forwardable
|
8
13
|
def_delegator :client, :get
|
data/lib/fastly_nsq/launcher.rb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'fastly_nsq/safe_thread'
|
4
4
|
|
5
|
+
##
|
6
|
+
# FastlyNsq::Launcher is a lighweight wrapper of a thread manager
|
7
|
+
# and heartbeat.
|
8
|
+
#
|
9
|
+
# This class is used internally by FastlyNsq::CLI.
|
5
10
|
class FastlyNsq::Launcher
|
6
11
|
include FastlyNsq::SafeThread
|
7
12
|
|
@@ -19,6 +24,7 @@ class FastlyNsq::Launcher
|
|
19
24
|
@logger = logger
|
20
25
|
|
21
26
|
FastlyNsq.manager = FastlyNsq::Manager.new(options)
|
27
|
+
FastlyNsq.fire_event :startup
|
22
28
|
end
|
23
29
|
|
24
30
|
def beat
|
@@ -28,6 +34,7 @@ class FastlyNsq::Launcher
|
|
28
34
|
def stop
|
29
35
|
@done = true
|
30
36
|
manager.terminate(timeout)
|
37
|
+
FastlyNsq.fire_event :shutdown
|
31
38
|
end
|
32
39
|
|
33
40
|
def stop_listeners
|
@@ -57,6 +64,8 @@ class FastlyNsq::Launcher
|
|
57
64
|
# ::Process.kill('dieing because...', $$)
|
58
65
|
rescue => e
|
59
66
|
logger.error "Heartbeat error: #{e.message}"
|
67
|
+
ensure
|
68
|
+
FastlyNsq.fire_event :heartbeat
|
60
69
|
end
|
61
70
|
|
62
71
|
def start_heartbeat
|
data/lib/fastly_nsq/listener.rb
CHANGED
@@ -1,16 +1,81 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# The main interface to setting up a thread that listens for and
|
5
|
+
# processes NSQ messages from a given topic/channel.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# FastlyNsq::Listener.new(
|
9
|
+
# topic: topic,
|
10
|
+
# channel: channel,
|
11
|
+
# processor: ->(m) { puts 'got: '+ m.body }
|
12
|
+
# )
|
3
13
|
class FastlyNsq::Listener
|
4
14
|
extend Forwardable
|
5
15
|
|
16
|
+
# Default queue priority used when setting up the consumer queue
|
6
17
|
DEFAULT_PRIORITY = 0
|
7
|
-
DEFAULT_CONNECTION_TIMEOUT = 5 # seconds
|
8
18
|
|
19
|
+
# Default NSQ connection timeout in seconds
|
20
|
+
DEFAULT_CONNECTION_TIMEOUT = 5
|
21
|
+
|
22
|
+
# @!method connected?
|
23
|
+
# Delegated to +self.consumer+
|
24
|
+
# @return [FastlyNsq::Consumer#connected?]
|
9
25
|
def_delegators :consumer, :connected?
|
10
26
|
|
11
|
-
|
27
|
+
# @return [String] NSQ Channel
|
28
|
+
attr_reader :channel
|
29
|
+
|
30
|
+
# @return [FastlyNsq::Consumer]
|
31
|
+
attr_reader :consumer
|
32
|
+
|
33
|
+
# @return [Logger]
|
34
|
+
attr_reader :logger
|
35
|
+
|
36
|
+
# @return [Integer] maxium number of times an NSQ message will be attempted
|
12
37
|
attr_reader :max_attempts
|
13
38
|
|
39
|
+
# @return [Proc]
|
40
|
+
attr_reader :preprocessor
|
41
|
+
|
42
|
+
# @return [String] NSQ Topic
|
43
|
+
attr_reader :topic
|
44
|
+
|
45
|
+
# @return [Integer] Queue priority
|
46
|
+
attr_reader :priority
|
47
|
+
|
48
|
+
# @return [Proc] processor that is called for each message
|
49
|
+
attr_reader :processor
|
50
|
+
|
51
|
+
##
|
52
|
+
# Create a FastlyNsq::Listener
|
53
|
+
#
|
54
|
+
# @param topic [String] NSQ topic on which to listen
|
55
|
+
# @param processor [Proc#call] Any object that responds to +call+. Each message will
|
56
|
+
# be processed with +processor.call(FastlyNsq::Message.new(nsq_message))+.
|
57
|
+
# The processor should return +true+ to indicate that processing is complete
|
58
|
+
# and NSQ message can be finished. The processor is passed an instance of {FastlyNsq::Message}
|
59
|
+
# so the provided Proc can optionally manage the message state using methods provided by {FastlyNsq::Message}.
|
60
|
+
# @param preprocessor [Proc#call] Any object that responds to +call+. Similar to the processor
|
61
|
+
# each message it processes via +preprocessor.call(message)+. Default: {FastlyNsq.preprocessor}
|
62
|
+
# @param channel [String] NSQ Channel on which to listen. Default: {FastlyNsq.channel}
|
63
|
+
# @param consumer [FastlyNsq::Consumer] interface to read messages off the queue. If value is +nil+ the
|
64
|
+
# constructor will create a {FastlyNsq::Consumer} based on the provided parameters.
|
65
|
+
# @param logger [Logger] Default: {FastlyNsq.logger}
|
66
|
+
# @param priority [Integer] Queue piority. Default: {DEFAULT_PRIORITY}
|
67
|
+
# @param connect_timeout [Integer] NSQ connection timeout in seconds. Default: {DEFAULT_CONNECTION_TIMEOUT}
|
68
|
+
# @param max_attempts [Integer] maximum number of times an NSQ message will be attemped Default: {FastlyNsq.max_attempts}
|
69
|
+
# When set to +nil+, attempts will be unlimited
|
70
|
+
# @param consumer_options [Hash] additional options forwarded to the {FastlyNsq::Consumer}} contructor
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# FastlyNsq::Listener.new(
|
74
|
+
# topic: topic,
|
75
|
+
# channel: channel,
|
76
|
+
# processor: MessageProcessor,
|
77
|
+
# max_attempts: 15,
|
78
|
+
# )
|
14
79
|
def initialize(topic:, processor:, preprocessor: FastlyNsq.preprocessor, channel: FastlyNsq.channel, consumer: nil,
|
15
80
|
logger: FastlyNsq.logger, priority: DEFAULT_PRIORITY, connect_timeout: DEFAULT_CONNECTION_TIMEOUT,
|
16
81
|
max_attempts: FastlyNsq.max_attempts, **consumer_options)
|
@@ -36,6 +101,15 @@ class FastlyNsq::Listener
|
|
36
101
|
FastlyNsq.manager.add_listener(self)
|
37
102
|
end
|
38
103
|
|
104
|
+
##
|
105
|
+
# Process an NSQ message.
|
106
|
+
#
|
107
|
+
# @see FastlyNsq::Feeder#push
|
108
|
+
#
|
109
|
+
# @param nsq_message [Nsq::Message]
|
110
|
+
# @see {https://www.rubydoc.info/gems/nsq-ruby/Nsq/Message}
|
111
|
+
#
|
112
|
+
# @return [FastlyNsq::Message]
|
39
113
|
def call(nsq_message)
|
40
114
|
message = FastlyNsq::Message.new(nsq_message)
|
41
115
|
|
@@ -57,6 +131,10 @@ class FastlyNsq::Listener
|
|
57
131
|
message
|
58
132
|
end
|
59
133
|
|
134
|
+
##
|
135
|
+
# Close the NSQ Conneciton
|
136
|
+
#
|
137
|
+
# @see FastlyNsq::Consumer#terminate
|
60
138
|
def terminate
|
61
139
|
return unless connected?
|
62
140
|
consumer.terminate
|
data/lib/fastly_nsq/manager.rb
CHANGED
@@ -1,11 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# Interface for tracking listeners and managing the processing pool.
|
3
5
|
class FastlyNsq::Manager
|
4
6
|
DEADLINE = 30
|
5
7
|
DEFAULT_POOL_SIZE = 5
|
6
8
|
|
7
|
-
|
9
|
+
# @return [Boolean] Set true when all listeners are stopped
|
10
|
+
attr_reader :done
|
8
11
|
|
12
|
+
# @return [FastlyNsq::PriorityThreadPool]
|
13
|
+
attr_reader :pool
|
14
|
+
|
15
|
+
# @return [Logger]
|
16
|
+
attr_reader :logger
|
17
|
+
|
18
|
+
##
|
19
|
+
# Create a FastlyNsq::Manager
|
20
|
+
#
|
21
|
+
# @param logger [Logger]
|
22
|
+
# @param pool_options [Hash] Options forwarded to {FastlyNsq::PriorityThreadPool} constructor.
|
9
23
|
def initialize(logger: FastlyNsq.logger, **pool_options)
|
10
24
|
@done = false
|
11
25
|
@logger = logger
|
@@ -14,18 +28,31 @@ class FastlyNsq::Manager
|
|
14
28
|
)
|
15
29
|
end
|
16
30
|
|
31
|
+
##
|
32
|
+
# Hash of listeners. Keys are topics, values are {FastlyNsq::Listener} instances.
|
33
|
+
# @return [Hash]
|
17
34
|
def topic_listeners
|
18
35
|
@topic_listeners ||= {}
|
19
36
|
end
|
20
37
|
|
38
|
+
##
|
39
|
+
# Array of listening topic names
|
40
|
+
# @return [Array]
|
21
41
|
def topics
|
22
42
|
topic_listeners.keys
|
23
43
|
end
|
24
44
|
|
45
|
+
##
|
46
|
+
# Set of {FastlyNsq::Listener} objects
|
47
|
+
# @return [Set]
|
25
48
|
def listeners
|
26
49
|
topic_listeners.values.to_set
|
27
50
|
end
|
28
51
|
|
52
|
+
##
|
53
|
+
# Stop the manager.
|
54
|
+
# Terminates the listeners and stops all processing in the pool.
|
55
|
+
# @param deadline [Integer] Number of seconds to wait for pool to stop processing
|
29
56
|
def terminate(deadline = DEADLINE)
|
30
57
|
return if done
|
31
58
|
|
@@ -38,10 +65,16 @@ class FastlyNsq::Manager
|
|
38
65
|
@done = true
|
39
66
|
end
|
40
67
|
|
68
|
+
##
|
69
|
+
# Manager state
|
70
|
+
# @return [Boolean]
|
41
71
|
def stopped?
|
42
72
|
done
|
43
73
|
end
|
44
74
|
|
75
|
+
##
|
76
|
+
# Add a {FastlyNsq::Listener} to the @topic_listeners
|
77
|
+
# @param listener [FastlyNsq::Listener}
|
45
78
|
def add_listener(listener)
|
46
79
|
logger.info { "topic #{listener.topic}, channel #{listener.channel}: listening" }
|
47
80
|
|
@@ -52,6 +85,10 @@ class FastlyNsq::Manager
|
|
52
85
|
topic_listeners[listener.topic] = listener
|
53
86
|
end
|
54
87
|
|
88
|
+
##
|
89
|
+
# Transer listeners to a new manager and stop processing from the existing pool.
|
90
|
+
# @param new_manager [FastlyNsq::Manager] new manager to which listeners will be added
|
91
|
+
# @param deadline [Integer] Number of seconds to wait for exsiting pool to stop processing
|
55
92
|
def transfer(new_manager, deadline: DEADLINE)
|
56
93
|
new_manager.topic_listeners.merge!(topic_listeners)
|
57
94
|
stop_processing(deadline)
|
@@ -59,6 +96,8 @@ class FastlyNsq::Manager
|
|
59
96
|
@done = true
|
60
97
|
end
|
61
98
|
|
99
|
+
##
|
100
|
+
# Terminate all listeners
|
62
101
|
def stop_listeners
|
63
102
|
logger.info { 'Stopping listeners' }
|
64
103
|
listeners.each(&:terminate)
|
@@ -67,6 +106,9 @@ class FastlyNsq::Manager
|
|
67
106
|
|
68
107
|
protected
|
69
108
|
|
109
|
+
##
|
110
|
+
# Shutdown the pool
|
111
|
+
# @param deadline [Integer] Number of seconds to wait for pool to stop processing
|
70
112
|
def stop_processing(deadline)
|
71
113
|
logger.info { 'Stopping processors' }
|
72
114
|
pool.shutdown
|
data/lib/fastly_nsq/message.rb
CHANGED
@@ -2,14 +2,40 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
|
5
|
+
##
|
6
|
+
# Adapter to Nsq::Message. Provides convenience methods for interacting
|
7
|
+
# with a message. Delegates management methods to the Nsq::Message
|
5
8
|
class FastlyNsq::Message
|
6
9
|
extend Forwardable
|
7
10
|
|
11
|
+
# @!method attempts
|
12
|
+
# Delegated to +self.nsq_message+
|
13
|
+
# @return [Nsq::Message#attempts]
|
14
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq/Message#attempts-instance_method
|
15
|
+
# @!method touch
|
16
|
+
# Delegated to +self.nsq_message+
|
17
|
+
# @return [Nsq::Message#touch]
|
18
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq/Message#touch-instance_method
|
19
|
+
# @!method timestamp
|
20
|
+
# Delegated to +self.nsq_message+
|
21
|
+
# @return [Nsq::Message#timestamp]
|
22
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq/Message#timestamp-instance_method
|
8
23
|
def_delegators :@nsq_message, :attempts, :touch, :timestamp
|
9
24
|
|
10
|
-
|
25
|
+
# @return [Symbol] Message state. Returns +nil+ if message has not been requeued or finished.
|
26
|
+
attr_reader :managed
|
27
|
+
|
28
|
+
# @return [Nsq::Message]
|
29
|
+
# @see https://www.rubydoc.info/gems/nsq-ruby/Nsq/Message
|
30
|
+
attr_reader :nsq_message
|
31
|
+
|
32
|
+
# @return [String] Nsq::Message body
|
33
|
+
attr_reader :raw_body
|
34
|
+
|
11
35
|
alias to_s raw_body
|
12
36
|
|
37
|
+
##
|
38
|
+
# @param nsq_message [Nsq::Message]
|
13
39
|
def initialize(nsq_message)
|
14
40
|
@nsq_message = nsq_message
|
15
41
|
@raw_body = nsq_message.body
|
@@ -27,6 +53,8 @@ class FastlyNsq::Message
|
|
27
53
|
@body ||= JSON.parse(raw_body)
|
28
54
|
end
|
29
55
|
|
56
|
+
##
|
57
|
+
# Finish an NSQ message
|
30
58
|
def finish
|
31
59
|
return managed if managed
|
32
60
|
|
@@ -34,6 +62,9 @@ class FastlyNsq::Message
|
|
34
62
|
nsq_message.finish
|
35
63
|
end
|
36
64
|
|
65
|
+
##
|
66
|
+
# Requeue an NSQ Message
|
67
|
+
# @param timeout [Integer] timeout in milliseconds
|
37
68
|
def requeue(timeout = nil)
|
38
69
|
return managed if managed
|
39
70
|
timeout ||= requeue_period
|
data/lib/fastly_nsq/messenger.rb
CHANGED
@@ -1,11 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
##
|
4
|
+
# Provides interface for writing messages to NSQ.
|
5
|
+
# Manages tracking and creation of {FastlyNsq::Producer}s
|
6
|
+
# @example
|
7
|
+
# FastlyNsq::Messenger.deliver(
|
8
|
+
# message: message,
|
9
|
+
# topic: 'topic',
|
10
|
+
# meta: metadata_hash,
|
11
|
+
# )
|
3
12
|
module FastlyNsq::Messenger
|
4
13
|
DEFAULT_ORIGIN = 'Unknown'
|
5
14
|
@originating_service = DEFAULT_ORIGIN
|
6
15
|
|
7
16
|
module_function
|
8
17
|
|
18
|
+
##
|
19
|
+
# Deliver an NSQ message
|
20
|
+
# @param message [#to_s] written to the +data+ key of the NSQ message payload
|
21
|
+
# @param topic [String] NSQ topic on which to deliver the message
|
22
|
+
# @param originating_service [String] added to meta key of message payload
|
23
|
+
# @param meta [Hash]
|
24
|
+
# @return [Void]
|
9
25
|
def deliver(message:, topic:, originating_service: nil, meta: {})
|
10
26
|
meta[:originating_service] = originating_service || self.originating_service
|
11
27
|
meta[:sent_at] = Time.now.iso8601(5)
|
@@ -22,6 +38,9 @@ module FastlyNsq::Messenger
|
|
22
38
|
@originating_service = service
|
23
39
|
end
|
24
40
|
|
41
|
+
##
|
42
|
+
# @param topic [String] NSQ topic
|
43
|
+
# @return [FastlyNsq::Producer] returns producer for given topic
|
25
44
|
def producer_for(topic:)
|
26
45
|
producer = producers[topic]
|
27
46
|
|
@@ -30,10 +49,16 @@ module FastlyNsq::Messenger
|
|
30
49
|
producer
|
31
50
|
end
|
32
51
|
|
52
|
+
##
|
53
|
+
# Map of subscribed topics to FastlyNsq::Producer
|
54
|
+
# @return [Hash]
|
33
55
|
def producers
|
34
56
|
@producers ||= Hash.new { |hash, topic| hash[topic] = FastlyNsq::Producer.new(topic: topic) }
|
35
57
|
end
|
36
58
|
|
59
|
+
##
|
60
|
+
# Terminate producer for given topic
|
61
|
+
# @param topic [String] NSQ topic
|
37
62
|
def terminate_producer(topic:)
|
38
63
|
producer_for(topic: topic).terminate
|
39
64
|
producers.delete(topic)
|
data/lib/fastly_nsq/testing.rb
CHANGED
@@ -1,6 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FastlyNsq
|
4
|
+
##
|
5
|
+
# Interface for testing FastlyNsq
|
6
|
+
# @example
|
7
|
+
# require 'fastly_nsq/testing'
|
8
|
+
# FastlyNsq::Testing.enabled? #=> true
|
9
|
+
# FastlyNsq::Testing.disabled? #=> false
|
10
|
+
|
11
|
+
# producer = FastlyNsq::Producer.new(topic: topic)
|
12
|
+
# listener = FastlyNsq::Listener.new(topic: topic, channel: channel, processor: ->(m) { puts 'got: '+ m.body })
|
13
|
+
|
14
|
+
# FastlyNsq::Testing.fake! # default, messages accumulate on the listeners
|
15
|
+
|
16
|
+
# producer.write '{"foo":"bar"}'
|
17
|
+
# listener.messages.size #=> 1
|
18
|
+
|
19
|
+
# FastlyNsq::Testing.reset! # remove all accumulated messages
|
20
|
+
|
21
|
+
# listener.messages.size #=> 0
|
22
|
+
|
23
|
+
# producer.write '{"foo":"bar"}'
|
24
|
+
# listener.messages.size #=> 1
|
25
|
+
|
26
|
+
# listener.drain
|
27
|
+
# # got: {"foo"=>"bar"}
|
28
|
+
# listener.messages.size #=> 0
|
29
|
+
|
30
|
+
# FastlyNsq::Testing.inline! # messages are processed as they are produced
|
31
|
+
# producer.write '{"foo":"bar"}'
|
32
|
+
# # got: {"foo"=>"bar"}
|
33
|
+
# listener.messages.size #=> 0
|
34
|
+
|
35
|
+
# FastlyNsq::Testing.disable! # do it live
|
36
|
+
# FastlyNsq::Testing.enable! # re-enable testing mode
|
4
37
|
class Testing
|
5
38
|
class << self
|
6
39
|
attr_accessor :__test_mode
|
@@ -52,6 +85,18 @@ module FastlyNsq
|
|
52
85
|
FastlyNsq::Messages.messages.clear
|
53
86
|
end
|
54
87
|
|
88
|
+
##
|
89
|
+
# Creates a FastlyNsq::TestMessage that is used to create a FastlyNsq::Message where the underlying
|
90
|
+
# +nsq_message+ is the TestMessage and not an Nsq::Message. This aids in testing application code that
|
91
|
+
# is doing message processing
|
92
|
+
#
|
93
|
+
# @param data [String] NSQ message data
|
94
|
+
# @param meta [String] NSQ message metadata
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# test_message = FastlyNsq::Testing.message(data: post_data, meta: {})
|
98
|
+
# processor_klass.call(test_message)
|
99
|
+
# expect(Post.find(post_data['id']).not_to be nil
|
55
100
|
def message(data:, meta: nil)
|
56
101
|
test_message = FastlyNsq::TestMessage.new(JSON.dump('data' => data, 'meta' => meta))
|
57
102
|
FastlyNsq::Message.new(test_message)
|
@@ -65,6 +110,10 @@ module FastlyNsq
|
|
65
110
|
end
|
66
111
|
end
|
67
112
|
|
113
|
+
##
|
114
|
+
# Stub for Nsq::Message used for testing.
|
115
|
+
# Use this class instead of a struct or test stubs
|
116
|
+
# when testing application logic that requires a Nsq::Message.
|
68
117
|
class TestMessage
|
69
118
|
attr_reader :raw_body
|
70
119
|
attr_reader :attempts
|
data/lib/fastly_nsq/version.rb
CHANGED
data/spec/fastly_nsq_spec.rb
CHANGED
@@ -69,4 +69,20 @@ RSpec.describe FastlyNsq do
|
|
69
69
|
expect(subject.lookupd_http_addresses).to eq(ENV['NSQLOOKUPD_HTTP_ADDRESS'].split(','))
|
70
70
|
end
|
71
71
|
end
|
72
|
+
|
73
|
+
describe '#on' do
|
74
|
+
before { FastlyNsq.events.each { |(_, v)| v.clear } }
|
75
|
+
after { FastlyNsq.events.each { |(_, v)| v.clear } }
|
76
|
+
|
77
|
+
it 'registers callbacks for events' do
|
78
|
+
%i[startup shutdown heartbeat].each do |event|
|
79
|
+
block = -> {}
|
80
|
+
expect { FastlyNsq.on(event, &block) }.to change { FastlyNsq.events[event] }.by([block])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'limits callback registration to valid events' do
|
85
|
+
expect { FastlyNsq.on(:foo, &-> {}) }.to raise_error(ArgumentError, /Invalid event name/)
|
86
|
+
end
|
87
|
+
end
|
72
88
|
end
|
data/spec/launcher_spec.rb
CHANGED
@@ -3,19 +3,21 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
RSpec.describe FastlyNsq::Launcher do
|
6
|
+
let!(:channel) { 'fnsq' }
|
6
7
|
let!(:options) { { max_threads: 3, timeout: 9 } }
|
7
|
-
let!(:launcher) { FastlyNsq::Launcher.new options }
|
8
8
|
let!(:topic) { 'fnsq' }
|
9
|
-
|
9
|
+
|
10
|
+
let(:launcher) { FastlyNsq::Launcher.new options }
|
10
11
|
let(:listener) { FastlyNsq::Listener.new(topic: topic, channel: channel, processor: ->(*) {}) }
|
12
|
+
let(:manager) { launcher.manager }
|
11
13
|
|
12
14
|
before { reset_topic(topic, channel: channel) }
|
13
15
|
before { expect { listener }.to eventually(be_connected).within(5) }
|
14
16
|
after { listener.terminate if listener.connected? }
|
15
17
|
|
16
|
-
let(:manager) { launcher.manager }
|
17
|
-
|
18
18
|
it 'creates a manager with correct options' do
|
19
|
+
launcher
|
20
|
+
|
19
21
|
expect(FastlyNsq.manager.pool.max_threads).to eq(3)
|
20
22
|
end
|
21
23
|
|
@@ -30,6 +32,8 @@ RSpec.describe FastlyNsq::Launcher do
|
|
30
32
|
end
|
31
33
|
|
32
34
|
describe '#stop_listeners' do
|
35
|
+
before { launcher }
|
36
|
+
|
33
37
|
it 'stops listeners and sets done' do
|
34
38
|
expect(launcher).not_to be_stopping
|
35
39
|
expect(manager).to receive(:stop_listeners)
|
@@ -42,9 +46,45 @@ RSpec.describe FastlyNsq::Launcher do
|
|
42
46
|
end
|
43
47
|
|
44
48
|
describe '#stop' do
|
49
|
+
before { launcher }
|
50
|
+
|
45
51
|
it 'stops the manager within a deadline' do
|
46
52
|
expect(manager).to receive(:terminate).with(options[:timeout])
|
47
53
|
launcher.stop
|
48
54
|
end
|
49
55
|
end
|
56
|
+
|
57
|
+
describe 'callbacks' do
|
58
|
+
before { FastlyNsq.events.each { |(_, v)| v.clear } }
|
59
|
+
after { FastlyNsq.events.each { |(_, v)| v.clear } }
|
60
|
+
|
61
|
+
it 'fires :startup event on initialization' do
|
62
|
+
obj = spy
|
63
|
+
block = -> { obj.start }
|
64
|
+
FastlyNsq.on(:startup, &block)
|
65
|
+
|
66
|
+
launcher
|
67
|
+
expect(obj).to have_received(:start)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'fires :shutdown event on #stop' do
|
71
|
+
launcher
|
72
|
+
|
73
|
+
obj = spy
|
74
|
+
block = -> { obj.stop }
|
75
|
+
FastlyNsq.on(:shutdown, &block)
|
76
|
+
|
77
|
+
launcher.stop
|
78
|
+
expect(obj).to have_received(:stop)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'fires :heartbeat event on #heartbeat' do
|
82
|
+
obj = spy
|
83
|
+
block = -> { obj.beat }
|
84
|
+
FastlyNsq.on(:heartbeat, &block)
|
85
|
+
launcher.beat
|
86
|
+
|
87
|
+
expect { obj }.to eventually(have_received(:beat).at_least(:once)).within(0.5)
|
88
|
+
end
|
89
|
+
end
|
50
90
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastly_nsq
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tommy O'Neil
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2018-
|
16
|
+
date: 2018-05-30 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: awesome_print
|
@@ -127,6 +127,20 @@ dependencies:
|
|
127
127
|
- - "~>"
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '3.0'
|
130
|
+
- !ruby/object:Gem::Dependency
|
131
|
+
name: yard
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
type: :development
|
138
|
+
prerelease: false
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
130
144
|
- !ruby/object:Gem::Dependency
|
131
145
|
name: concurrent-ruby
|
132
146
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
268
|
version: '0'
|
255
269
|
requirements: []
|
256
270
|
rubyforge_project:
|
257
|
-
rubygems_version: 2.
|
271
|
+
rubygems_version: 2.6.14
|
258
272
|
signing_key:
|
259
273
|
specification_version: 4
|
260
274
|
summary: Fastly NSQ Adapter
|