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