restrainer 1.1.1 → 1.1.3
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 +4 -4
- data/CHANGELOG.md +44 -0
- data/MIT_LICENSE.txt +1 -1
- data/README.md +33 -0
- data/VERSION +1 -1
- data/lib/restrainer.rb +69 -39
- data/restrainer.gemspec +28 -21
- metadata +11 -61
- data/.gitignore +0 -17
- data/CHANGE_LOG.md +0 -13
- data/Rakefile +0 -6
- data/spec/restrainer_spec.rb +0 -138
- data/spec/spec_helper.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd2f59427326450c5159980282e4d463d275dac6fceaa2a31423a5433ff1502f
|
4
|
+
data.tar.gz: ddf65e8c8915e920a9689f78bce879d6feae920740890acc7c3df5f5c8ccb2d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4166271f5f617a48286024f8e57ab7ebc662c0edfd357ced9e4e40d3998b975db1abcb4db6a1c8de2922ffc57ace7593e752798f39a6f552659877a146fff1b5
|
7
|
+
data.tar.gz: 032357eb1992399ba5d10681b7194e667d852ca000b51dcc6ac592f8d10b4734306225208c918d9c3f73faec11adf690a6d0a1ffccefab57deef09438854437a
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 1.1.3
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Support for using fractional seconds in the lock timeout.
|
12
|
+
|
13
|
+
## 1.1.2
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- Redis instance will now default to a Redis instance with the default options
|
18
|
+
instead of throwing an error if it was not set.
|
19
|
+
- Minumum Ruby version set to 2.5
|
20
|
+
|
21
|
+
## 1.1.1
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
- Circular reference warning
|
26
|
+
|
27
|
+
## 1.1.0
|
28
|
+
|
29
|
+
### Added
|
30
|
+
|
31
|
+
- Expose manually locking and unlocking processes.
|
32
|
+
- Allow passing in a redis connection in the constructor.
|
33
|
+
|
34
|
+
## 1.0.1
|
35
|
+
|
36
|
+
### Fixed
|
37
|
+
|
38
|
+
- Use Lua script to avoid race conditions and ensure no extra processes slip through.
|
39
|
+
|
40
|
+
## 1.0.0
|
41
|
+
|
42
|
+
### Added
|
43
|
+
|
44
|
+
- Initial release.
|
data/MIT_LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://github.com/bdurand/restrainer/actions/workflows/continuous_integration.yml)
|
2
|
+
[](https://github.com/bdurand/restrainer/actions/workflows/regression_test.yml)
|
3
|
+
[](https://github.com/testdouble/standard)
|
4
|
+
[](https://badge.fury.io/rb/restrainer)
|
5
|
+
|
1
6
|
This gem provides a method of throttling calls across processes that can be very useful if you have to call an external service with limited resources.
|
2
7
|
|
3
8
|
A [redis server](http://redis.io/) is required to use this gem.
|
@@ -59,3 +64,31 @@ restrainer = Restrainer.new(:my_service, 100, timeout: 10)
|
|
59
64
|
```
|
60
65
|
|
61
66
|
This gem does clean up after itself nicely, so that it won't ever leave unused data lying around in redis.
|
67
|
+
|
68
|
+
## Installation
|
69
|
+
|
70
|
+
Add this line to your application's Gemfile:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
gem 'restrainer'
|
74
|
+
```
|
75
|
+
|
76
|
+
And then execute:
|
77
|
+
```bash
|
78
|
+
$ bundle
|
79
|
+
```
|
80
|
+
|
81
|
+
Or install it yourself as:
|
82
|
+
```bash
|
83
|
+
$ gem install restrainer
|
84
|
+
```
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
Open a pull request on GitHub.
|
89
|
+
|
90
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
91
|
+
|
92
|
+
## License
|
93
|
+
|
94
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.3
|
data/lib/restrainer.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "redis"
|
4
|
+
require "securerandom"
|
5
5
|
|
6
6
|
# Redis backed throttling mechanism to ensure that only a limited number of processes can
|
7
7
|
# be executed at any one time.
|
@@ -14,7 +14,6 @@ require 'securerandom'
|
|
14
14
|
# If more than the specified number of processes as identified by the name argument is currently
|
15
15
|
# running, then the throttle block will raise an error.
|
16
16
|
class Restrainer
|
17
|
-
|
18
17
|
attr_reader :name, :limit
|
19
18
|
|
20
19
|
ADD_PROCESS_SCRIPT = <<-LUA
|
@@ -39,7 +38,7 @@ class Restrainer
|
|
39
38
|
-- Success so add to the list and set a global expiration so the list cleans up after itself.
|
40
39
|
if process_count < limit then
|
41
40
|
redis.call('zadd', sorted_set, now, process_id)
|
42
|
-
redis.call('
|
41
|
+
redis.call('pexpire', sorted_set, math.ceil(ttl * 1000))
|
43
42
|
end
|
44
43
|
|
45
44
|
-- Return the number of processes running before the process was added.
|
@@ -53,54 +52,69 @@ class Restrainer
|
|
53
52
|
class ThrottledError < StandardError
|
54
53
|
end
|
55
54
|
|
55
|
+
@redis = nil
|
56
|
+
|
56
57
|
class << self
|
57
58
|
# Either configure the redis instance using a block or yield the instance. Configuring with
|
58
59
|
# a block allows you to use things like connection pools etc. without hard coding a single
|
59
60
|
# instance.
|
60
61
|
#
|
61
|
-
#
|
62
|
+
# @param block [Proc] A block that returns a redis instance.
|
63
|
+
# @return [Redis] The redis instance.
|
64
|
+
# @example
|
65
|
+
# Restrainer.redis { redis_pool.instance }
|
62
66
|
def redis(&block)
|
63
67
|
if block
|
64
68
|
@redis = block
|
65
|
-
elsif defined?(@redis) && @redis
|
66
|
-
@redis.call
|
67
69
|
else
|
68
|
-
|
70
|
+
unless @redis
|
71
|
+
client = Redis.new
|
72
|
+
@redis = lambda { client }
|
73
|
+
end
|
74
|
+
@redis.call
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
72
|
-
# Set the redis instance to a specific instance. It is usually preferable to use
|
73
|
-
# form for configurating the instance so that it can be evaluated at
|
78
|
+
# Set the redis instance to a specific instance. It is usually preferable to use
|
79
|
+
# the block form for configurating the instance so that it can be evaluated at
|
80
|
+
# runtime.
|
74
81
|
#
|
75
|
-
#
|
82
|
+
# @param conn [Redis]
|
83
|
+
# @return [void]
|
84
|
+
# @example
|
85
|
+
# Restrainer.redis = Redis.new
|
76
86
|
def redis=(conn)
|
77
|
-
@redis = lambda{ conn }
|
87
|
+
@redis = lambda { conn }
|
78
88
|
end
|
79
89
|
end
|
80
90
|
|
81
|
-
# Create a new restrainer. The name is used to identify the Restrainer and group
|
82
|
-
# You can create any number of Restrainers with different names.
|
91
|
+
# Create a new restrainer. The name is used to identify the Restrainer and group
|
92
|
+
# processes together. You can create any number of Restrainers with different names.
|
83
93
|
#
|
84
|
-
# The required limit parameter specifies the maximum number of processes that
|
85
|
-
# throttle block at any point in time.
|
94
|
+
# The required limit parameter specifies the maximum number of processes that
|
95
|
+
# will be allowed to execute the throttle block at any point in time.
|
86
96
|
#
|
87
|
-
# The timeout parameter is used for cleaning up internal data structures so that
|
88
|
-
# if their process is killed. Processes will automatically
|
89
|
-
#
|
97
|
+
# The timeout parameter is used for cleaning up internal data structures so that
|
98
|
+
# jobs aren't orphaned if their process is killed. Processes will automatically
|
99
|
+
# be removed from the running jobs list after the specified number of seconds.
|
100
|
+
# Note that the Restrainer will not handle timing out any code itself. This
|
90
101
|
# value is just used to insure the integrity of internal data structures.
|
91
102
|
def initialize(name, limit:, timeout: 60, redis: nil)
|
92
103
|
@name = name
|
93
104
|
@limit = limit
|
94
105
|
@timeout = timeout
|
95
|
-
@key = "#{self.class.name}.#{name
|
96
|
-
@redis
|
106
|
+
@key = "#{self.class.name}.#{name}"
|
107
|
+
@redis = redis
|
97
108
|
end
|
98
109
|
|
99
|
-
# Wrap a block with this method to throttle concurrent execution. If more than the
|
100
|
-
# of processes (as identified by the name) are currently executing,
|
101
|
-
# will be raised.
|
110
|
+
# Wrap a block with this method to throttle concurrent execution. If more than the
|
111
|
+
# alotted number of processes (as identified by the name) are currently executing,
|
112
|
+
# then a Restrainer::ThrottledError will be raised.
|
102
113
|
#
|
103
|
-
#
|
114
|
+
# @param limit [Integer] The maximum number of processes that can be executing
|
115
|
+
# at any one time. Defaults to the value passed to the constructor.
|
116
|
+
# @return [void]
|
117
|
+
# @raise [Restrainer::ThrottledError] If the throttle would be exceeded.
|
104
118
|
def throttle(limit: nil)
|
105
119
|
limit ||= self.limit
|
106
120
|
|
@@ -119,9 +133,12 @@ class Restrainer
|
|
119
133
|
# identifier that must be passed to the release! to release the lock.
|
120
134
|
# You can pass in a unique identifier if you already have one.
|
121
135
|
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
136
|
+
# @param process_id [String] A unique identifier for the process. If none is
|
137
|
+
# passed, a unique value will be generated.
|
138
|
+
# @param limit [Integer] The maximum number of processes that can be executing
|
139
|
+
# at any one time. Defaults to the value passed to the constructor.
|
140
|
+
# @return [String] The process identifier.
|
141
|
+
# @raise [Restrainer::ThrottledError] If the throttle would be exceeded.
|
125
142
|
def lock!(process_id = nil, limit: nil)
|
126
143
|
process_id ||= SecureRandom.uuid
|
127
144
|
limit ||= self.limit
|
@@ -134,17 +151,30 @@ class Restrainer
|
|
134
151
|
process_id
|
135
152
|
end
|
136
153
|
|
137
|
-
|
154
|
+
def lock(process_id = nil, limit: nil)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Release one of the allowed processes. You must pass in a process id
|
158
|
+
# returned by the lock method.
|
159
|
+
#
|
160
|
+
# @param process_id [String] The process identifier returned by the lock call.
|
161
|
+
# @return [Boolean] True if the process was released, false if it was not found.
|
138
162
|
def release!(process_id)
|
139
|
-
|
163
|
+
return false if process_id.nil?
|
164
|
+
|
165
|
+
remove_process!(redis, process_id)
|
140
166
|
end
|
141
167
|
|
142
168
|
# Get the number of processes currently being executed for this restrainer.
|
169
|
+
#
|
170
|
+
# @return [Integer] The number of processes currently being executed.
|
143
171
|
def current
|
144
172
|
redis.zcard(key).to_i
|
145
173
|
end
|
146
174
|
|
147
|
-
# Clear all locks
|
175
|
+
# Clear all locks.
|
176
|
+
#
|
177
|
+
# @return [void]
|
148
178
|
def clear!
|
149
179
|
redis.del(key)
|
150
180
|
end
|
@@ -156,9 +186,7 @@ class Restrainer
|
|
156
186
|
end
|
157
187
|
|
158
188
|
# Hash key in redis to story a sorted set of current processes.
|
159
|
-
|
160
|
-
@key
|
161
|
-
end
|
189
|
+
attr_reader :key
|
162
190
|
|
163
191
|
# Add a process to the currently run set.
|
164
192
|
def add_process!(redis, process_id, throttle_limit)
|
@@ -168,23 +196,25 @@ class Restrainer
|
|
168
196
|
end
|
169
197
|
end
|
170
198
|
|
171
|
-
# Remove a process to the currently run set.
|
199
|
+
# Remove a process to the currently run set. Returns true if the process was removed.
|
172
200
|
def remove_process!(redis, process_id)
|
173
|
-
redis.zrem(key, process_id)
|
201
|
+
result = redis.zrem(key, process_id)
|
202
|
+
result = true if result == 1
|
203
|
+
result
|
174
204
|
end
|
175
205
|
|
176
206
|
# Evaluate and execute a Lua script on the redis server.
|
177
207
|
def eval_script(redis, process_id, throttle_limit)
|
178
208
|
sha1 = @add_process_sha1
|
179
|
-
if sha1
|
209
|
+
if sha1.nil?
|
180
210
|
sha1 = redis.script(:load, ADD_PROCESS_SCRIPT)
|
181
211
|
@add_process_sha1 = sha1
|
182
212
|
end
|
183
213
|
|
184
214
|
begin
|
185
|
-
redis.evalsha(sha1, [], [key, process_id, throttle_limit, @timeout, Time.now.
|
215
|
+
redis.evalsha(sha1, [], [key, process_id, throttle_limit, @timeout, Time.now.to_f])
|
186
216
|
rescue Redis::CommandError => e
|
187
|
-
if e.message.include?(
|
217
|
+
if e.message.include?("NOSCRIPT")
|
188
218
|
sha1 = redis.script(:load, ADD_PROCESS_SCRIPT)
|
189
219
|
@add_process_sha1 = sha1
|
190
220
|
retry
|
data/restrainer.gemspec
CHANGED
@@ -1,27 +1,34 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
5
1
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
|
11
|
-
spec.
|
12
|
-
spec.homepage
|
13
|
-
spec.license
|
14
|
-
|
15
|
-
|
16
|
-
|
2
|
+
spec.name = "restrainer"
|
3
|
+
spec.version = File.read(File.expand_path("VERSION", __dir__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "Code for throttling workloads so as not to overwhelm external services"
|
8
|
+
spec.homepage = "https://github.com/bdurand/restrainer"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Appraisals
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
Rakefile
|
19
|
+
bin/
|
20
|
+
gemfiles/
|
21
|
+
spec/
|
22
|
+
]
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
25
|
+
end
|
26
|
+
|
17
27
|
spec.require_paths = ["lib"]
|
18
28
|
|
19
|
-
spec.required_ruby_version =
|
29
|
+
spec.required_ruby_version = ">= 2.5"
|
20
30
|
|
21
|
-
spec.add_dependency(
|
31
|
+
spec.add_dependency("redis")
|
22
32
|
|
23
|
-
spec.add_development_dependency "bundler"
|
24
|
-
spec.add_development_dependency "rake"
|
25
|
-
spec.add_development_dependency "rspec"
|
26
|
-
spec.add_development_dependency "timecop"
|
33
|
+
spec.add_development_dependency "bundler"
|
27
34
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restrainer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- We Heart It
|
8
7
|
- Brian Durand
|
9
|
-
autorequire:
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2023-12-22 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: redis
|
@@ -27,48 +26,6 @@ dependencies:
|
|
27
26
|
version: '0'
|
28
27
|
- !ruby/object:Gem::Dependency
|
29
28
|
name: bundler
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - "~>"
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '1.3'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - "~>"
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '1.3'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rake
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: rspec
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: timecop
|
72
29
|
requirement: !ruby/object:Gem::Requirement
|
73
30
|
requirements:
|
74
31
|
- - ">="
|
@@ -81,29 +38,24 @@ dependencies:
|
|
81
38
|
- - ">="
|
82
39
|
- !ruby/object:Gem::Version
|
83
40
|
version: '0'
|
84
|
-
description:
|
41
|
+
description:
|
85
42
|
email:
|
86
|
-
- dev@weheartit.com
|
87
43
|
- bbdurand@gmail.com
|
88
44
|
executables: []
|
89
45
|
extensions: []
|
90
46
|
extra_rdoc_files: []
|
91
47
|
files:
|
92
|
-
-
|
93
|
-
- CHANGE_LOG.md
|
48
|
+
- CHANGELOG.md
|
94
49
|
- MIT_LICENSE.txt
|
95
50
|
- README.md
|
96
|
-
- Rakefile
|
97
51
|
- VERSION
|
98
52
|
- lib/restrainer.rb
|
99
53
|
- restrainer.gemspec
|
100
|
-
|
101
|
-
- spec/spec_helper.rb
|
102
|
-
homepage: https://github.com/weheartit/restrainer
|
54
|
+
homepage: https://github.com/bdurand/restrainer
|
103
55
|
licenses:
|
104
56
|
- MIT
|
105
57
|
metadata: {}
|
106
|
-
post_install_message:
|
58
|
+
post_install_message:
|
107
59
|
rdoc_options: []
|
108
60
|
require_paths:
|
109
61
|
- lib
|
@@ -111,17 +63,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
63
|
requirements:
|
112
64
|
- - ">="
|
113
65
|
- !ruby/object:Gem::Version
|
114
|
-
version: '2.
|
66
|
+
version: '2.5'
|
115
67
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
68
|
requirements:
|
117
69
|
- - ">="
|
118
70
|
- !ruby/object:Gem::Version
|
119
71
|
version: '0'
|
120
72
|
requirements: []
|
121
|
-
rubygems_version: 3.
|
122
|
-
signing_key:
|
73
|
+
rubygems_version: 3.4.20
|
74
|
+
signing_key:
|
123
75
|
specification_version: 4
|
124
76
|
summary: Code for throttling workloads so as not to overwhelm external services
|
125
|
-
test_files:
|
126
|
-
- spec/restrainer_spec.rb
|
127
|
-
- spec/spec_helper.rb
|
77
|
+
test_files: []
|
data/.gitignore
DELETED
data/CHANGE_LOG.md
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# 1.1.1
|
2
|
-
|
3
|
-
* Circular reference warning fix
|
4
|
-
|
5
|
-
# 1.1.0
|
6
|
-
|
7
|
-
* Expose manually locking and unlocking processes.
|
8
|
-
|
9
|
-
* Allow passing in a redis connection in the constructor.
|
10
|
-
|
11
|
-
# 1.0.1
|
12
|
-
|
13
|
-
* Use Lua script to avoid race conditions and ensure no extra processes slip through.
|
data/Rakefile
DELETED
data/spec/restrainer_spec.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
|
5
|
-
describe Restrainer do
|
6
|
-
|
7
|
-
before(:each) do
|
8
|
-
Restrainer.new(:restrainer_test, limit: 1).clear!
|
9
|
-
end
|
10
|
-
|
11
|
-
it "should have a name and max_processes" do
|
12
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1)
|
13
|
-
expect(restrainer.name).to eq(:restrainer_test)
|
14
|
-
expect(restrainer.limit).to eq(1)
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should run a block!" do
|
18
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1)
|
19
|
-
x = nil
|
20
|
-
expect(restrainer.throttle{ x = restrainer.current }).to eq(1)
|
21
|
-
expect(x).to eq(1)
|
22
|
-
expect(restrainer.current).to eq(0)
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should throw an error if too many processes are already running" do
|
26
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 5)
|
27
|
-
x = nil
|
28
|
-
restrainer.throttle do
|
29
|
-
restrainer.throttle do
|
30
|
-
restrainer.throttle do
|
31
|
-
restrainer.throttle do
|
32
|
-
restrainer.throttle do
|
33
|
-
expect(lambda{restrainer.throttle{ x = 1 }}).to raise_error(Restrainer::ThrottledError)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
expect(x).to eq(nil)
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should not throw an error if the number of processes is under the limit" do
|
43
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 2)
|
44
|
-
x = nil
|
45
|
-
restrainer.throttle do
|
46
|
-
restrainer.throttle{ x = 1 }
|
47
|
-
end
|
48
|
-
expect(x).to eq(1)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should let the throttle method override the limit" do
|
52
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1)
|
53
|
-
x = nil
|
54
|
-
restrainer.throttle do
|
55
|
-
restrainer.throttle(limit: 2){ x = 1 }
|
56
|
-
end
|
57
|
-
expect(x).to eq(1)
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should allow processing to be turned off entirely by setting the limit to zero" do
|
61
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1)
|
62
|
-
x = nil
|
63
|
-
expect(lambda{restrainer.throttle(limit: 0){ x = 1 }}).to raise_error(Restrainer::ThrottledError)
|
64
|
-
expect(x).to eq(nil)
|
65
|
-
end
|
66
|
-
|
67
|
-
it "should allow the throttle to be opened up entirely with a negative limit" do
|
68
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 0)
|
69
|
-
x = nil
|
70
|
-
restrainer.throttle(limit: -1){ x = 1 }
|
71
|
-
expect(x).to eq(1)
|
72
|
-
end
|
73
|
-
|
74
|
-
it "should cleanup the running process list if orphaned processes exist" do
|
75
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1, timeout: 10)
|
76
|
-
x = nil
|
77
|
-
restrainer.throttle do
|
78
|
-
Timecop.travel(11) do
|
79
|
-
restrainer.throttle{ x = 1 }
|
80
|
-
end
|
81
|
-
end
|
82
|
-
expect(x).to eq(1)
|
83
|
-
end
|
84
|
-
|
85
|
-
it "should be able to lock! and release! processes manually" do
|
86
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 5)
|
87
|
-
p1 = restrainer.lock!
|
88
|
-
begin
|
89
|
-
p2 = restrainer.lock!
|
90
|
-
begin
|
91
|
-
p3 = restrainer.lock!
|
92
|
-
begin
|
93
|
-
p4 = restrainer.lock!
|
94
|
-
begin
|
95
|
-
p5 = restrainer.lock!
|
96
|
-
begin
|
97
|
-
expect{ restrainer.lock! }.to raise_error(Restrainer::ThrottledError)
|
98
|
-
ensure
|
99
|
-
restrainer.release!(p5)
|
100
|
-
end
|
101
|
-
p6 = restrainer.lock!
|
102
|
-
restrainer.release!(p6)
|
103
|
-
ensure
|
104
|
-
restrainer.release!(p4)
|
105
|
-
end
|
106
|
-
ensure
|
107
|
-
restrainer.release!(p3)
|
108
|
-
end
|
109
|
-
ensure
|
110
|
-
restrainer.release!(p2)
|
111
|
-
end
|
112
|
-
ensure
|
113
|
-
restrainer.release!(p1)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should be able to pass in the process id" do
|
118
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 1)
|
119
|
-
expect(restrainer.lock!("foo")).to eq "foo"
|
120
|
-
end
|
121
|
-
|
122
|
-
it "should not get a lock! if the limit is 0" do
|
123
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 0)
|
124
|
-
expect{ restrainer.lock! }.to raise_error(Restrainer::ThrottledError)
|
125
|
-
end
|
126
|
-
|
127
|
-
it "should get a lock! if the limit is negative" do
|
128
|
-
restrainer = Restrainer.new(:restrainer_test, limit: -1)
|
129
|
-
process_id = restrainer.lock!
|
130
|
-
expect(process_id).to eq nil
|
131
|
-
restrainer.release!(nil)
|
132
|
-
end
|
133
|
-
|
134
|
-
it "should be able to override the limit in lock!" do
|
135
|
-
restrainer = Restrainer.new(:restrainer_test, limit: 0)
|
136
|
-
restrainer.lock!(limit: 1)
|
137
|
-
end
|
138
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path('../../lib/restrainer', __FILE__)
|
4
|
-
require 'timecop'
|
5
|
-
|
6
|
-
RSpec.configure do |config|
|
7
|
-
config.run_all_when_everything_filtered = true
|
8
|
-
config.filter_run :focus
|
9
|
-
|
10
|
-
# Run specs in random order to surface order dependencies. If you find an
|
11
|
-
# order dependency and want to debug it, you can fix the order by providing
|
12
|
-
# the seed, which is printed after each run.
|
13
|
-
# --seed 1234
|
14
|
-
config.order = 'random'
|
15
|
-
|
16
|
-
redis_opts = {}
|
17
|
-
redis_opts = {url: ENV["REDIS_URL"]} if ENV["REDIS_URL"]
|
18
|
-
Restrainer.redis = Redis.new(redis_opts)
|
19
|
-
end
|