activejob-traffic_control 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a536acd2cfc994594b0ae3c7e463b4aac834ba5
4
- data.tar.gz: 87ab13103b57fe115c1b287c18714662892a42e9
3
+ metadata.gz: 2906ae48cd5953f18837860c3685e3ebf8a2dd84
4
+ data.tar.gz: 5bb69b07b5439da6c1f56d5674fbfec37bd111d8
5
5
  SHA512:
6
- metadata.gz: fa226f177e0f5cb5408222de47f3652ccf88ee591719a39521db19fbc68ff21fca906e6a8c06e2eba7615f5be7b3ec04561121a539198d5e75a297d8cbd151da
7
- data.tar.gz: fe358db32250fbb19b129d7061d2abd3cd5129e86a149a16e9b6c234d05cca72a400d06692d691426bb3927b5384cfd9544db39d570fcf97a1727e22fe08410e
6
+ metadata.gz: 5887b615c100a481113967f5541c4e4d86621a4ce9a4ae604dcac4c524c53d0a097ec30ce9a5d88d6bd30d18fdcce4646d8731d660a190532fc496a31b19a39a
7
+ data.tar.gz: d5ccb7e7fa1c4782bcc1650a62d6f1ca17117aace77a130e2c6561714d97619faec95f86b5526516772f3ca429361ce4701e727c212585d8f896528b0de2170c
data/.gitignore CHANGED
@@ -1,9 +1,18 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rubocop.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  AllCops:
2
+ TargetRubyVersion: 2.3
2
3
  Exclude:
3
4
  - .git/**/*
4
5
  - tmp/**/*
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in activejob-traffic_control.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -23,18 +23,15 @@ Or install it yourself as:
23
23
  `ActiveJob::TrafficControl` adds three modules you can mixin to your job classes as needed, or to `ApplicationJob` if you are using ActiveJob 5+ (or you have created a base job class yourself).
24
24
 
25
25
  ```ruby
26
- ActiveJob::TrafficControl.client = ConnectionPool.new(size: 5, timeout: 5) { Redis.new } # set thresholds as needed
27
-
28
- class CanDisableJob < ActiveJob::Base
29
- include ActiveJob::TrafficControl::Disable
26
+ # to initialize the type of locking client (memcached vs. redis):
27
+ ActiveJob::TrafficControl.client = ConnectionPool.new(size: 5, timeout: 5) { Redis.new } # set poolthresholds as needed
28
+ # or, ActiveJob::TrafficControl.client = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new }
29
+ # or if not multithreaded, ActiveJob::TrafficControl.client = Redis.new
30
+ ```
30
31
 
31
- def perform
32
- # you can pause this job from running by executing `CanDisableJob.disable!` (which will cause the job to be re-enqueued),
33
- # or have it be dropped entirely via `CanDisableJob.disable!(drop: true)`
34
- # enable it again via `CanDisableJob.enable!`
35
- end
36
- end
32
+ ### `Throttle`
37
33
 
34
+ ```ruby
38
35
  class CanThrottleJob < ActiveJob::Base
39
36
  include ActiveJob::TrafficControl::Throttle
40
37
 
@@ -45,7 +42,11 @@ class CanThrottleJob < ActiveJob::Base
45
42
  # if more than that attempt to run, they will be dropped (you can set `drop: false` to have the re-enqueued instead)
46
43
  end
47
44
  end
45
+ ```
48
46
 
