FanSQS 0.0.1
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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +2 -0
- data/FanSQS.gemspec +32 -0
- data/Gemfile +4 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +22 -0
- data/NOTES.txt +6 -0
- data/README.md +62 -0
- data/Rakefile +1 -0
- data/lib/FanSQS.rb +26 -0
- data/lib/FanSQS/message_parser.rb +12 -0
- data/lib/FanSQS/poller.rb +42 -0
- data/lib/FanSQS/pool.rb +155 -0
- data/lib/FanSQS/queue_wrapper.rb +37 -0
- data/lib/FanSQS/queues_cache.rb +59 -0
- data/lib/FanSQS/railtie.rb +7 -0
- data/lib/FanSQS/tasks/FanSQS.rake +7 -0
- data/lib/FanSQS/version.rb +3 -0
- data/lib/FanSQS/worker.rb +30 -0
- data/spec/FanSQS/message_parser_spec.rb +11 -0
- data/spec/FanSQS/poller_spec.rb +15 -0
- data/spec/FanSQS/pool_spec.rb +15 -0
- data/spec/FanSQS/queue_wrapper_spec.rb +64 -0
- data/spec/FanSQS/queues_cache_spec.rb +104 -0
- data/spec/FanSQS/worker_spec.rb +15 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/sqs_queue_mocks.rb +13 -0
- data/spec/support/sqs_queue_stubs.rb +47 -0
- data/spec/support/sqs_received_message_mocks.rb +17 -0
- data/spec/support/sqs_sent_message_mocks.rb +7 -0
- metadata +211 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cb1bcbb591271928ff993e7fe68b63c3b7f8e577
|
4
|
+
data.tar.gz: cd7e8775fe377c7c9158be44ea7bbd61676f61c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 396454510c18f3653fe53ebeb352cdd3574eeed301bd5ad0845f9147e9caf0ac394a320c3202001eb023ed953a899996d9830f42c8019c8999710b98f108df16
|
7
|
+
data.tar.gz: 8c7e247c2a1ea5912f04dd83f004f698c8c1e73123f1c6999037214562d8898d70b9aeef5066e26c360571e57eac600baaca23bafad7eb73c1fcb4ff89bddb2e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/FanSQS.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'FanSQS/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "FanSQS"
|
8
|
+
spec.version = FanSQS::VERSION
|
9
|
+
spec.authors = ["Khang Pham"]
|
10
|
+
spec.email = ["vkhang55@gmail.com"]
|
11
|
+
spec.summary = %q{Distributed messages queuing system using AWS SQS}
|
12
|
+
spec.description = %q{Distributed messages queuing system using AWS SQS}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rake"
|
22
|
+
spec.add_dependency "aws-sdk", "~> 1.41.0"
|
23
|
+
|
24
|
+
# For testing
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
26
|
+
spec.add_development_dependency "guard"
|
27
|
+
spec.add_development_dependency "guard-rspec"
|
28
|
+
spec.add_development_dependency 'pry'
|
29
|
+
spec.add_development_dependency "rspec"
|
30
|
+
spec.add_development_dependency 'rspec-mocks'
|
31
|
+
spec.add_development_dependency 'shoulda-matchers'
|
32
|
+
end
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :rspec do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
9
|
+
|
10
|
+
# Turnip features and steps
|
11
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
12
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
13
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Khang Pham
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NOTES.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# FanSQS
|
2
|
+
|
3
|
+
FanSQS is a background job processing engine for Ruby using [AWS SQS](http://aws.amazon.com/sqs/) for message storage.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'FanSQS'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install FanSQS
|
18
|
+
|
19
|
+
## Rails Setup
|
20
|
+
|
21
|
+
Create a **config/initializer.rb** file in your application directory and insert this line:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
AWS.config( access_key_id: '<your_access_key_id>', secret_access_key: '<your_secret_access_key>')
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
FanSQS integrates seemlessly with your Rails applications. Just *include* the module **FanSQS::Worker** in the class that encapsulates your job logic and define a class method called *perform*. The method *perform* should be able to accept any number of parameters of any type.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class MessagePublisher
|
33
|
+
include FanSQS::Worker
|
34
|
+
set_fan_sqs_options queue: :message_queue # use the key :queue to define the message queue name
|
35
|
+
|
36
|
+
def self.perform(arg1, arg2, ...)
|
37
|
+
# code to do work
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
To push jobs into an SQS queue for processing, simply call:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
MessagePublisher.perform_async(arg1, arg2, ...)
|
46
|
+
```
|
47
|
+
|
48
|
+
## Features to add
|
49
|
+
|
50
|
+
1. Rake task to start poller
|
51
|
+
2. Priority queues
|
52
|
+
3. Auto retries on error
|
53
|
+
4. Hook up to Travis
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
1. Fork it ( http://github.com/<my-github-username>/FanSQS/fork )
|
58
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
59
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
60
|
+
4. Test coverage for your changes. Sorry I will not merge changes without test coverage!
|
61
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
62
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/FanSQS.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require "FanSQS/pool"
|
2
|
+
require "FanSQS/version"
|
3
|
+
require "FanSQS/message_parser"
|
4
|
+
require "FanSQS/queue_wrapper"
|
5
|
+
require "FanSQS/queues_cache"
|
6
|
+
require "FanSQS/poller"
|
7
|
+
require "FanSQS/worker"
|
8
|
+
require "aws"
|
9
|
+
|
10
|
+
# Reference: [http://ruby.awsblog.com/post/Tx16QY1CI5GVBFT/Threading-with-the-AWS-SDK-for-Ruby]
|
11
|
+
# Issue this line will give significant performance boost
|
12
|
+
AWS.eager_autoload! AWS::SQS
|
13
|
+
|
14
|
+
# $pool = Pool.new(50) # Performs max at 50 threads. Need to make this value configurable later.
|
15
|
+
|
16
|
+
module FanSQS
|
17
|
+
extend self
|
18
|
+
|
19
|
+
ErrorQueue = :fan_sqs_queue_error
|
20
|
+
class << self
|
21
|
+
attr_accessor :pool
|
22
|
+
end
|
23
|
+
self.pool = Pool.new(100)
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'FanSQS/railtie' if defined?(Rails) # include FanSQS Rake task if Rails is defined
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module FanSQS
|
4
|
+
class Poller
|
5
|
+
class << self
|
6
|
+
def start(qnames = [])
|
7
|
+
@queues_cache = FanSQS::QueuesCache.new(qnames)
|
8
|
+
loop do
|
9
|
+
@queues_cache.fetch.each do |queue|
|
10
|
+
FanSQS.pool.schedule do # Allows for multiple concurrent (non-blocking) HTTP requests to SQS
|
11
|
+
queue.receive_messages(limit: 10) do |message|
|
12
|
+
process(message.body)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def process(msg)
|
22
|
+
message = MessageParser.parse(msg)
|
23
|
+
Thread.new do
|
24
|
+
begin
|
25
|
+
klass = Object::const_get(message[:class])
|
26
|
+
klass.send(:perform, *message[:arguments])
|
27
|
+
rescue ArgumentError => e
|
28
|
+
store_exception(e, message)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def store_exception(exception, message)
|
34
|
+
error_message = { class: message[:class],
|
35
|
+
arguments: message[:arguments],
|
36
|
+
stack_trace: exception.backtrace.join("\n") }
|
37
|
+
queue = FanSQS::QueueWrapper.instantiate(FanSQS::ErrorQueue)
|
38
|
+
queue.send_message(error_message.to_json)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/FanSQS/pool.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Credit to Kim Burgestrand @ http://burgestrand.se/articles/quick-and-simple-ruby-thread-pool.html
|
2
|
+
|
3
|
+
# Ruby Thread Pool
|
4
|
+
# ================
|
5
|
+
# A thread pool is useful when you wish to do some work in a thread, but do
|
6
|
+
# not know how much work you will be doing in advance. Spawning one thread
|
7
|
+
# for each task is potentially expensive, as threads are not free.
|
8
|
+
#
|
9
|
+
# In this case, it might be more beneficial to start a predefined set of
|
10
|
+
# threads and then hand off work to them as it becomes available. This is
|
11
|
+
# the pure essence of what a thread pool is: an array of threads, all just
|
12
|
+
# waiting to do some work for you!
|
13
|
+
#
|
14
|
+
# Prerequisites
|
15
|
+
# -------------
|
16
|
+
|
17
|
+
# We need the [Queue](http://rdoc.info/stdlib/thread/1.9.2/Queue), as our
|
18
|
+
# thread pool is largely dependent on it. Thanks to this, the implementation
|
19
|
+
# becomes very simple!
|
20
|
+
require 'thread'
|
21
|
+
|
22
|
+
# Public Interface
|
23
|
+
# ----------------
|
24
|
+
|
25
|
+
# `Pool` is our thread pool class. It will allow us to do three operations:
|
26
|
+
#
|
27
|
+
# - `.new(size)` creates a thread pool of a given size
|
28
|
+
# - `#schedule(*args, &job)` schedules a new job to be executed
|
29
|
+
# - `#shutdown` shuts down all threads (after letting them finish working, of course)
|
30
|
+
module FanSQS
|
31
|
+
class Pool
|
32
|
+
|
33
|
+
# ### initialization, or `Pool.new(size)`
|
34
|
+
# Creating a new `Pool` involves a certain amount of work. First, however,
|
35
|
+
# we need to define its’ `size`. It defines how many threads we will have
|
36
|
+
# working internally.
|
37
|
+
#
|
38
|
+
# Which size is best for you is hard to answer. You do not want it to be
|
39
|
+
# too low, as then you won’t be able to do as many things concurrently.
|
40
|
+
# However, if you make it too high Ruby will spend too much time switching
|
41
|
+
# between threads, and that will also degrade performance!
|
42
|
+
def initialize(size)
|
43
|
+
# Before we do anything else, we need to store some information about
|
44
|
+
# our pool. `@size` is useful later, when we want to shut our pool down,
|
45
|
+
# and `@jobs` is the heart of our pool that allows us to schedule work.
|
46
|
+
@size = size
|
47
|
+
@jobs = Queue.new
|
48
|
+
|
49
|
+
# #### Creating our pool of threads
|
50
|
+
# Once preparation is done, it’s time to create our pool of threads.
|
51
|
+
# Each thread store its’ index in a thread-local variable, in case we
|
52
|
+
# need to know which thread a job is executing in later on.
|
53
|
+
@pool = Array.new(@size) do |i|
|
54
|
+
Thread.new do
|
55
|
+
Thread.current[:id] = i
|
56
|
+
|
57
|
+
# We start off by defining a `catch` around our worker loop. This
|
58
|
+
# way we’ve provided a method for graceful shutdown of our threads.
|
59
|
+
# Shutting down is merely a `#schedule { throw :exit }` away!
|
60
|
+
catch(:exit) do
|
61
|
+
# The worker thread life-cycle is very simple. We continuously wait
|
62
|
+
# for tasks to be put into our job `Queue`. If the `Queue` is empty,
|
63
|
+
# we will wait until it’s not.
|
64
|
+
loop do
|
65
|
+
# Once we have a piece of work to be done, we will pull out the
|
66
|
+
# information we need and get to work.
|
67
|
+
job, args = @jobs.pop
|
68
|
+
job.call(*args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ### Work scheduling
|
76
|
+
|
77
|
+
# To schedule a piece of work to be done is to say to the `Pool` that you
|
78
|
+
# want something done.
|
79
|
+
def schedule(*args, &block)
|
80
|
+
# Your given task will not be run immediately; rather, it will be put
|
81
|
+
# into the work `Queue` and executed once a thread is ready to work.
|
82
|
+
@jobs << [block, args]
|
83
|
+
end
|
84
|
+
|
85
|
+
# ### Graceful shutdown
|
86
|
+
|
87
|
+
# If you ever wish to close down your application, I took the liberty of
|
88
|
+
# making it easy for you to wait for any currently executing jobs to finish
|
89
|
+
# before you exit.
|
90
|
+
def shutdown
|
91
|
+
# A graceful shutdown involves threads exiting cleanly themselves, and
|
92
|
+
# since we’ve defined a `catch`-handler around the threads’ worker loop
|
93
|
+
# it is simply a matter of throwing `:exit`. Thus, if we throw one `:exit`
|
94
|
+
# for each thread in our pool, they will all exit eventually!
|
95
|
+
@size.times do
|
96
|
+
schedule { throw :exit }
|
97
|
+
end
|
98
|
+
|
99
|
+
# And now one final thing: wait for our `throw :exit` jobs to be run on
|
100
|
+
# all our worker threads. This call will not return until all worker threads
|
101
|
+
# have exited.
|
102
|
+
@pool.map(&:join)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Demonstration
|
108
|
+
# -------------
|
109
|
+
# Running this file will display how the thread pool works.
|
110
|
+
# if $0 == __FILE__
|
111
|
+
# # - First, we create a new thread pool with a size of 10. This number is
|
112
|
+
# # lower than our planned amount of work, to show that threads do not
|
113
|
+
# # exit once they have finished a task.
|
114
|
+
# p = Pool.new(10)
|
115
|
+
#
|
116
|
+
# # - Next we simulate some workload by scheduling a large amount of work
|
117
|
+
# # to be done. The actual time taken for each job is randomized. This
|
118
|
+
# # is to demonstrate that even if two tasks are scheduled approximately
|
119
|
+
# # at the same time, the one that takes less time to execute is likely
|
120
|
+
# # to finish before the other one.
|
121
|
+
# 20.times do |i|
|
122
|
+
# p.schedule do
|
123
|
+
# sleep rand(4) + 2
|
124
|
+
# puts "Job #{i} finished by thread #{Thread.current[:id]}"
|
125
|
+
# end
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# # - Finally, register an `at_exit`-hook that will wait for our thread pool
|
129
|
+
# # to properly shut down before allowing our script to completely exit.
|
130
|
+
# at_exit { p.shutdown }
|
131
|
+
# end
|
132
|
+
|
133
|
+
# License (X11 License)
|
134
|
+
# =====================
|
135
|
+
#
|
136
|
+
# Copyright (c) 2012, Kim Burgestrand <kim@burgestrand.se>
|
137
|
+
#
|
138
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
139
|
+
# of this software and associated documentation files (the "Software"), to deal
|
140
|
+
# in the Software without restriction, including without limitation the rights
|
141
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
142
|
+
# copies of the Software, and to permit persons to whom the Software is
|
143
|
+
# furnished to do so, subject to the following conditions:
|
144
|
+
#
|
145
|
+
# The above copyright notice and this permission notice shall be included in
|
146
|
+
# all copies or substantial portions of the Software.
|
147
|
+
#
|
148
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
149
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
150
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
151
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
152
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
153
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
154
|
+
# SOFTWARE.
|
155
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module FanSQS
|
2
|
+
class QueueWrapper
|
3
|
+
@cache ||= {} # cache for queues
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# Find out if the queue exists. If it does not exist, create a new one
|
7
|
+
# This line below has a high chance of causing confusion as it will try to use either FanSQS::Queue or AWS::SQS::Queue. Maybe use the class from AWS gem instead?
|
8
|
+
def instantiate(qname)
|
9
|
+
name = formatted_queue_name(qname)
|
10
|
+
exists?(name) || create_new(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_new(name)
|
14
|
+
AWS.sqs.queues.create(name.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache
|
18
|
+
@cache
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?(qname)
|
22
|
+
if @cache[qname]
|
23
|
+
return @cache[qname]
|
24
|
+
else
|
25
|
+
return @cache[qname] = AWS.sqs.queues.named(formatted_queue_name(qname)) while @cache[qname] == nil
|
26
|
+
end
|
27
|
+
rescue AWS::SQS::Errors::NonExistentQueue
|
28
|
+
return false
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def formatted_queue_name(qname)
|
33
|
+
qname.to_s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# QueuesCache's purpose is for speed optimization. Using this class, we do not have to constantly ping
|
2
|
+
# AWS for new queues. It will ping AWS only every X times.
|
3
|
+
module FanSQS
|
4
|
+
class QueuesCache
|
5
|
+
RESET_THRESHOLD = 20000
|
6
|
+
|
7
|
+
def initialize(qnames)
|
8
|
+
@sqs_client ||= AWS::SQS::Client.new
|
9
|
+
@qnames = resolve_qnames(qnames)
|
10
|
+
@counter = 1
|
11
|
+
@queues = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
if @qnames.empty?
|
16
|
+
fetch_all_queues
|
17
|
+
else
|
18
|
+
fetch_specific_queues
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
# Fetch all queues except for the queue equals to FanSQS::ErrorQueue
|
24
|
+
def fetch_all_queues
|
25
|
+
if @counter % RESET_THRESHOLD == 0
|
26
|
+
@counter = 1 # reset counter
|
27
|
+
@queue_names = @sqs_client.list_queues[:queue_urls].map { |q| q.split('/').last }.uniq
|
28
|
+
@queue_names.reject! { |name| name == FanSQS::ErrorQueue.to_s } # do not include the error queue
|
29
|
+
@queues = @queue_names.inject([]) { |queues, name| queues << QueueWrapper.instantiate(name) } # reset cache & fetch queues from AWS
|
30
|
+
else
|
31
|
+
@counter += 1
|
32
|
+
@queues
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Fetch only specific queues. Can also fetch queues with prefix = "xxx*"
|
37
|
+
def fetch_specific_queues
|
38
|
+
return @queues unless @queues.empty?
|
39
|
+
@qnames.each do |qname|
|
40
|
+
if qname =~ /\*/
|
41
|
+
queues_with_prefix = AWS::SQS.new.queues.with_prefix(qname.split('*').first)
|
42
|
+
queues_with_prefix.each do |queue|
|
43
|
+
@queues << queue
|
44
|
+
end
|
45
|
+
else
|
46
|
+
@queues << AWS::SQS.new.queues.named(qname)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_qnames(qnames)
|
52
|
+
if qnames.is_a?(Array)
|
53
|
+
qnames.map(&:to_s)
|
54
|
+
else
|
55
|
+
qnames.to_s.split(',').uniq
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module FanSQS
|
4
|
+
module Worker
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.class_attribute :fan_sqs_options_hash
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def perform_async(*args)
|
12
|
+
FanSQS.pool.schedule do # Allows for multiple concurrent (non-blocking) HTTP requests to SQS
|
13
|
+
queue, sent_message = nil
|
14
|
+
qname = fan_sqs_options_hash ? fan_sqs_options_hash[:queue] : :fan_sqs_queue
|
15
|
+
queue = FanSQS::QueueWrapper.instantiate(qname)
|
16
|
+
params = { class: self.name, arguments: args }
|
17
|
+
sent_message = queue.send_message(params.to_json) while sent_message.try(&:md5) == nil # retry until receives sent_message confirmation
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_fan_sqs_options(options = {})
|
22
|
+
if options.empty?
|
23
|
+
self.fan_sqs_options_hash = { queue: :fan_sqs_queue }
|
24
|
+
else
|
25
|
+
self.fan_sqs_options_hash = options
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FanSQS::MessageParser do
|
4
|
+
describe "#parse" do
|
5
|
+
it "should return a JSON object with expected result" do
|
6
|
+
msg = { a: 1, b: 2}
|
7
|
+
json = FanSQS::MessageParser.parse(msg.to_json)
|
8
|
+
expect(json).to eq(msg)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FanSQS::Poller do
|
4
|
+
describe "#start" do
|
5
|
+
it "is a pending example"
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#process" do
|
9
|
+
it "is a pending example"
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#store_exception" do
|
13
|
+
it "is a pending example"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FanSQS::QueueWrapper do
|
4
|
+
before do
|
5
|
+
stub_retrieving_named_queues
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#exists?" do
|
9
|
+
context "cached" do
|
10
|
+
it "should returns a named queue from @cache" do
|
11
|
+
sample_queue = mocked_queue
|
12
|
+
FanSQS::QueueWrapper.instance_variable_set(:@cache, {:sample_qname => sample_queue})
|
13
|
+
expect(FanSQS::QueueWrapper.exists?(:sample_qname)).to eq(sample_queue)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "not cached" do
|
18
|
+
before do
|
19
|
+
FanSQS::QueueWrapper.instance_variable_set(:@cache, {}) #empty cache
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should returns fetches the queue from AWS" do
|
23
|
+
expect_any_instance_of(AWS::SQS::QueueCollection).to receive(:named)
|
24
|
+
FanSQS::QueueWrapper.exists?(:sample_qname)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "non-existent queue" do
|
28
|
+
it "should raise exception AWS::SQS::Errors::NonExistentQueue and returns false" do
|
29
|
+
stub_retrieving_named_queues_raise_exception
|
30
|
+
expect(FanSQS::QueueWrapper.exists?(:sample_qname)).to eq(false)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#create_new" do
|
37
|
+
it "should call #create method from QueueCollection" do
|
38
|
+
expect_any_instance_of(AWS::SQS::QueueCollection).to receive(:create).once
|
39
|
+
FanSQS::QueueWrapper.create_new(:sample_queue)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#instantiate" do
|
44
|
+
context "cached" do
|
45
|
+
it "should call exists?, receive a mocked queue, and not call create_new" do
|
46
|
+
sample_queue = mocked_queue
|
47
|
+
FanSQS::QueueWrapper.instance_variable_set(:@cache, {:sample_qname => sample_queue})
|
48
|
+
expect(FanSQS::QueueWrapper).to_not receive(:create_new)
|
49
|
+
FanSQS::QueueWrapper.instantiate(:sample_qname)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should call exists?, catch an exception, and call create_new" do
|
53
|
+
stub_retrieving_named_queues_raise_exception
|
54
|
+
FanSQS::QueueWrapper.instance_variable_set(:@cache, {})
|
55
|
+
expect(FanSQS::QueueWrapper).to_not receive(:create_new).once
|
56
|
+
FanSQS::QueueWrapper.instantiate(:sample_qname)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "formats symbol to string qname" do
|
62
|
+
expect(FanSQS::QueueWrapper.send(:formatted_queue_name, :symbol)).to be_an_instance_of(String)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FanSQS::QueuesCache do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "should call these methods and end up with these values" do
|
6
|
+
qnames = [ :queue_1, :queue_2 ]
|
7
|
+
@queues_cache = FanSQS::QueuesCache.new(qnames)
|
8
|
+
expect(@queues_cache.instance_variable_get(:@qnames)).to eq(qnames.map(&:to_s))
|
9
|
+
expect(@queues_cache.instance_variable_get(:@counter)).to eq(1)
|
10
|
+
expect(@queues_cache.instance_variable_get(:@queues)).to eq([])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#fetch" do
|
15
|
+
context "@qnames.empty" do
|
16
|
+
it "should call fetch_all_queues" do
|
17
|
+
qnames = []
|
18
|
+
@queues_cache = FanSQS::QueuesCache.new(qnames)
|
19
|
+
expect(@queues_cache).to receive(:fetch_all_queues).once
|
20
|
+
@queues_cache.fetch
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "@qnames is not empty" do
|
25
|
+
it "should call fetch_specific_queues" do
|
26
|
+
qnames = [:queue_1, :queue_2]
|
27
|
+
@queues_cache = FanSQS::QueuesCache.new(qnames)
|
28
|
+
expect(@queues_cache).to receive(:fetch_specific_queues).once
|
29
|
+
@queues_cache.fetch
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#fetch_all_queues" do
|
35
|
+
context "counter % RESET_THRESHOLD != 0" do
|
36
|
+
before do
|
37
|
+
@queues_cache = FanSQS::QueuesCache.new([:queue_1, :queue_2])
|
38
|
+
@sample_queues = [ mocked_queue('queue_1'), mocked_queue('queue_2') ]
|
39
|
+
@queues_cache.instance_variable_set(:@queues, @sample_queues)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return 2 queues" do
|
43
|
+
queues = @queues_cache.send(:fetch_all_queues)
|
44
|
+
expect(queues.size).to eq(2)
|
45
|
+
expect(queues).to eq(@sample_queues)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "counter % RESET_THRESHOLD == 0" do
|
50
|
+
before do
|
51
|
+
stub_client_list_queues
|
52
|
+
stub_retrieving_named_queues
|
53
|
+
@queues_cache = FanSQS::QueuesCache.new([:queue_1, :queue_2, :queue_3])
|
54
|
+
@queues_cache.instance_variable_set(:@cache, { :queue_1 => mocked_queue('queue_1'), :queue_2 => mocked_queue('queue_2'), :queue_3 => mocked_queue('queue_3')})
|
55
|
+
@queues_cache.instance_variable_set(:@counter, FanSQS::QueuesCache::RESET_THRESHOLD)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return 3 queues" do
|
59
|
+
queues = @queues_cache.send(:fetch_all_queues)
|
60
|
+
expect(queues.size).to eq(3)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#fetch_specific_queues" do
|
66
|
+
context "@queues is not empty" do
|
67
|
+
before do
|
68
|
+
@queues_cache = FanSQS::QueuesCache.new([:queue_1, :queue_2, :queue_3])
|
69
|
+
@queues_cache.instance_variable_set(:@queues, [ mocked_queue('queue_1') ])
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return @queues" do
|
73
|
+
queues = @queues_cache.send(:fetch_specific_queues)
|
74
|
+
expect(queues.size).to eq(1)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "@queues is empty" do
|
79
|
+
before do
|
80
|
+
stub_retrieving_named_queues
|
81
|
+
@queues_cache = FanSQS::QueuesCache.new([:queue_1, :queue_2])
|
82
|
+
@queues_cache.instance_variable_set(:@queues, [ ])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return 2 queues" do
|
86
|
+
queues = @queues_cache.send(:fetch_specific_queues)
|
87
|
+
expect(queues.size).to eq(2)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#resolve_qnames" do
|
93
|
+
let!(:qnames) { [ :q1, :q2 ] }
|
94
|
+
let!(:obj) { FanSQS::QueuesCache.new(:sample) }
|
95
|
+
|
96
|
+
it "should return the argument as it is passed in if the argument is an Array" do
|
97
|
+
expect(obj.send(:resolve_qnames, qnames)).to eq(qnames.map(&:to_s))
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should return the list of queues if it is passed in as a String" do
|
101
|
+
expect(obj.send(:resolve_qnames, 'q1,q2,q3,q1').sort).to eq([ 'q1', 'q2', 'q3' ])
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FanSQS::Worker do
|
4
|
+
describe "#included" do
|
5
|
+
it "is a pending example"
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#perform_async" do
|
9
|
+
it "is a pending example"
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#set_fan_sqs_options" do
|
13
|
+
it "is a pending example"
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'FanSQS'
|
9
|
+
|
10
|
+
Dir["./spec/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
|
16
|
+
config.include SQSQueueMocks
|
17
|
+
config.include SQSQueueStubs
|
18
|
+
config.include SQSReceivedMessageMocks
|
19
|
+
config.include SQSSentMessageMocks
|
20
|
+
|
21
|
+
# Run specs in random order to surface order dependencies. If you find an
|
22
|
+
# order dependency and want to debug it, you can fix the order by providing
|
23
|
+
# the seed, which is printed after each run.
|
24
|
+
# --seed 1234
|
25
|
+
config.order = 'random'
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module SQSQueueMocks
|
2
|
+
def mocked_queue(name = 'queue')
|
3
|
+
queue = double(name)
|
4
|
+
received_messages = mocked_received_message_collection(10)
|
5
|
+
allow(queue).to receive(:receive_messages).with({ limit: 10 }).and_return(received_messages)
|
6
|
+
end
|
7
|
+
|
8
|
+
def mocked_queue_collection(size = 3)
|
9
|
+
queues = []
|
10
|
+
size.times { |i| queues << mocked_queue("queue_#{i}") }
|
11
|
+
queues
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SQSQueueStubs
|
2
|
+
# stubbed for
|
3
|
+
# AWS.sqs.queues.create(name.to_s)
|
4
|
+
def stub_queue_create
|
5
|
+
allow_any_instance_of(AWS::SQS::QueueCollection).to receive(:create).with(an_instance_of(String)).and_return(mocked_queue)
|
6
|
+
end
|
7
|
+
|
8
|
+
# stubbed for
|
9
|
+
# AWS::SQS.new.queues.named(qname)
|
10
|
+
# AWS.sqs.queues.named(formatted_queue_name(qname))
|
11
|
+
def stub_retrieving_named_queues
|
12
|
+
allow_any_instance_of(AWS::SQS::QueueCollection).to receive(:named).with(an_instance_of(String)).and_return(mocked_queue)
|
13
|
+
end
|
14
|
+
|
15
|
+
# stubbed for
|
16
|
+
# AWS::SQS.new.queues.named(qname)
|
17
|
+
def stub_retrieving_named_queues_raise_exception
|
18
|
+
allow_any_instance_of(AWS::SQS::QueueCollection).to receive(:named).with(an_instance_of(String)).and_raise(AWS::SQS::Errors::NonExistentQueue)
|
19
|
+
end
|
20
|
+
|
21
|
+
# stubbed for
|
22
|
+
# AWS::SQS.new.queues.with_prefix(qname.split('*').first)
|
23
|
+
def stub_retrieving_queues_with_prefix
|
24
|
+
allow_any_instance_of(AWS::SQS::QueueCollection).to receive(:with_refix).with(any_args).and_return(mocked_queue_collection)
|
25
|
+
end
|
26
|
+
|
27
|
+
# stubbed for
|
28
|
+
# AWS::SQS::Client.new.list_queues[:queue_urls]
|
29
|
+
def stub_client_list_queues
|
30
|
+
queue_urls = { queue_urls: ["https://sqs.us-east-1.amazonaws.com/1/queue_1", "https://sqs.us-east-1.amazonaws.com/2/queue_2", "https://sqs.us-east-1.amazonaws.com/3/queue_3"] }
|
31
|
+
allow_any_instance_of(AWS::SQS::Client::V20121105).to receive(:list_queues).and_return(queue_urls)
|
32
|
+
end
|
33
|
+
|
34
|
+
# stub for
|
35
|
+
# queue.receive_messages(limit: 10) do |message|
|
36
|
+
def stub_queue_receive_messages(size = 10)
|
37
|
+
received_messages = mocked_received_message_collection(size)
|
38
|
+
allow_any_instance_of(AWS::SQS::Queue).to receive(:receive_messages).with(limit: size).and_yield(receive_messages)
|
39
|
+
end
|
40
|
+
|
41
|
+
# stub for
|
42
|
+
# params = { class: self.name, arguments: args }
|
43
|
+
# queue.send_message(params.to_json)
|
44
|
+
def stub_queue_send_message(size = 10)
|
45
|
+
allow_any_instance_of(AWS::SQS::Queue).to receive(:send_message).with(an_instance_of(Hash)).and_return(mocked_sent_message)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SQSReceivedMessageMocks
|
2
|
+
def mocked_received_message(name = 'received_message')
|
3
|
+
received_message = double(name)
|
4
|
+
allow(received_message).to receive(:body).and_return(
|
5
|
+
{
|
6
|
+
class: 'MessagePublisher',
|
7
|
+
arguments: [ name ]
|
8
|
+
}.to_json
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def mocked_received_message_collection(size = 3)
|
13
|
+
received_messages = []
|
14
|
+
size.times { |i| received_messages << mocked_received_message("received_message_#{i}") }
|
15
|
+
received_messages
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: FanSQS
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Khang Pham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.41.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.41.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-mocks
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: shoulda-matchers
|
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
|
+
description: Distributed messages queuing system using AWS SQS
|
140
|
+
email:
|
141
|
+
- vkhang55@gmail.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".rspec"
|
148
|
+
- FanSQS.gemspec
|
149
|
+
- Gemfile
|
150
|
+
- Guardfile
|
151
|
+
- LICENSE.txt
|
152
|
+
- NOTES.txt
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- lib/FanSQS.rb
|
156
|
+
- lib/FanSQS/message_parser.rb
|
157
|
+
- lib/FanSQS/poller.rb
|
158
|
+
- lib/FanSQS/pool.rb
|
159
|
+
- lib/FanSQS/queue_wrapper.rb
|
160
|
+
- lib/FanSQS/queues_cache.rb
|
161
|
+
- lib/FanSQS/railtie.rb
|
162
|
+
- lib/FanSQS/tasks/FanSQS.rake
|
163
|
+
- lib/FanSQS/version.rb
|
164
|
+
- lib/FanSQS/worker.rb
|
165
|
+
- spec/FanSQS/message_parser_spec.rb
|
166
|
+
- spec/FanSQS/poller_spec.rb
|
167
|
+
- spec/FanSQS/pool_spec.rb
|
168
|
+
- spec/FanSQS/queue_wrapper_spec.rb
|
169
|
+
- spec/FanSQS/queues_cache_spec.rb
|
170
|
+
- spec/FanSQS/worker_spec.rb
|
171
|
+
- spec/spec_helper.rb
|
172
|
+
- spec/support/sqs_queue_mocks.rb
|
173
|
+
- spec/support/sqs_queue_stubs.rb
|
174
|
+
- spec/support/sqs_received_message_mocks.rb
|
175
|
+
- spec/support/sqs_sent_message_mocks.rb
|
176
|
+
homepage: ''
|
177
|
+
licenses:
|
178
|
+
- MIT
|
179
|
+
metadata: {}
|
180
|
+
post_install_message:
|
181
|
+
rdoc_options: []
|
182
|
+
require_paths:
|
183
|
+
- lib
|
184
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
185
|
+
requirements:
|
186
|
+
- - ">="
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
requirements: []
|
195
|
+
rubyforge_project:
|
196
|
+
rubygems_version: 2.2.2
|
197
|
+
signing_key:
|
198
|
+
specification_version: 4
|
199
|
+
summary: Distributed messages queuing system using AWS SQS
|
200
|
+
test_files:
|
201
|
+
- spec/FanSQS/message_parser_spec.rb
|
202
|
+
- spec/FanSQS/poller_spec.rb
|
203
|
+
- spec/FanSQS/pool_spec.rb
|
204
|
+
- spec/FanSQS/queue_wrapper_spec.rb
|
205
|
+
- spec/FanSQS/queues_cache_spec.rb
|
206
|
+
- spec/FanSQS/worker_spec.rb
|
207
|
+
- spec/spec_helper.rb
|
208
|
+
- spec/support/sqs_queue_mocks.rb
|
209
|
+
- spec/support/sqs_queue_stubs.rb
|
210
|
+
- spec/support/sqs_received_message_mocks.rb
|
211
|
+
- spec/support/sqs_sent_message_mocks.rb
|