circuitry 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e63a7c32a7c197c43ef244bdb5b3be5ae1544f39
4
- data.tar.gz: f99bee8e6cc734e0ae334d31de01eb70851717e3
3
+ metadata.gz: aabb7e5d67924cf49e3d6efdd5f34c27d4d850f6
4
+ data.tar.gz: b63bad1d70be23cec5c666b699f8473b25a2d7cb
5
5
  SHA512:
6
- metadata.gz: 4bb4da24b7d24c81b212c3c8f750d8bed4c85e8bde06f96e25f08c5d6291e1bed908948eec337caa193333e821cd02a01cfc94d5c86dcb3eeeddd24fab893da5
7
- data.tar.gz: a54b2dc0d13eb5172e869163bef080e2a3095f09e3fa832ce6b79d0a1b508ec9fdf03610c4a98be83a3c7127b914ea82a398a935beb05d12f2e5891a3ed00933
6
+ metadata.gz: 01e354ad6cfaccaf54184d932714609fbad3e95c9f0a35137e756abe5448fc4fda5b7a8f52333d7dc7f05e4b80d4d2cb2c3a30b52e271e90cc70ddd2e6efd4ab
7
+ data.tar.gz: 146e463dc1f63ea887ec99ffbd350d9b28d9f3aff2d96fca12ce407c200a2f3a1480ce2b4be15747980f533ec00e2716854607ec53e46249b6a705e74d137bcf
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in circuitry.gemspec
4
4
  gemspec
5
+
6
+ gem 'memcache_mock', '0.0.14', github: 'mhuggins/MemcacheMock', branch: 'expiry-and-add'
data/README.md CHANGED
@@ -36,6 +36,7 @@ Circuitry.config do |c|
36
36
  HoneyBadger.notify(error)
37
37
  HoneyBadger.flush
38
38
  end
39
+ c.lock_strategy = Circuitry::Lock::Redis.new(url: 'redis://localhost:6379')
39
40
  c.publish_async_strategy = :batch
40
41
  c.subscribe_async_strategy = :thread
41
42
  end
@@ -54,6 +55,9 @@ Available configuration options include:
54
55
  * `error_handler`: An object that responds to `call` with two arguments: the
55
56
  deserialized message contents and the topic name used when publishing to SNS.
56
57
  *(optional, default: `nil`)*
