komrade-client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []