komrade-client 1.0.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.
@@ -0,0 +1,36 @@
1
+ require 'uri'
2
+
3
+ module Komrade
4
+ extend self
5
+ Error = Class.new(StandardError)
6
+
7
+ def env(key)
8
+ ENV[key]
9
+ end
10
+
11
+ def env!(key)
12
+ env(key) || raise(Error, "Komrade requires #{key} be set in the ENV.")
13
+ end
14
+
15
+ def url
16
+ URI.parse(env!("KOMRADE_URL"))
17
+ end
18
+
19
+ def log(data)
20
+ result = nil
21
+ if data.key?(:measure)
22
+ data[:measure].insert(0, "komrade.")
23
+ end
24
+ if block_given?
25
+ start = Time.now
26
+ result = yield
27
+ data.merge!(val: (Time.now - start))
28
+ end
29
+ data.reduce(out=String.new) do |s, tup|
30
+ s << [tup.first, tup.last].join("=") << " "
31
+ end
32
+ $stdout.puts(out)
33
+ return result
34
+ end
35
+
36
+ end
@@ -0,0 +1,74 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'komrade'
4
+ require 'komrade/rate_limiter'
5
+
6
+ module Komrade
7
+ module HttpHelpers
8
+ MAX_RETRY = 4
9
+
10
+ def put(path, body)
11
+ make_request(Net::HTTP::Put.new(path), body)
12
+ end
13
+
14
+ def get(path)
15
+ make_request(Net::HTTP::Get.new(path))
16
+ end
17
+
18
+ def delete(path)
19
+ make_request(Net::HTTP::Delete.new(path))
20
+ end
21
+
22
+ def make_request(req, body=nil)
23
+ req.basic_auth(Komrade.url.user, Komrade.url.password)
24
+ if body
25
+ begin
26
+ req.content_type = 'application/json'
27
+ req.body = JSON.dump(body)
28
+ rescue JSON => e
29
+ raise(ArgumentError,
30
+ "Komrade is unable to convert enqueue payload to JSON.\n" +
31
+ "payload=#{body}\n")
32
+ end
33
+ end
34
+ attempts = 0
35
+ while attempts < MAX_RETRY
36
+ begin
37
+ resp = nil
38
+ RateLimiter.limiter(req.path, 10, 1) do
39
+ resp = http.request(req)
40
+ end
41
+ if (Integer(resp.code) / 100) == 2
42
+ return JSON.parse(resp.body)
43
+ end
44
+ rescue Net::HTTPError => e
45
+ next
46
+ rescue RateLimiter::Unavailable
47
+ sleep(0.5)
48
+ ensure
49
+ attempts += 1
50
+ end
51
+ end
52
+ case req.class
53
+ when Net::HTTP::Delete
54
+ raise(Komrade::Error, "Unable to delete work from Komrade.")
55
+ when Net::HTTP::Put
56
+ raise(Komrade::Error, "Unable to send work to Komrade.")
57
+ when Net::HTTP::Get
58
+ raise(Komrade::Error, "Unable to get work from Komrade.")
59
+ default
60
+ raise(Komrade::Error, "Unable to communicate with Komrade.")
61
+ end
62
+ end
63
+
64
+ def http
65
+ @http ||= Net::HTTP.new(Komrade.url.host, Komrade.url.port).tap do |h|
66
+ if Komrade.url.scheme == 'https'
67
+ h.use_ssl = true
68
+ h.verify_mode = OpenSSL::SSL::VERIFY_NONE
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,25 @@
1
+ require 'securerandom'
2
+ require 'komrade'
3
+ require 'komrade/http_helpers'
4
+
5
+ module Komrade
6
+ module Queue
7
+ extend self
8
+ extend HttpHelpers
9
+
10
+ def enqueue(method, *args)
11
+ SecureRandom.uuid.tap do |id|
12
+ put("/jobs/#{id}", method: method, args: args)
13
+ end
14
+ end
15
+
16
+ def dequeue(opts={})
17
+ limit = opts[:limit] || 1
18
+ get("/jobs?limit=#{limit}")
19
+ end
20
+
21
+ def remove(id)
22
+ delete("/jobs/#{id}")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ module Komrade
2
+ module RateLimiter
3
+ extend self
4
+
5
+ Unavailable = Class.new(Komrade::Error)
6
+
7
+ def limits
8
+ @limits ||= {counts: Hash.new(0), timestamps: Hash.new(0)}
9
+ end
10
+
11
+ def breaks
12
+ @breaks ||= {counts: Hash.new(0), timestamps: Hash.new(0)}
13
+ end
14
+
15
+ def stamp(data, attr, bucket)
16
+ timestamp = Time.now.to_i / bucket
17
+ data[:counts][attr] = 0 unless data[:timestamps][attr] == timestamp
18
+ timestamp
19
+ end
20
+
21
+ def increment(data, attr, timestamp)
22
+ data[:timestamps][attr] = timestamp
23
+ data[:counts][attr] += 1
24
+ end
25
+
26
+ def check(data, attr, value, &blk)
27
+ if value == 0 || data[:counts][attr] < value
28
+ yield
29
+ else
30
+ raise(Unavailable, attr)
31
+ end
32
+ end
33
+
34
+ def limiter(attr, value, bucket, &blk)
35
+ timestamp = stamp(limits, attr, bucket)
36
+ increment(limits, attr, timestamp)
37
+ check(limits, attr, value) do
38
+ yield
39
+ end
40
+ end
41
+
42
+ def breaker(attr, value, bucket, &blk)
43
+ timestamp = stamp(breaks, attr, bucket)
44
+ check(breaks, attr, value) do
45
+ begin
46
+ yield
47
+ rescue => e
48
+ increment(breaks, attr, timestamp)
49
+ raise
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ require 'komrade/worker'
2
+
3
+ task :environment
4
+
5
+ namespace :komrade do
6
+ desc "Start a new worker."
7
+ task :work => :environment do
8
+ trap('INT') {exit}
9
+ trap('TERM') {@worker.stop}
10
+ @worker = Komrade::Worker.new
11
+ @worker.start
12
+ end
13
+
14
+ desc "Returns the number of jobs in the (default or QUEUE) queue"
15
+ task :count => :environment do
16
+ $stdout.puts(Komrade::Worker.new.queue.count)
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ require 'komrade/queue'
2
+
3
+ module Komrade
4
+ class Worker
5
+
6
+ def initialize(args={})
7
+ @running = true
8
+ end
9
+
10
+ # Start a loop and work jobs indefinitely.
11
+ # Call this method to start the worker.
12
+ # This is the easiest way to start working jobs.
13
+ def start
14
+ work while @running
15
+ end
16
+
17
+ # Call this method to stop the worker.
18
+ # The worker may not stop immediately if the worker
19
+ # is sleeping.
20
+ def stop
21
+ @running = false
22
+ end
23
+
24
+ # This method will lock a job & evaluate the code defined by the job.
25
+ # Also, this method will make the best attempt to delete the job
26
+ # from the queue before returning.
27
+ def work
28
+ jobs = Queue.dequeue
29
+ until jobs.empty?
30
+ job = jobs.pop
31
+ begin
32
+ call(job["payload"])
33
+ rescue => e
34
+ handle_failure(job, e)
35
+ ensure
36
+ Queue.remove(job["id"])
37
+ log(:at => "remove-job", :job => job["id"])
38
+ end
39
+ end
40
+ end
41
+
42
+ # Each job includes a method column. We will use ruby's eval
43
+ # to grab the ruby object from memory. We send the method to
44
+ # the object and pass the args.
45
+ def call(payload)
46
+ args = payload["args"]
47
+ klass = eval(payload["method"].split(".").first)
48
+ message = payload["method"].split(".").last
49
+ klass.send(message, *args)
50
+ end
51
+
52
+ # This method will be called when an exception
53
+ # is raised during the execution of the job.
54
+ def handle_failure(job,e)
55
+ log(:at => "handle_failure")
56
+ end
57
+
58
+ def log(data)
59
+ Komrade.log(data)
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,61 @@
1
+ # Komrade
2
+
3
+ A client library for the komrade worker queue.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ $ heroku addons:add komrade:basic
9
+ $ gem install komrade-client
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ 1. Install Gem
15
+ 2. Enqueue
16
+ 3. Dequeue
17
+
18
+ ### Install
19
+
20
+ Gemfile
21
+
22
+ ```ruby
23
+ source :rubygems
24
+ gem 'komrade-client', '1.0.0'
25
+ ```
26
+
27
+ ### Enqueue
28
+
29
+ Example Model
30
+
31
+ ```ruby
32
+ class User < ActiveRecord::Base
33
+ after_create :enqueue_welcome_email
34
+
35
+ def self.send_welcome_email(id)
36
+ if u = find(id)
37
+ Mailer.welcome(u).deliver
38
+ end
39
+ end
40
+
41
+ def enqueue_welcome_email
42
+ Komrade.enqueue("User.send_welcome_email", self.id)
43
+ end
44
+ end
45
+ ```
46
+
47
+ ### Dequeue
48
+
49
+ Rakefile
50
+
51
+ ```ruby
52
+ require 'komrade'
53
+ require 'komrade/tasks'
54
+ ```
55
+
56
+ Procfile
57
+
58
+ ```
59
+ web: bundle exec rails s
60
+ worker: bundle exec rake komrade:work
61
+ ```
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: komrade-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Smith (♠ ace hacker)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-26 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A client library for the komrad worker queue.
15
+ email: komrade@32k.io
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/komrade/http_helpers.rb
21
+ - lib/komrade/queue.rb
22
+ - lib/komrade/rate_limiter.rb
23
+ - lib/komrade/tasks.rb
24
+ - lib/komrade/worker.rb
25
+ - lib/komrade.rb
26
+ - readme.md
27
+ homepage: http://github.com/ryandotsmith/komrade-client
28
+ licenses:
29
+ - MIT
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 1.8.23
49
+ signing_key:
50
+ specification_version: 3
51
+ summary: Queues are tough. Let Komrade work for you.
52
+ test_files: []