throttle_machines 0.2.4-x86_64-linux
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 +7 -0
- data/LICENSE +20 -0
- data/README.md +204 -0
- data/lib/throttle_machines/async_breaker.rb +12 -0
- data/lib/throttle_machines/async_limiter.rb +24 -0
- data/lib/throttle_machines/async_support.rb +75 -0
- data/lib/throttle_machines/control.rb +95 -0
- data/lib/throttle_machines/controller_helpers.rb +79 -0
- data/lib/throttle_machines/dependency_error.rb +6 -0
- data/lib/throttle_machines/engine.rb +18 -0
- data/lib/throttle_machines/hedged_breaker.rb +23 -0
- data/lib/throttle_machines/hedged_request.rb +117 -0
- data/lib/throttle_machines/instrumentation.rb +158 -0
- data/lib/throttle_machines/limiter.rb +167 -0
- data/lib/throttle_machines/middleware.rb +89 -0
- data/lib/throttle_machines/native_speedup.rb +98 -0
- data/lib/throttle_machines/rack_middleware/allow2_ban.rb +51 -0
- data/lib/throttle_machines/rack_middleware/ban_filter.rb +29 -0
- data/lib/throttle_machines/rack_middleware/blocklist.rb +14 -0
- data/lib/throttle_machines/rack_middleware/configuration.rb +103 -0
- data/lib/throttle_machines/rack_middleware/fail2_ban.rb +78 -0
- data/lib/throttle_machines/rack_middleware/list_filter.rb +39 -0
- data/lib/throttle_machines/rack_middleware/request.rb +12 -0
- data/lib/throttle_machines/rack_middleware/safelist.rb +14 -0
- data/lib/throttle_machines/rack_middleware/throttle.rb +95 -0
- data/lib/throttle_machines/rack_middleware/track.rb +51 -0
- data/lib/throttle_machines/rack_middleware.rb +87 -0
- data/lib/throttle_machines/storage/base.rb +72 -0
- data/lib/throttle_machines/storage/memory.rb +267 -0
- data/lib/throttle_machines/storage/null.rb +67 -0
- data/lib/throttle_machines/storage/redis/gcra.lua +22 -0
- data/lib/throttle_machines/storage/redis/increment_counter.lua +9 -0
- data/lib/throttle_machines/storage/redis/peek_gcra.lua +16 -0
- data/lib/throttle_machines/storage/redis/peek_token_bucket.lua +18 -0
- data/lib/throttle_machines/storage/redis/token_bucket.lua +23 -0
- data/lib/throttle_machines/storage/redis.rb +231 -0
- data/lib/throttle_machines/throttled_error.rb +14 -0
- data/lib/throttle_machines/version.rb +5 -0
- data/lib/throttle_machines.rb +200 -0
- data/lib/throttle_machines_native/throttle_machines_native.so +0 -0
- metadata +218 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
require 'zeitwerk'
|
|
6
|
+
require 'active_support/core_ext/class/attribute'
|
|
7
|
+
|
|
8
|
+
# Ecosystem dependencies
|
|
9
|
+
require 'chrono_machines'
|
|
10
|
+
require 'breaker_machines'
|
|
11
|
+
|
|
12
|
+
# Set up Zeitwerk loader
|
|
13
|
+
loader = Zeitwerk::Loader.for_gem
|
|
14
|
+
loader.ignore("#{__dir__}/throttle_machines/engine.rb") unless defined?(Rails::Engine)
|
|
15
|
+
loader.setup
|
|
16
|
+
|
|
17
|
+
module ThrottleMachines
|
|
18
|
+
class Configuration
|
|
19
|
+
attr_accessor :default_limit, :default_period, :default_storage, :clock,
|
|
20
|
+
:default_algorithm, :instrumentation_enabled, :instrumentation_backend,
|
|
21
|
+
:_storage_instance
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@default_limit = 100
|
|
25
|
+
@default_period = 60 # 1 minute
|
|
26
|
+
@default_storage = :memory
|
|
27
|
+
@default_algorithm = :fixed_window
|
|
28
|
+
@clock = nil
|
|
29
|
+
@instrumentation_enabled = true
|
|
30
|
+
@instrumentation_backend = nil
|
|
31
|
+
@fiber_safe = false
|
|
32
|
+
@_storage_instance = nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def fiber_safe
|
|
36
|
+
@fiber_safe
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fiber_safe=(value)
|
|
40
|
+
@fiber_safe = !!value
|
|
41
|
+
ThrottleMachines.load_async_runtime! if @fiber_safe && ThrottleMachines.respond_to?(:load_async_runtime!)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
alias async_enabled fiber_safe
|
|
45
|
+
alias async_enabled= fiber_safe=
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@config = Configuration.new
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Delegate monotonic time to BreakerMachines for consistency
|
|
52
|
+
delegate :monotonic_time, to: :BreakerMachines
|
|
53
|
+
|
|
54
|
+
def config
|
|
55
|
+
@config
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def configure
|
|
59
|
+
yield(config) if block_given?
|
|
60
|
+
|
|
61
|
+
# Apply instrumentation settings
|
|
62
|
+
Instrumentation.enabled = config.instrumentation_enabled
|
|
63
|
+
Instrumentation.backend = config.instrumentation_backend if config.instrumentation_backend
|
|
64
|
+
load_async_runtime! if config.fiber_safe
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def storage
|
|
68
|
+
config._storage_instance ||= create_storage(config.default_storage)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def storage=(value)
|
|
72
|
+
config._storage_instance = create_storage(value)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def reset!(key = nil)
|
|
76
|
+
if key
|
|
77
|
+
storage.clear("#{key}*")
|
|
78
|
+
else
|
|
79
|
+
storage.clear
|
|
80
|
+
# Reset storage instance to force recreation with defaults
|
|
81
|
+
config._storage_instance = nil
|
|
82
|
+
# Re-apply instrumentation settings from the configuration
|
|
83
|
+
Instrumentation.enabled = config.instrumentation_enabled
|
|
84
|
+
Instrumentation.backend = config.instrumentation_backend
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def control(key, &block)
|
|
89
|
+
control = Control.new(key)
|
|
90
|
+
control.instance_eval(&block) if block
|
|
91
|
+
control
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def limit(key, limit:, period:, algorithm: nil, &block)
|
|
95
|
+
limiter = limiter(key, limit: limit, period: period, algorithm: algorithm)
|
|
96
|
+
limiter.throttle!(&block)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def break_circuit(key, failures:, timeout:, &block)
|
|
100
|
+
# Delegate to BreakerMachines; use reset_timeout for open duration
|
|
101
|
+
breaker = BreakerMachines::Circuit.new(
|
|
102
|
+
key,
|
|
103
|
+
failure_threshold: failures,
|
|
104
|
+
reset_timeout: timeout
|
|
105
|
+
)
|
|
106
|
+
breaker.call(&block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def retry_with(max_attempts: 3, backoff: :exponential, &block)
|
|
110
|
+
# Delegate to chrono_machines
|
|
111
|
+
policy_options = {
|
|
112
|
+
max_attempts: max_attempts,
|
|
113
|
+
jitter_factor: backoff == :exponential ? 1.0 : 0.0
|
|
114
|
+
}
|
|
115
|
+
ChronoMachines.retry(policy_options, &block)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def limiter(key, limit:, period:, algorithm: nil)
|
|
119
|
+
Limiter.new(
|
|
120
|
+
key,
|
|
121
|
+
limit: limit,
|
|
122
|
+
period: period,
|
|
123
|
+
algorithm: algorithm || config.default_algorithm,
|
|
124
|
+
storage: storage
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def async_breaker(...)
|
|
129
|
+
ThrottleMachines::AsyncBreaker.new(...)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def load_async_runtime!
|
|
133
|
+
require 'async'
|
|
134
|
+
require 'async/task'
|
|
135
|
+
true
|
|
136
|
+
rescue LoadError => e
|
|
137
|
+
raise unless e.message.include?('async')
|
|
138
|
+
|
|
139
|
+
raise LoadError, "The 'async' gem is required for fiber_safe mode. Add `gem 'async'` to your Gemfile."
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def create_storage(storage)
|
|
145
|
+
case storage
|
|
146
|
+
when Symbol
|
|
147
|
+
create_storage_from_symbol(storage)
|
|
148
|
+
when Class
|
|
149
|
+
storage.new
|
|
150
|
+
when Storage::Base
|
|
151
|
+
storage
|
|
152
|
+
else
|
|
153
|
+
raise ArgumentError, "Invalid storage: #{storage.inspect}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def create_storage_from_symbol(symbol)
|
|
158
|
+
case symbol
|
|
159
|
+
when :memory
|
|
160
|
+
Storage::Memory.new
|
|
161
|
+
when :redis
|
|
162
|
+
raise ArgumentError, 'Redis storage requires redis gem' unless defined?(Redis)
|
|
163
|
+
|
|
164
|
+
raise ArgumentError, 'Redis storage requires a Redis client instance. ' \
|
|
165
|
+
'Configure with: config.storage = Storage::Redis.new(redis: Redis.new)'
|
|
166
|
+
|
|
167
|
+
when :null
|
|
168
|
+
Storage::Null.new
|
|
169
|
+
else
|
|
170
|
+
raise ArgumentError, "Unknown storage type: #{symbol}"
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Auto-configure with defaults
|
|
176
|
+
configure
|
|
177
|
+
|
|
178
|
+
CircuitOpenError = BreakerMachines::CircuitOpenError
|
|
179
|
+
RetryExhaustedError = ChronoMachines::MaxRetriesExceededError
|
|
180
|
+
|
|
181
|
+
# Back-compat wrapper: use BreakerMachines as the circuit implementation
|
|
182
|
+
Breaker = BreakerMachines::Circuit
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Optional speedups (order matters: most specific first)
|
|
186
|
+
# These are loaded after Zeitwerk setup to allow graceful fallback.
|
|
187
|
+
|
|
188
|
+
# Native Rust extension for performance-critical algorithms
|
|
189
|
+
begin
|
|
190
|
+
require_relative "throttle_machines/native_speedup"
|
|
191
|
+
rescue LoadError
|
|
192
|
+
# Native extension not available, pure Ruby will be used
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Async/Fiber support when Async gem is loaded
|
|
196
|
+
begin
|
|
197
|
+
require_relative "throttle_machines/async_support"
|
|
198
|
+
rescue LoadError
|
|
199
|
+
# Async gem not available
|
|
200
|
+
end
|
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: throttle_machines
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.4
|
|
5
|
+
platform: x86_64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Abdelkader Boudih
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-10-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 8.0.4
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 8.0.4
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: concurrent-ruby
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.3'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rack
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: zeitwerk
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.7'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '2.7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: breaker_machines
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.19'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.19'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: chrono_machines
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.9'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.9'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: minitest
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '5.16'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '5.16'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '13.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '13.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rake-compiler
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '1.3'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '1.3'
|
|
139
|
+
description: ThrottleMachines provides ultra-thin, elegant rate limiting with temporal
|
|
140
|
+
precision for distributed systems.
|
|
141
|
+
email:
|
|
142
|
+
- terminale@gmail.com
|
|
143
|
+
executables: []
|
|
144
|
+
extensions: []
|
|
145
|
+
extra_rdoc_files: []
|
|
146
|
+
files:
|
|
147
|
+
- LICENSE
|
|
148
|
+
- README.md
|
|
149
|
+
- lib/throttle_machines.rb
|
|
150
|
+
- lib/throttle_machines/async_breaker.rb
|
|
151
|
+
- lib/throttle_machines/async_limiter.rb
|
|
152
|
+
- lib/throttle_machines/async_support.rb
|
|
153
|
+
- lib/throttle_machines/control.rb
|
|
154
|
+
- lib/throttle_machines/controller_helpers.rb
|
|
155
|
+
- lib/throttle_machines/dependency_error.rb
|
|
156
|
+
- lib/throttle_machines/engine.rb
|
|
157
|
+
- lib/throttle_machines/hedged_breaker.rb
|
|
158
|
+
- lib/throttle_machines/hedged_request.rb
|
|
159
|
+
- lib/throttle_machines/instrumentation.rb
|
|
160
|
+
- lib/throttle_machines/limiter.rb
|
|
161
|
+
- lib/throttle_machines/middleware.rb
|
|
162
|
+
- lib/throttle_machines/native_speedup.rb
|
|
163
|
+
- lib/throttle_machines/rack_middleware.rb
|
|
164
|
+
- lib/throttle_machines/rack_middleware/allow2_ban.rb
|
|
165
|
+
- lib/throttle_machines/rack_middleware/ban_filter.rb
|
|
166
|
+
- lib/throttle_machines/rack_middleware/blocklist.rb
|
|
167
|
+
- lib/throttle_machines/rack_middleware/configuration.rb
|
|
168
|
+
- lib/throttle_machines/rack_middleware/fail2_ban.rb
|
|
169
|
+
- lib/throttle_machines/rack_middleware/list_filter.rb
|
|
170
|
+
- lib/throttle_machines/rack_middleware/request.rb
|
|
171
|
+
- lib/throttle_machines/rack_middleware/safelist.rb
|
|
172
|
+
- lib/throttle_machines/rack_middleware/throttle.rb
|
|
173
|
+
- lib/throttle_machines/rack_middleware/track.rb
|
|
174
|
+
- lib/throttle_machines/storage/base.rb
|
|
175
|
+
- lib/throttle_machines/storage/memory.rb
|
|
176
|
+
- lib/throttle_machines/storage/null.rb
|
|
177
|
+
- lib/throttle_machines/storage/redis.rb
|
|
178
|
+
- lib/throttle_machines/storage/redis/gcra.lua
|
|
179
|
+
- lib/throttle_machines/storage/redis/increment_counter.lua
|
|
180
|
+
- lib/throttle_machines/storage/redis/peek_gcra.lua
|
|
181
|
+
- lib/throttle_machines/storage/redis/peek_token_bucket.lua
|
|
182
|
+
- lib/throttle_machines/storage/redis/token_bucket.lua
|
|
183
|
+
- lib/throttle_machines/throttled_error.rb
|
|
184
|
+
- lib/throttle_machines/version.rb
|
|
185
|
+
- lib/throttle_machines_native/throttle_machines_native.so
|
|
186
|
+
homepage: https://github.com/seuros/throttle_machines
|
|
187
|
+
licenses:
|
|
188
|
+
- MIT
|
|
189
|
+
metadata:
|
|
190
|
+
homepage_uri: https://github.com/seuros/throttle_machines
|
|
191
|
+
source_code_uri: https://github.com/seuros/throttle_machines
|
|
192
|
+
changelog_uri: https://github.com/seuros/throttle_machines/blob/main/CHANGELOG.md
|
|
193
|
+
rubygems_mfa_required: 'true'
|
|
194
|
+
cargo_crate_name: throttle_machines_native
|
|
195
|
+
cargo_manifest_path: ext/throttle_machines_native/ffi/Cargo.toml
|
|
196
|
+
post_install_message:
|
|
197
|
+
rdoc_options: []
|
|
198
|
+
require_paths:
|
|
199
|
+
- lib
|
|
200
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
201
|
+
requirements:
|
|
202
|
+
- - ">="
|
|
203
|
+
- !ruby/object:Gem::Version
|
|
204
|
+
version: '4.0'
|
|
205
|
+
- - "<"
|
|
206
|
+
- !ruby/object:Gem::Version
|
|
207
|
+
version: 4.1.dev
|
|
208
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - ">="
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '0'
|
|
213
|
+
requirements: []
|
|
214
|
+
rubygems_version: 3.5.23
|
|
215
|
+
signing_key:
|
|
216
|
+
specification_version: 4
|
|
217
|
+
summary: Advanced Rate limiting for Ruby applications
|
|
218
|
+
test_files: []
|