restrainer 1.1.0 → 1.1.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 +4 -4
- data/CHANGELOG.md +33 -0
- data/MIT_LICENSE.txt +1 -1
- data/README.md +32 -0
- data/VERSION +1 -1
- data/lib/restrainer.rb +72 -45
- data/restrainer.gemspec +28 -21
- metadata +11 -61
- data/.gitignore +0 -17
- data/CHANGE_LOG.md +0 -9
- data/Rakefile +0 -6
- data/spec/restrainer_spec.rb +0 -138
- data/spec/spec_helper.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c49c2e29f3d29970f0975f8219d99dc74a79668f263b5c923ab0eb2ff92f8fae
|
4
|
+
data.tar.gz: 7af2876a48e9a34d4ff3aad0cd5fb7b12bc5e20c7ee2e875760a59276cc7c601
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23d0627b6b1cca6b1c8c84ffeb20c595570c2918db27c73ce0a4a9aa158f8cf4df67cdd331d2e3b001106185c8adacffd070b215e2f47ac69b013cee1f3d11c0
|
7
|
+
data.tar.gz: c7fc3e5188fa06628d7ab0f5fb4b27b74db917bb95f32ca76875c40be1052237ce83e9018c4bde079e99c09c19514dc1825a2df765f62787c910733e32e4c3f5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,33 @@
|
|
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.2
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Redis instance will now default to a Redis instance with the default options
|
11
|
+
instead of throwing an error if it was not set.
|
12
|
+
- Minumum Ruby version set to 2.5
|
13
|
+
|
14
|
+
## 1.1.1
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
- Circular reference warning
|
18
|
+
|
19
|
+
## 1.1.0
|
20
|
+
|
21
|
+
### Added
|
22
|
+
- Expose manually locking and unlocking processes.
|
23
|
+
- Allow passing in a redis connection in the constructor.
|
24
|
+
|
25
|
+
## 1.0.1
|
26
|
+
|
27
|
+
### Fixed
|
28
|
+
- Use Lua script to avoid race conditions and ensure no extra processes slip through.
|
29
|
+
|
30
|
+
## 1.0.0
|
31
|
+
|
32
|
+
### Added
|
33
|
+
- Initial release.
|
data/MIT_LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
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
|
+
|
1
5
|
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
6
|
|
3
7
|
A [redis server](http://redis.io/) is required to use this gem.
|
@@ -59,3 +63,31 @@ restrainer = Restrainer.new(:my_service, 100, timeout: 10)
|
|
59
63
|
```
|
60
64
|
|
61
65
|
This gem does clean up after itself nicely, so that it won't ever leave unused data lying around in redis.
|
66
|
+
|
67
|
+
## Installation
|
68
|
+
|
69
|
+
Add this line to your application's Gemfile:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
gem 'restrainer'
|
73
|
+
```
|
74
|
+
|
75
|
+
And then execute:
|
76
|
+
```bash
|
77
|
+
$ bundle
|
78
|
+
```
|
79
|
+
|
80
|
+
Or install it yourself as:
|
81
|
+
```bash
|
82
|
+
$ gem install restrainer
|
83
|
+
```
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
Open a pull request on GitHub.
|
88
|
+
|
89
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
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.2
|
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
|
@@ -41,7 +40,7 @@ class Restrainer
|
|
41
40
|
redis.call('zadd', sorted_set, now, process_id)
|
42
41
|
redis.call('expire', sorted_set, ttl)
|
43
42
|
end
|
44
|
-
|
43
|
+
|
45
44
|
-- Return the number of processes running before the process was added.
|
46
45
|
return process_count
|
47
46
|
LUA
|
@@ -53,60 +52,75 @@ 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
|
|
107
121
|
# limit of less zero is no limit; limit of zero is allow none
|
108
122
|
return yield if limit < 0
|
109
|
-
|
123
|
+
|
110
124
|
process_id = lock!(limit: limit)
|
111
125
|
begin
|
112
126
|
yield
|
@@ -114,15 +128,18 @@ class Restrainer
|
|
114
128
|
release!(process_id)
|
115
129
|
end
|
116
130
|
end
|
117
|
-
|
131
|
+
|
118
132
|
# Obtain a lock on one the allowed processes. The method returns a process
|
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
|
-
#
|
125
|
-
|
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.
|
142
|
+
def lock!(process_id = nil, limit: nil)
|
126
143
|
process_id ||= SecureRandom.uuid
|
127
144
|
limit ||= self.limit
|
128
145
|
|
@@ -133,18 +150,28 @@ class Restrainer
|
|
133
150
|
add_process!(redis, process_id, limit)
|
134
151
|
process_id
|
135
152
|
end
|
136
|
-
|
137
|
-
#
|
153
|
+
|
154
|
+
# Release one of the allowed processes. You must pass in a process id
|
155
|
+
# returned by the lock method.
|
156
|
+
#
|
157
|
+
# @param process_id [String] The process identifier returned by the lock call.
|
158
|
+
# @return [Boolean] True if the process was released, false if it was not found.
|
138
159
|
def release!(process_id)
|
139
|
-
|
160
|
+
return false if process_id.nil?
|
161
|
+
|
162
|
+
remove_process!(redis, process_id)
|
140
163
|
end
|
141
164
|
|
142
165
|
# Get the number of processes currently being executed for this restrainer.
|
166
|
+
#
|
167
|
+
# @return [Integer] The number of processes currently being executed.
|
143
168
|
def current
|
144
169
|
redis.zcard(key).to_i
|
145
170
|
end
|
146
|
-
|
147
|
-
# Clear all locks
|
171
|
+
|
172
|
+
# Clear all locks.
|
173
|
+
#
|
174
|
+
# @return [void]
|
148
175
|
def clear!
|
149
176
|
redis.del(key)
|
150
177
|
end
|
@@ -154,29 +181,29 @@ class Restrainer
|
|
154
181
|
def redis
|
155
182
|
@redis || self.class.redis
|
156
183
|
end
|
157
|
-
|
184
|
+
|
158
185
|
# Hash key in redis to story a sorted set of current processes.
|
159
|
-
|
160
|
-
@key
|
161
|
-
end
|
186
|
+
attr_reader :key
|
162
187
|
|
163
188
|
# Add a process to the currently run set.
|
164
189
|
def add_process!(redis, process_id, throttle_limit)
|
165
|
-
process_count = eval_script(redis, process_id, throttle_limit)
|
190
|
+
process_count = eval_script(redis, process_id, throttle_limit)
|
166
191
|
if process_count >= throttle_limit
|
167
192
|
raise ThrottledError.new("#{self.class}: #{@name} already has #{process_count} processes running")
|
168
193
|
end
|
169
194
|
end
|
170
195
|
|
171
|
-
# Remove a process to the currently run set.
|
196
|
+
# Remove a process to the currently run set. Returns true if the process was removed.
|
172
197
|
def remove_process!(redis, process_id)
|
173
|
-
redis.zrem(key, process_id)
|
198
|
+
result = redis.zrem(key, process_id)
|
199
|
+
result = true if result == 1
|
200
|
+
result
|
174
201
|
end
|
175
202
|
|
176
203
|
# Evaluate and execute a Lua script on the redis server.
|
177
204
|
def eval_script(redis, process_id, throttle_limit)
|
178
205
|
sha1 = @add_process_sha1
|
179
|
-
if sha1
|
206
|
+
if sha1.nil?
|
180
207
|
sha1 = redis.script(:load, ADD_PROCESS_SCRIPT)
|
181
208
|
@add_process_sha1 = sha1
|
182
209
|
end
|
@@ -184,7 +211,7 @@ class Restrainer
|
|
184
211
|
begin
|
185
212
|
redis.evalsha(sha1, [], [key, process_id, throttle_limit, @timeout, Time.now.to_i])
|
186
213
|
rescue Redis::CommandError => e
|
187
|
-
if e.message.include?(
|
214
|
+
if e.message.include?("NOSCRIPT")
|
188
215
|
sha1 = redis.script(:load, ADD_PROCESS_SCRIPT)
|
189
216
|
@add_process_sha1 = sha1
|
190
217
|
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.2
|
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-11-11 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.12
|
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
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,17 +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
|
-
Restrainer.redis = Redis.new
|
17
|
-
end
|