58
+ * `:lock_strategy` - The store used to ensure that no duplicate messages are
59
+ processed. Please refer to the [Lock Strategies](#lock-strategies) section for
60
+ more details regarding this option. *(default: `Circuitry::Locks::Memory.new`)*
57
61
  * `publish_async_strategy`: One of `:fork`, `:thread`, or `:batch` that
58
62
  determines how asynchronous publish requests are processed. *(optional,
59
63
  default: `:fork`)*
@@ -90,10 +94,14 @@ The `publish` method also accepts options that impact instantiation of the
90
94
  the `publish_async_strategy` value from the gem configuration. Please refer to
91
95
  the [Asynchronous Support](#asynchronous-support) section for more details
92
96
  regarding this option. *(default: `false`)*
97
+ * `:timeout` - The maximum amount of time in seconds that publishing a message
98
+ will be attempted before giving up. If the timeout is exceeded, an exception
99
+ will raised to be handled by your application or `error_handler`. *(default:
100
+ 15)*
93
101
 
94
102
  ```ruby
95
103
  obj = { foo: 'foo', bar: 'bar' }
96
- Circuitry.publish('my-topic-name', obj, async: true)
104
+ Circuitry.publish('my-topic-name', obj, async: true, timeout: 20)
97
105
  ```
98
106
 
99
107
  Alternatively, if your options hash will remain unchanged, you can build a single
@@ -120,13 +128,22 @@ end
120
128
  The `subscribe` method also accepts options that impact instantiation of the
121
129
  `Subscriber` object, which currently includes the following options.
122
130
 
131
+ * `:lock` - The strategy used to ensure that no duplicate messages are processed.
132
+ Accepts `true`, `false`, or an instance of a class inheriting from
133
+ `Circuitry::Locks::Base`. Passing `true` uses the `lock_strategy` value from
134
+ the gem configuration. Passing `false` uses the [NOOP](#NOOP) strategy. Please
135
+ refer to the [Lock Strategies](#lock-strategies) section for more details
136
+ regarding this option. *(default: `true`)*
123
137
  * `:async` - Whether or not subscribing should occur in the background. Accepts
124
138
  one of `:fork`, `:thread`, `true`, or `false`. Passing `true` uses the
125
139
  `subscribe_async_strategy` value from the gem configuration. Passing an
126
- asynchronous value will cause messages to be handled concurrently, meaning
127
- that queued messages *might not be processed in the order they're received*.
128
- Please refer to the [Asynchronous Support](#asynchronous-support) section for
129
- more details regarding this option. *(default: `false`)*
140
+ asynchronous value will cause messages to be handled concurrently. Please
141
+ refer to the [Asynchronous Support](#asynchronous-support) section for more
142
+ details regarding this option. *(default: `false`)*
143
+ * `:timeout` - The maximum amount of time in seconds that processing a message
144
+ will be attempted before giving up. If the timeout is exceeded, an exception
145
+ will raised to be handled by your application or `error_handler`. *(default:
146
+ 15)*
130
147
  * `:wait_time` - The number of seconds to wait for messages while connected to
131
148
  SQS. Anything above 0 results in long-polling, while 0 results in
132
149
  short-polling. *(default: 10)*
@@ -134,7 +151,15 @@ The `subscribe` method also accepts options that impact instantiation of the
134
151
  *(default: 10)*
135
152
 
136
153
  ```ruby
137
- Circuitry.subscribe('https://...', async: true, wait_time: 60, batch_size: 20) do |message, topic_name|
154
+ options = {
155
+ lock: true,
156
+ async: true,
157
+ timeout: 20,
158
+ wait_time: 60,
159
+ batch_size: 20
160
+ }
161
+
162
+ Circuitry.subscribe('https://...', options) do |message, topic_name|
138
163
  # ...
139
164
  end
140
165
  ```
@@ -204,6 +229,152 @@ Batched publish and subscribe requests are queued in memory and do not begin
204
229
  processing until you explicit flush them. This can be done by calling
205
230
  `Circuitry.flush`.
206
231
 
232
+ ### Lock Strategies
233
+
234
+ The [Amazon SQS FAQ](http://aws.amazon.com/sqs/faqs/) includes the following
235
+ important point:
236
+
237
+ > Amazon SQS is engineered to provide “at least once” delivery of all messages in
238
+ > its queues. Although most of the time each message will be delivered to your
239
+ > application exactly once, you should design your system so that processing a
240
+ > message more than once does not create any errors or inconsistencies.
241
+
242
+ Given this, it's up to the user to ensure messages are not processed multiple
243
+ times in the off chance that Amazon does not recognize that a message has been
244
+ processed.
245
+
246
+ The circuitry gem handles this by caching SQS message IDs: first via a "soft
247
+ lock" that denotes the message is about to be processed, then via a "hard lock"
248
+ that denotes the message has finished processing.
249
+
250
+ The soft lock has a default TTL of 15 minutes (a seemingly sane amount of time
251
+ during which processing most queue messages should certainly be able to
252
+ complete), while the hard lock has a default TTL of 24 hours (based upon
253
+ [a suggestion by an AWS employee](https://forums.aws.amazon.com/thread.jspa?threadID=140782#507605)).
254
+ The soft and hard TTL values can be changed by passing a `:soft_ttl` or
255
+ `:hard_ttl` value to the lock initializer, representing the number of seconds
256
+ that a lock should persist. For example:
257
+
258
+ ```ruby
259
+ Circuitry.config.lock_strategy = Circuitry::Lock::Memory.new(
260
+ soft_ttl: 10 * 60, # 10 minutes
261
+ hard_ttl: 48 * 60 * 60 # 48 hours
262
+ )
263
+ ```
264
+
265
+ #### Memory
266
+
267
+ If not specified in your circuitry configuration, the memory store will be used
268
+ by default. This lock strategy is provided as the lowest barrier to entry given
269
+ that it has no third-party dependencies. It should be avoided if running
270
+ multiple subscriber processes or if expecting a high throughput that would result
271
+ in a large amount of memory consumption.
272
+
273
+ ```ruby
274
+ Circuitry::Lock::Memory.new
275
+ ```
276
+
277
+ #### Redis
278
+
279
+ Using the redis lock strategy requires that you add `gem 'redis'` to your
280
+ `Gemfile`, as it is not included bundled with the circuitry gem by default.
281
+
282
+ There are two ways to use the redis lock strategy. The first is to pass your
283
+ redis connection options to the lock in the same way that you would when building
284
+ a new `Redis` object.
285
+
286
+ ```ruby
287
+ Circuitry::Lock::Redis.new(url: 'redis://localhost:6379')
288
+ ```
289
+
290
+ The second way is to pass in a `:client` option that specifies the redis client
291
+ itself. This is useful for more advanced usage such as sharing an existing redis
292
+ connection, utilizing [Redis::Namespace](https://github.com/resque/redis-namespace),
293
+ or utilizing [hiredis](https://github.com/redis/hiredis-rb).
294
+
295
+ ```ruby
296
+ client = Redis.new(url: 'redis://localhost:6379')
297
+ Circuitry::Lock::Redis.new(client: client)
298
+ ```
299
+
300
+ #### Memcache
301
+
302
+ Using the memcache lock strategy requires that you add `gem 'dalli'` to your
303
+ `Gemfile`, as it is not included bundled with the circuitry gem by default.
304
+
305
+ There are two ways to use the memcache lock strategy. The first is to pass your
306
+ dalli connection host and options to the lock in the same way that you would when
307
+ building a new `Dalli::Client` object. The special `host` option will be treated
308
+ as the memcache host, just as the first argument to `Dalli::Client`.
309
+
310
+ ```ruby
311
+ Circuitry::Lock::Memcache.new(host: 'localhost:11211', namespace: '...')
312
+ ```
313
+
314
+ The second way is to pass in a `:client` option that specifies the dalli client
315
+ itself. This is useful for sharing an existing memcache connection.
316
+
317
+ ```ruby
318
+ client = Dalli::Client.new('localhost:11211', namespace: '...')
319
+ Circuitry::Lock::Memcache.new(client: client)
320
+ ```
321
+
322
+ #### NOOP
323
+
324
+ Using the noop lock strategy permits you to continue to treat SQS as a
325
+ distributed queue in a true sense, meaning that you might receive duplicate
326
+ messages. Please refer to the Amazon SQS documentation pertaining to the
327
+ [Properties of Distributed Queues](http://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/DistributedQueues.html).
328
+
329
+ #### Custom
330
+
331
+ It's also possible to roll your own lock strategy. Simply create a class that
332
+ includes (or module that extends) `Circuitry::Lock::Base` and implements the
333
+ following methods:
334
+
335
+ * `lock`: Accepts the `key` and `ttl` as parameters. If the key is already
336
+ locked, this method must return false. If the key is not already locked, it
337
+ must lock the key for `ttl` seconds and return true. It is important that
338
+ the check and update are **atomic** in order to ensure the same message isn't
339
+ processed more than once.
340
+ * `lock!`: Accepts the `key` and `ttl` as parameters. Must lock the key for
341
+ `ttl` seconds regardless of whether or not the key was previously locked.
342
+
343
+ For example, a database-backed solution might look something like the following:
344
+
345
+ ```ruby
346
+ class DatabaseLockStrategy
347
+ include Circuitry::Lock::Base
348
+
349
+ def initialize(options = {})
350
+ super(options)
351
+ self.connection = options.fetch(:connection)
352
+ end
353
+
354
+ protected
355
+
356
+ def lock(key, ttl)
357
+ connection.exec("INSERT INTO locks (key, expires_at) VALUES ('#{key}', '#{Time.now + ttl}')")
358
+ end
359
+
360
+ def lock!(key, ttl)
361
+ connection.exec("UPSERT INTO locks (key, expires_at) VALUES ('#{key}', '#{Time.now + ttl}')")
362
+ end
363
+
364
+ private
365
+
366
+ attr_reader :connection
367
+ end
368
+ ```
369
+
370
+ To use, simply create an instance of the class with your necessary options, and
371
+ pass your lock instance to the configuration as the `:lock_strategy`.
372
+
373
+ ```ruby
374
+ connection = PG.connect(...)
375
+ Circuitry.config.lock_strategy = DatabaseLockStrategy.new(connection: connection)
376
+ ```
377
+
207
378
  ## Development
208
379
 
209
380
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
data/circuitry.gemspec CHANGED
@@ -22,10 +22,14 @@ Gem::Specification.new do |spec|
22
22
  spec.add_dependency 'fog-aws', '~> 0.4'
23
23
  spec.add_dependency 'virtus', '~> 1.0'
24
24
 
25
- spec.add_development_dependency 'pry-nav'
25
+ spec.add_development_dependency 'pry-byebug'
26
26
  spec.add_development_dependency 'bundler', '~> 1.8'
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.2'
29
29
  spec.add_development_dependency 'rspec-its', '~> 1.2'
30
30
  spec.add_development_dependency 'codeclimate-test-reporter'
31
+ spec.add_development_dependency 'redis'
32
+ spec.add_development_dependency 'mock_redis'
33
+ spec.add_development_dependency 'dalli'
34
+ spec.add_development_dependency 'memcache_mock'
31
35
  end
@@ -10,6 +10,7 @@ module Circuitry
10
10
  attribute :region, String, default: 'us-east-1'
11
11
  attribute :logger, Logger, default: Logger.new(STDERR)
12
12
  attribute :error_handler
13
+ attribute :lock_strategy, Object, default: ->(page, attribute) { Circuitry::Locks::Memory.new }
13
14
  attribute :publish_async_strategy, Symbol, default: ->(page, attribute) { :fork }
14
15
  attribute :subscribe_async_strategy, Symbol, default: ->(page, attribute) { :fork }
15
16
 
@@ -0,0 +1,41 @@
1
+ module Circuitry
2
+ module Locks
3
+ module Base
4
+ DEFAULT_SOFT_TTL = (15 * 60).freeze # 15 minutes
5
+ DEFAULT_HARD_TTL = (24 * 60 * 60).freeze # 24 hours
6
+
7
+ attr_reader :soft_ttl, :hard_ttl
8
+
9
+ def initialize(options = {})
10
+ self.soft_ttl = options.fetch(:soft_ttl, DEFAULT_SOFT_TTL)
11
+ self.hard_ttl = options.fetch(:hard_ttl, DEFAULT_HARD_TTL)
12
+ end
13
+
14
+ def soft_lock(id)
15
+ lock(lock_key(id), soft_ttl)
16
+ end
17
+
18
+ def hard_lock(id)
19
+ lock!(lock_key(id), hard_ttl)
20
+ end
21
+
22
+ protected
23
+
24
+ def lock(key, ttl)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def lock!(key, ttl)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ private
33
+
34
+ attr_writer :soft_ttl, :hard_ttl
35
+
36
+ def lock_key(id)
37
+ "circuitry:lock:#{id}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ module Circuitry
2
+ module Locks
3
+ class Memcache
4
+ include Base
5
+
6
+ def initialize(options = {})
7
+ super(options)
8
+
9
+ self.client = options.fetch(:client) do
10
+ require 'dalli'
11
+ ::Dalli::Client.new(options[:host], options)
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def lock(key, ttl)
18
+ client.add(key, (Time.now + ttl).to_i, ttl)
19
+ end
20
+
21
+ def lock!(key, ttl)
22
+ client.set(key, (Time.now + ttl).to_i, ttl)
23
+ end
24
+
25
+ private
26
+
27
+ attr_accessor :client
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ module Circuitry
2
+ module Locks
3
+ class Memory
4
+ include Base
5
+
6
+ class << self
7
+ def store
8
+ @store ||= {}
9
+ end
10
+
11
+ def semaphore
12
+ @semaphore ||= Mutex.new
13
+ end
14
+ end
15
+
16
+ protected
17
+
18
+ def lock(key, ttl)
19
+ reap
20
+
21
+ store do |store|
22
+ if store.has_key?(key)
23
+ false
24
+ else
25
+ store[key] = Time.now + ttl
26
+ true
27
+ end
28
+ end
29
+ end
30
+
31
+ def lock!(key, ttl)
32
+ reap
33
+
34
+ store do |store|
35
+ store[key] = Time.now + ttl
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def store(&block)
42
+ semaphore.synchronize do
43
+ block.call(self.class.store)
44
+ end
45
+ end
46
+
47
+ def reap
48
+ store do |store|
49
+ now = Time.now
50
+ store.delete_if { |key, expires_at| expires_at <= now }
51
+ end
52
+ end
53
+
54
+ def semaphore
55
+ self.class.semaphore
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ module Circuitry
2
+ module Locks
3
+ class NOOP
4
+ include Base
5
+
6
+ protected
7
+
8
+ def lock(key, ttl)
9
+ true
10
+ end
11
+
12
+ def lock!(key, ttl)
13
+ # do nothing
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Circuitry
2
+ module Locks
3
+ class Redis
4
+ include Base
5
+
6
+ def initialize(options = {})
7
+ super(options)
8
+
9
+ self.client = options.fetch(:client) do
10
+ require 'redis'
11
+ ::Redis.new(options)
12
+ end
13
+ end
14
+
15
+ protected
16
+
17
+ def lock(key, ttl)
18
+ client.set(key, (Time.now + ttl).to_i, ex: ttl, nx: true)
19
+ end
20
+
21
+ def lock!(key, ttl)
22
+ client.set(key, (Time.now + ttl).to_i, ex: ttl)
23
+ end
24
+
25
+ private
26
+
27
+ attr_accessor :client
28
+ end
29
+ end
30
+ end
@@ -1,6 +1,6 @@
1
1
  module Circuitry
2
2
  module Processor
3
- def process
3
+ def process(&block)
4
4
  raise NotImplementedError, "#{self} must implement class method `process`"
5
5
  end
6
6
 
@@ -1,4 +1,5 @@
1
1
  require 'json'
2
+ require 'timeout'
2
3
  require 'circuitry/concerns/async'
3
4
  require 'circuitry/services/sns'
4
5
  require 'circuitry/topic_creator'
@@ -12,12 +13,16 @@ module Circuitry
12
13
 
13
14
  DEFAULT_OPTIONS = {
14
15
  async: false,
16
+ timeout: 15,
15
17
  }.freeze
16
18
 
19
+ attr_reader :timeout
20
+
17
21
  def initialize(options = {})
18
22
  options = DEFAULT_OPTIONS.merge(options)
19
23
 
20
24
  self.async = options[:async]
25
+ self.timeout = options[:timeout]
21
26
  end
22
27
 
23
28
  def publish(topic_name, object)
@@ -30,8 +35,10 @@ module Circuitry
30
35
  end
31
36
 
32
37
  process = -> do
33
- topic = TopicCreator.find_or_create(topic_name)
34
- sns.publish(topic.arn, object.to_json)
38
+ Timeout.timeout(timeout) do
39
+ topic = TopicCreator.find_or_create(topic_name)
40
+ sns.publish(topic.arn, object.to_json)
41
+ end
35
42
  end
36
43
 
37
44
  if async?
@@ -45,6 +52,10 @@ module Circuitry
45
52
  Circuitry.config.publish_async_strategy
46
53
  end
47
54
 
55
+ protected
56
+
57
+ attr_writer :timeout
58
+
48
59
  private
49
60
 
50
61
  def logger
@@ -1,3 +1,4 @@
1
+ require 'timeout'
1
2
  require 'circuitry/concerns/async'
2
3
  require 'circuitry/services/sqs'
3
4
  require 'circuitry/message'
@@ -9,10 +10,12 @@ module Circuitry
9
10
  include Concerns::Async
10
11
  include Services::SQS
11
12
 
12
- attr_reader :queue, :wait_time, :batch_size, :max_retries, :failure_queue
13
+ attr_reader :queue, :timeout, :wait_time, :batch_size, :lock
13
14
 
14
15
  DEFAULT_OPTIONS = {
16
+ lock: true,
15
17
  async: false,
18
+ timeout: 15,
16
19
  wait_time: 10,
17
20
  batch_size: 10,
18
21
  }.freeze
@@ -27,7 +30,9 @@ module Circuitry
27
30
  options = DEFAULT_OPTIONS.merge(options)
28
31
 
29
32
  self.queue = queue
33
+ self.lock = options[:lock]
30
34
  self.async = options[:async]
35
+ self.timeout = options[:timeout]
31
36
  self.wait_time = options[:wait_time]
32
37
  self.batch_size = options[:batch_size]
33
38
  end
@@ -60,7 +65,18 @@ module Circuitry
60
65
 
61
66
  protected
62
67
 
63
- attr_writer :queue, :wait_time, :batch_size
68
+ attr_writer :queue, :timeout, :wait_time, :batch_size
69
+
70
+ def lock=(value)
71
+ value = case value
72
+ when true then Circuitry.config.lock_strategy
73
+ when false then Circuitry::Locks::NOOP.new
74
+ when Circuitry::Locks::Base then value
75
+ else raise ArgumentError, "Invalid value `#{value}`, must be one of `true`, `false`, or instance of `#{Circuitry::Locks::Base}`"
76
+ end
77
+
78
+ @lock = value
79
+ end
64
80
 
65
81
  private
66
82
 
@@ -85,21 +101,29 @@ module Circuitry
85
101
  def process_message(message, &block)
86
102
  message = Message.new(message)
87
103
 
88
- unless message.nil?
89
- logger.info("Processing message #{message.id}")
90
- handle_message(message, &block)
91
- delete_message(message)
92
- end
104
+ logger.info("Processing message #{message.id}")
105
+ handle_message(message, &block)
106
+ delete_message(message)
93
107
  rescue => e
94
108
  logger.error("Error processing message #{message.id}: #{e}")
95
109
  error_handler.call(e) if error_handler
96
110
  end
97
111
 
98
112
  def handle_message(message, &block)
99
- block.call(message.body, message.topic.name)
100
- rescue => e
101
- logger.error("Error handling message #{message.id}: #{e}")
102
- raise e
113
+ if lock.soft_lock(message.id)
114
+ begin
115
+ Timeout.timeout(timeout) do
116
+ block.call(message.body, message.topic.name)
117
+ end
118
+ rescue => e
119
+ logger.error("Error handling message #{message.id}: #{e}")
120
+ raise e
121
+ end
122
+
123
+ lock.hard_lock(message.id)
124
+ else
125
+ logger.info("Ignoring duplicate message #{message.id}")
126
+ end
103
127
  end
104
128
 
105
129
  def delete_message(message)
@@ -1,3 +1,3 @@
1
1
  module Circuitry
2
- VERSION = '1.1.0'
2
+ VERSION = '1.2.0'
3
3
  end
data/lib/circuitry.rb CHANGED
@@ -1,4 +1,9 @@
1
1
  require 'circuitry/version'
2
+ require 'circuitry/locks/base'
3
+ require 'circuitry/locks/memcache'
4
+ require 'circuitry/locks/memory'
5
+ require 'circuitry/locks/noop'
6
+ require 'circuitry/locks/redis'
2
7
  require 'circuitry/processor'
3
8
  require 'circuitry/processors/batcher'
4
9
  require 'circuitry/processors/forker'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: circuitry
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Huggins
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-07-08 00:00:00.000000000 Z
11
+ date: 2015-07-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fog-aws
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: pry-nav
42
+ name: pry-byebug
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
@@ -122,6 +122,62 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: redis
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mock_redis
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: dalli
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: memcache_mock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
125
181
  description: Amazon SNS publishing and SQS queue processing.
126
182
  email:
127
183
  - matt.huggins@kapost.com
@@ -142,6 +198,11 @@ files:
142
198
  - lib/circuitry.rb
143
199
  - lib/circuitry/concerns/async.rb
144
200
  - lib/circuitry/configuration.rb
201
+ - lib/circuitry/locks/base.rb
202
+ - lib/circuitry/locks/memcache.rb
203
+ - lib/circuitry/locks/memory.rb
204
+ - lib/circuitry/locks/noop.rb
205
+ - lib/circuitry/locks/redis.rb
145
206
  - lib/circuitry/message.rb
146
207
  - lib/circuitry/processor.rb
147
208
  - lib/circuitry/processors/batcher.rb