47
+ ### `Concurrency`
48
+
49
+ ```ruby
49
50
  class ConcurrencyTestJob < ActiveJob::Base
50
51
  include ActiveJob::TrafficControl::Concurrency
51
52
 
@@ -54,6 +55,48 @@ class ConcurrencyTestJob < ActiveJob::Base
54
55
  def perform
55
56
  # only five `ConcurrencyTestJob` will ever run simultaneously
56
57
  end
58
+ end
59
+ ```
60
+
61
+ ### `Disable`
62
+
63
+ For `Disable`, you also need to configure the cache client:
64
+
65
+ ```ruby
66
+ ActiveJob::TrafficControl.cache_client = Rails.cache.dalli # if using :dalli_store
67
+ # or ActiveJob::TrafficControl.cache_client = ActiveSupport::Cache.lookup_store(:dalli_store, "localhost:11211")
68
+ ```
69
+
70
+ ```ruby
71
+ class CanDisableJob < ActiveJob::Base
72
+ include ActiveJob::TrafficControl::Disable
73
+
74
+ def perform
75
+ # you can pause this job from running by executing `CanDisableJob.disable!` (which will cause the job to be re-enqueued),
76
+ # or have it be dropped entirely via `CanDisableJob.disable!(drop: true)`
77
+ # enable it again via `CanDisableJob.enable!`
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### `ApplicationJob`
83
+
84
+ To provide all of the above functionality to your jobs
85
+
86
+ ```ruby
87
+ class ApplicationJob < ActiveJob::Base
88
+ include ActiveJob::TrafficControl::Throttle
89
+ include ActiveJob::TrafficControl::Concurrency
90
+ include ActiveJob::TrafficControl::Disable
91
+
92
+ concurrency 2, drop: false
93
+ throttle threshold: 10, period: 1.minute, drop: false
94
+
95
+ def perform
96
+ # will have all of the behaviors
97
+ end
98
+ end
99
+ ```
57
100
 
58
101
  ## Development
59
102
 
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rake/testtask"
3
5
 
4
6
  Rake::TestTask.new(:test) do |t|
5
7
  t.libs << "test"
6
8
  t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
9
+ t.test_files = FileList["test/**/*_test.rb"]
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # coding: utf-8
2
3
  lib = File.expand_path("../lib", __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -9,8 +10,8 @@ Gem::Specification.new do |spec|
9
10
  spec.authors = ["Nick Elser"]
10
11
  spec.email = ["nick.elser@gmail.com"]
11
12
 
12
- spec.summary = %q{Traffic control for ActiveJob}
13
- spec.description = %q{Traffic control for ActiveJob: Concurrency/enabling/throttling}
13
+ spec.summary = %q(Traffic control for ActiveJob)
14
+ spec.description = %q(Traffic control for ActiveJob: Concurrency/enabling/throttling)
14
15
  spec.homepage = "https://www.activejobtrafficcontrol.com"
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_job"
2
4
  require "active_support/all"
3
5
  require "suo"
@@ -34,7 +36,7 @@ module ActiveJob
34
36
  def client
35
37
  @client ||= begin
36
38
  logger.error "defaulting to Redis as the lock client; please set "\
37
- " `ActiveJob::TrafficControl.client` to a Redis or Memcached client,"
39
+ " `ActiveJob::TrafficControl.client` to a Redis or Memcached client."
38
40
  @client_klass = Suo::Client::Redis
39
41
  Redis.new(url: ENV["REDIS_URL"])
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "forwardable"
2
4
 
3
5
  module ActiveJob
@@ -16,6 +18,22 @@ module ActiveJob
16
18
  def cache_client
17
19
  ActiveJob::TrafficControl.cache_client
18
20
  end
21
+
22
+ def lock_key(prefix, job, config_attr)
23
+ if config_attr
24
+ if config_attr[:key].respond_to?(:call)
25
+ "traffic_control:#{prefix}:#{config_attr[:key].call(job)}"
26
+ else
27
+ @static_job_key ||= begin
28
+ if config_attr[:key].present?
29
+ "traffic_control:#{prefix}:#{config_attr[:key]}"
30
+ else
31
+ "traffic_control:#{prefix}:#{cleaned_name}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
19
37
  end
20
38
 
21
39
  # convenience methods
@@ -26,12 +44,12 @@ module ActiveJob
26
44
  def reenqueue(range, reason)
27
45
  later_delay = rand(range).seconds
28
46
  retry_job(wait: later_delay)
29
- logger.error "Re-enqueing #{self.class.name} to run in #{later_delay}s due to #{reason}"
47
+ logger.info "Re-enqueing #{self.class.name} to run in #{later_delay}s due to #{reason}"
30
48
  ActiveSupport::Notifications.instrument "re_enqueue.active_job", job: self, reason: reason
31
49
  end
32
50
 
33
51
  def drop(reason)
34
- logger.error "Dropping #{self.class.name} due to #{reason}"
52
+ logger.info "Dropping #{self.class.name} due to #{reason}"
35
53
  ActiveSupport::Notifications.instrument "dropped.active_job", job: self, reason: reason
36
54
  end
37
55
 
@@ -1,27 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveJob
2
4
  module TrafficControl
3
5
  module Concurrency
4
6
  extend ::ActiveSupport::Concern
5
7
 
8
+ CONCURRENCY_REENQUEUE_DELAY = ENV["RACK_ENV"] == "test" ? 1...5 : 30...(60 * 5)
9
+
6
10
  class_methods do
7
11
  attr_accessor :job_concurrency
8
12
 
9
13
  def concurrency(threshold, drop: true, key: nil, wait_timeout: 0.1, stale_timeout: 60 * 10)
10
14
  raise ArgumentError, "Concurrent jobs needs to be an integer > 0" if threshold.to_i < 1
11
- @job_concurrency = {threshold: threshold.to_i, drop: drop, wait_timeout: wait_timeout.to_f, stale_timeout: stale_timeout.to_f, key: key}
15
+
16
+ @job_concurrency = {
17
+ threshold: threshold.to_i,
18
+ drop: drop,
19
+ wait_timeout: wait_timeout.to_f,
20
+ stale_timeout: stale_timeout.to_f,
21
+ key: key
22
+ }
12
23
  end
13
24
 
14
- def concurrency_key
15
- if job_concurrency
16
- @concurrency_key ||= job_concurrency[:key].present? ? job_concurrency[:key] : "traffic_control:concurrency:#{cleaned_name}".freeze
17
- end
25
+ def concurrency_lock_key(job)
26
+ lock_key("concurrency", job, job_concurrency)
18
27
  end
19
28
  end
20
29
 
21
30
  included do
22
31
  include ActiveJob::TrafficControl::Base
23
32
 
24
- around_perform do |_, block|
33
+ around_perform do |job, block|
25
34
  if self.class.job_concurrency.present?
26
35
  lock_options = {
27
36
  resources: self.class.job_concurrency[:threshold],
@@ -29,7 +38,7 @@ module ActiveJob
29
38
  stale_lock_expiration: self.class.job_concurrency[:stale_timeout]
30
39
  }
31
40
 
32
- with_lock_client(self.class.concurrency_key, lock_options) do |client|
41
+ with_lock_client(self.class.concurrency_lock_key(job), lock_options) do |client|
33
42
  locked = client.lock do
34
43
  block.call
35
44
  true
@@ -37,9 +46,9 @@ module ActiveJob
37
46
 
38
47
  unless locked
39
48
  if self.class.job_concurrency[:drop]
40
- drop("concurrency".freeze)
49
+ drop("concurrency")
41
50
  else
42
- reenqueue(CONCURRENCY_REENQUEUE_DELAY, "concurrency".freeze)
51
+ reenqueue(CONCURRENCY_REENQUEUE_DELAY, "concurrency")
43
52
  end
44
53
  end
45
54
  end
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveJob
2
4
  module TrafficControl
3
5
  module Disable
4
6
  extend ::ActiveSupport::Concern
5
7
 
6
8
  DISABLED_REENQUEUE_DELAY = 60...60 * 10
7
- SHOULD_DROP = "drop".freeze
8
- SHOULD_DISABLE = "true".freeze
9
+ SHOULD_DROP = "drop"
10
+ SHOULD_DISABLE = "true"
9
11
 
10
12
  private_constant :SHOULD_DROP, :SHOULD_DISABLE, :DISABLED_REENQUEUE_DELAY
11
13
 
@@ -23,7 +25,7 @@ module ActiveJob
23
25
  end
24
26
 
25
27
  def disable_key
26
- @disable_key ||= "traffic_control:disable:#{cleaned_name}".freeze
28
+ @disable_key ||= "traffic_control:disable:#{cleaned_name}"
27
29
  end
28
30
  end
29
31
 
@@ -35,9 +37,9 @@ module ActiveJob
35
37
  disabled = cache_client.read(self.class.disable_key)
36
38
 
37
39
  if disabled == SHOULD_DROP
38
- drop("disabled".freeze)
40
+ drop("disabled")
39
41
  elsif disabled == SHOULD_DISABLE
40
- reenqueue(DISABLED_REENQUEUE_DELAY, "disabled".freeze)
42
+ reenqueue(DISABLED_REENQUEUE_DELAY, "disabled")
41
43
  else
42
44
  block.call
43
45
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveJob
2
4
  module TrafficControl
3
5
  module Throttle
@@ -8,36 +10,40 @@ module ActiveJob
8
10
 
9
11
  def throttle(threshold:, period:, drop: false, key: nil)
10
12
  raise ArgumentError, "Threshold needs to be an integer > 0" if threshold.to_i < 1
11
- @job_throttling = {threshold: threshold, period: period, drop: drop, key: key}
13
+
14
+ @job_throttling = {
15
+ threshold: threshold,
16
+ period: period,
17
+ drop: drop,
18
+ key: key
19
+ }
12
20
  end
13
21
 
14
- def throttling_key
15
- if job_throttling
16
- @throttling_key ||= job_throttling[:key].present? ? job_throttling[:key] : "traffic_control:throttling:#{cleaned_name}".freeze
17
- end
22
+ def throttling_lock_key(job)
23
+ lock_key("throttle", job, job_throttling)
18
24
  end
19
25
  end
20
26
 
21
27
  included do
22
28
  include ActiveJob::TrafficControl::Base
23
29
 
24
- around_perform do |_, block|
30
+ around_perform do |job, block|
25
31
  if self.class.job_throttling.present?
26
32
  lock_options = {
27
33
  resources: self.class.job_throttling[:threshold],
28
34
  stale_lock_expiration: self.class.job_throttling[:period]
29
35
  }
30
36
 
31
- with_lock_client(self.class.throttling_key, lock_options) do |client|
37
+ with_lock_client(self.class.throttling_lock_key(job), lock_options) do |client|
32
38
  token = client.lock
33
39
 
34
40
  if token
35
41
  block.call
36
42
  elsif self.class.job_throttling[:drop]
37
- drop("throttling".freeze)
43
+ drop("throttling")
38
44
  else
39
45
  period = self.class.job_throttling[:period]
40
- reenqueue(period...period*5, "throttling".freeze)
46
+ reenqueue(period...(period * 5), "throttling")
41
47
  end
42
48
  end
43
49
  else
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveJob
2
4
  module TrafficControl
3
- VERSION = "0.1.0".freeze
5
+ VERSION = "0.1.1"
4
6
  end
5
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activejob-traffic_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Elser
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-06-30 00:00:00.000000000 Z
11
+ date: 2016-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob