afterparty 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: d5a18d9460144527504869b72c7a377ce2c6eda0
4
+ data.tar.gz: 4d9b52a88c5fea21d37da73b1a13f5f5bf180c9e
5
+ !binary "U0hBNTEy":
6
+ metadata.gz: d6323eb6eb7a72b1090207d498ae02890dbfca347bdc37414453f67441725e966b4205651d53d4756e15ce7a786ff1a1058c98572439c67e931ab0f150ce70c7
7
+ data.tar.gz: 1888cd7d556ee3f5164935ac9fb7a9b57dcf9fb1808ff8eae57315bd8175f3080e2a1dca599437bbf87770f11acc48b598fd62a1272a4c8077f0c7385304d9a1
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ services:
6
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in afterparty.gemspec
4
+ gemspec
5
+ gem 'rspec'
6
+ gem 'rails', github: "rails/rails", branch: "jobs", tag: "v4.0.0.rc1"
7
+ gem 'awesome_print'
8
+ gem 'guard-rspec'
9
+ gem 'ruby_gntp'
10
+ gem 'redis'
data/Guardfile ADDED
@@ -0,0 +1,24 @@
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
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+
17
+ # Capybara features specs
18
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
+
20
+ # Turnip features and steps
21
+ watch(%r{^spec/acceptance/(.+)\.feature$})
22
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
+ end
24
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Hank Stoever
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/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Afterparty
2
+
3
+ A Rails 4 compatible queue with support for executing jobs in the future and serialization with Redis.
4
+
5
+ ## Installation
6
+
7
+ Make sure you've installed [redis](http://redis.io) on your machine.
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ~~~Ruby
12
+ gem 'afterparty'
13
+ ~~~
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install afterparty
22
+
23
+ In your desired application environment, like `application.rb`:
24
+
25
+ ~~~Ruby
26
+ config.queue = Afterparty::RedisQueue.new
27
+ ~~~
28
+
29
+ ## Usage
30
+
31
+ A `job` is a ruby object with a `run` method.
32
+
33
+ ~~~Ruby
34
+ class Job
35
+ def run
36
+ puts "Hello!"
37
+ end
38
+ end
39
+ ~~~
40
+
41
+ Then add it to the queue at any time.
42
+
43
+ ~~~Ruby
44
+ Rails.queue << Job.new
45
+ ~~~
46
+
47
+ If your job responds to an `execute_at` method, the queue will wait to process that job until the specified time.
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Add a test in `spec/redis_queue_spec.rb`
54
+ 4. Make sure tests pass when you run `rake`
55
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
56
+ 4. Push to the branch (`git push origin my-new-feature`)
57
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'afterparty/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "afterparty"
8
+ spec.version = Afterparty::VERSION
9
+ spec.authors = ["Hank Stoever"]
10
+ spec.email = ["hstove@gmail.com"]
11
+ spec.description = %q{Rails 4 compatible queue with support for executing jobs later.}
12
+ spec.summary = %q{Rails 4 compatible queue with support for executing jobs later.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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 "redis"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.3"
24
+ spec.add_development_dependency "rake"
25
+ end
data/dump.rdb ADDED
Binary file
@@ -0,0 +1,67 @@
1
+ module Afterparty
2
+ module QueueHelpers
3
+ def [] namespace
4
+ @temp_namespace = namespace
5
+ end
6
+
7
+ def redis_queue_name
8
+ "afterparty_#{@temp_namespace || @options[:namespace]}_queue"
9
+ end
10
+
11
+ def clear
12
+ redis_call :del
13
+ end
14
+
15
+ def redis_call command, *args
16
+ result = Afterparty.redis.send(command, redis_queue_name, *args)
17
+ @temp_namespace = nil
18
+ result
19
+ end
20
+
21
+ def async_redis_call &block
22
+ Afterparty.redis.pipelined &block
23
+ end
24
+
25
+ def jobs
26
+ _jobs = redis_call(:zrange, 0, -1)
27
+ _jobs.each_with_index do |job, i|
28
+ _jobs[i] = Marshal.load(job)
29
+ end
30
+ _jobs
31
+ end
32
+
33
+ def jobs_with_scores
34
+ redis_call :zrange, 0, -1, {withscores: true}
35
+ end
36
+
37
+ def valid_jobs
38
+ redis_call :zrangebyscore, 0, Time.now.to_i
39
+ end
40
+
41
+ def jobs_empty?
42
+ count = total_jobs_count
43
+ # ap count
44
+ count == 0
45
+ end
46
+
47
+ def total_jobs_count
48
+ redis_call(:zcount, "-inf", "+inf")
49
+ end
50
+
51
+ def redis
52
+ @@redis
53
+ end
54
+
55
+ private
56
+
57
+ # returns true if job has an :execute_at value
58
+ def job_valid? job
59
+ job.respond_to?(:execute_at) && !job.execute_at.nil?
60
+ end
61
+
62
+ # return timestamp of :execute_at or current time
63
+ def queue_time job
64
+ time = job_valid?(job) ? job.execute_at.to_i : Time.now.to_i
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,57 @@
1
+ module Afterparty
2
+ class RedisQueue
3
+ attr_accessor :redis, :options, :temp_namespace, :consumer
4
+ include Afterparty::QueueHelpers
5
+
6
+ def initialize options={}, consumer_options={}
7
+ @consumer = ThreadedQueueConsumer.new(self, consumer_options).start
8
+ @options = options
9
+ @options[:namespace] ||= "default"
10
+ @options[:sleep] ||= 5
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ def push job
15
+ @mutex.synchronize do
16
+ return nil if job.nil?
17
+ async_redis_call{ redis_call :zadd, queue_time(job), Marshal.dump(job) }
18
+ @consumer.start unless @consumer.thread.alive?
19
+ @temp_namespace = nil
20
+ end
21
+ end
22
+ alias :<< :push
23
+ alias :eng :push
24
+
25
+ def pop
26
+ @mutex.synchronize do
27
+ while true do
28
+ if jobs_empty?
29
+ @consumer.shutdown
30
+ elsif !(_jobs = valid_jobs).empty?
31
+ job_dump = _jobs[0]
32
+ async_redis_call do
33
+ redis_call :zrem, job_dump
34
+ @temp_namespace = "completed"
35
+ redis_call :zadd, Time.now.to_i, job_dump
36
+ end
37
+ return Marshal.load(job_dump)
38
+ end
39
+ sleep(@options[:sleep])
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class TestRedisQueue < RedisQueue
46
+ attr_accessor :completed_jobs
47
+
48
+ def initialize opts={}, consumer_opts={}
49
+ super
50
+ @completed_jobs = []
51
+ @exceptions = []
52
+ end
53
+ def handle_exception job, exception
54
+ @exceptions << [job, exception]
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ module Afterparty
2
+
3
+ # inspired by the rails 4 implementation:
4
+ # https://github.com/rails/rails/blob/jobs/activesupport/lib/active_support/queueing.rb
5
+
6
+ # The threaded consumer will run jobs in a background thread in
7
+ # development mode or in a VM where running jobs on a thread in
8
+ # production mode makes sense.
9
+ #
10
+ # When the process exits, the consumer pushes a nil onto the
11
+ # queue and joins the thread, which will ensure that all jobs
12
+ # are executed before the process finally dies.
13
+ class ThreadedQueueConsumer
14
+ attr_accessor :logger, :thread
15
+
16
+ def initialize(queue, options = {})
17
+ @queue = queue
18
+ @logger = options[:logger]
19
+ @fallback_logger = Logger.new($stderr)
20
+ end
21
+
22
+ def start
23
+ @thread = Thread.new { consume }
24
+ self
25
+ end
26
+
27
+ def shutdown
28
+ @queue.push nil
29
+ @thread.join
30
+ end
31
+
32
+ def drain
33
+ while job = @queue.pop(true)
34
+ job.run
35
+ end
36
+ rescue ThreadError
37
+ end
38
+
39
+ def consume
40
+ while job = @queue.pop
41
+ if @queue.respond_to? :completed_jobs
42
+ @queue.completed_jobs << job
43
+ end
44
+ run job
45
+ end
46
+ end
47
+
48
+ def run(job)
49
+ job.run
50
+ rescue Exception => exception
51
+ handle_exception job, exception
52
+ end
53
+
54
+ def handle_exception(job, exception)
55
+ (logger || @fallback_logger).error "Job Error: #{job.inspect}\n#{exception.message}\n#{exception.backtrace.join("\n")}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Afterparty
2
+ VERSION = "0.0.2"
3
+ end
data/lib/afterparty.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'logger'
2
+ require 'afterparty/queue_helpers'
3
+ require 'afterparty/redis_queue'
4
+ Dir[File.expand_path('../afterparty/*', __FILE__)].each { |f| require f }
5
+
6
+
7
+ module Afterparty
8
+ def self.redis
9
+ @@redis
10
+ end
11
+ def self.redis=(redis)
12
+ @@redis = redis
13
+ end
14
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,28 @@
1
+ module Afterparty
2
+ module QueueTestHelpers
3
+
4
+ def test_job later=false, &block
5
+ job = block ? TestJob.new(block) : TestJob.new
6
+ job.execute_at = Time.now + (later) if later
7
+ @block = block
8
+ job
9
+ end
10
+
11
+ def chill seconds
12
+ t = Time.now
13
+ while Time.now < (t + seconds); end
14
+ end
15
+ end
16
+ end
17
+
18
+ class TestJob
19
+ attr_accessor :execute_at
20
+
21
+ def initialize &block
22
+ @block = block
23
+ end
24
+
25
+ def run
26
+ @block.call if @block
27
+ end
28
+ end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+ describe Afterparty::RedisQueue do
3
+ before do
4
+ require 'open-uri'
5
+ uri = URI.parse("redis://localhost:6379")
6
+ redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
7
+ Afterparty.redis = redis
8
+ @q = Afterparty::TestRedisQueue.new({sleep: 0.5})
9
+ end
10
+
11
+ before :each do
12
+ @q.completed_jobs.clear
13
+ @q.clear
14
+ Afterparty.redis.quit
15
+ end
16
+
17
+ it "pushes nil without errors" do
18
+ @q.push(nil)
19
+ @q.jobs.should eq([])
20
+ end
21
+
22
+ it "adds items to the queue" do
23
+ @q.push(test_job)
24
+ @q.total_jobs_count.should eq(1)
25
+ end
26
+
27
+ it "executes the job" do
28
+ job = TestJob.new
29
+ @q.push(job)
30
+ complete.size.should eq(0)
31
+ chill(3)
32
+ complete.size.should eq(1)
33
+ end
34
+
35
+ it "removes items from the queue after running them" do
36
+ @q.push TestJob.new
37
+ chill(1)
38
+ @q.jobs.should_not include(@job)
39
+ end
40
+
41
+ it "doesn't execute jobs that execute in a while" do
42
+ job = TestJob.new
43
+ job.execute_at = Time.now + 200
44
+ @q.push job
45
+ chill(3)
46
+ complete.size.should eq(0)
47
+ end
48
+
49
+ it "waits the correct amount of time to execute a job" do
50
+ job = TestJob.new
51
+ job.execute_at = Time.now + 2
52
+ @q.push(job)
53
+ chill(8)
54
+ complete.size.should eq(1)
55
+ end
56
+
57
+ it "doesn't execute the job synchronously when added" do
58
+ job = test_job 100
59
+ t = Time.now
60
+ @q.push(job)
61
+ (Time.now - t).should <= 1
62
+ end
63
+
64
+ it "executes jobs in the right order" do
65
+ late_job = test_job 60*10
66
+ early_job = test_job
67
+ @q.push(late_job)
68
+ @q.push(early_job)
69
+ chill(3)
70
+ complete.size.should eq(1)
71
+ complete[0].execute_at.should be(nil)
72
+ end
73
+
74
+ class ErrorJob
75
+ attr_accessor :execute_at
76
+
77
+ def run
78
+ raise "hello"
79
+ end
80
+ end
81
+
82
+ def complete
83
+ @q.completed_jobs
84
+ end
85
+
86
+ def error_job later=nil
87
+ job = ErrorJob.new
88
+ job.execute_at = Time.now + later if later
89
+ job
90
+ end
91
+
92
+ def chill seconds
93
+ t = Time.now
94
+ while Time.now < (t + seconds); end
95
+ end
96
+
97
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'awesome_print'
4
+ require 'redis'
5
+ require 'afterparty' # and any other gems you need
6
+ require 'helpers'
7
+
8
+ RSpec.configure do |config|
9
+ # some (optional) config here
10
+ config.include Afterparty::QueueTestHelpers
11
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: afterparty
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Hank Stoever
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Rails 4 compatible queue with support for executing jobs later.
56
+ email:
57
+ - hstove@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - .rspec
64
+ - .travis.yml
65
+ - Gemfile
66
+ - Guardfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - afterparty.gemspec
71
+ - dump.rdb
72
+ - lib/afterparty.rb
73
+ - lib/afterparty/queue_helpers.rb
74
+ - lib/afterparty/redis_queue.rb
75
+ - lib/afterparty/threaded_queue_consumer.rb
76
+ - lib/afterparty/version.rb
77
+ - spec/helpers.rb
78
+ - spec/redis_queue_spec.rb
79
+ - spec/spec_helper.rb
80
+ homepage: ''
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 2.0.3
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Rails 4 compatible queue with support for executing jobs later.
104
+ test_files:
105
+ - spec/helpers.rb
106
+ - spec/redis_queue_spec.rb
107
+ - spec/spec_helper.rb
108
+ has_rdoc: