circuitry 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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