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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +204 -0
  4. data/lib/throttle_machines/async_breaker.rb +12 -0
  5. data/lib/throttle_machines/async_limiter.rb +24 -0
  6. data/lib/throttle_machines/async_support.rb +75 -0
  7. data/lib/throttle_machines/control.rb +95 -0
  8. data/lib/throttle_machines/controller_helpers.rb +79 -0
  9. data/lib/throttle_machines/dependency_error.rb +6 -0
  10. data/lib/throttle_machines/engine.rb +18 -0
  11. data/lib/throttle_machines/hedged_breaker.rb +23 -0
  12. data/lib/throttle_machines/hedged_request.rb +117 -0
  13. data/lib/throttle_machines/instrumentation.rb +158 -0
  14. data/lib/throttle_machines/limiter.rb +167 -0
  15. data/lib/throttle_machines/middleware.rb +89 -0
  16. data/lib/throttle_machines/native_speedup.rb +98 -0
  17. data/lib/throttle_machines/rack_middleware/allow2_ban.rb +51 -0
  18. data/lib/throttle_machines/rack_middleware/ban_filter.rb +29 -0
  19. data/lib/throttle_machines/rack_middleware/blocklist.rb +14 -0
  20. data/lib/throttle_machines/rack_middleware/configuration.rb +103 -0
  21. data/lib/throttle_machines/rack_middleware/fail2_ban.rb +78 -0
  22. data/lib/throttle_machines/rack_middleware/list_filter.rb +39 -0
  23. data/lib/throttle_machines/rack_middleware/request.rb +12 -0
  24. data/lib/throttle_machines/rack_middleware/safelist.rb +14 -0
  25. data/lib/throttle_machines/rack_middleware/throttle.rb +95 -0
  26. data/lib/throttle_machines/rack_middleware/track.rb +51 -0
  27. data/lib/throttle_machines/rack_middleware.rb +87 -0
  28. data/lib/throttle_machines/storage/base.rb +72 -0
  29. data/lib/throttle_machines/storage/memory.rb +267 -0
  30. data/lib/throttle_machines/storage/null.rb +67 -0
  31. data/lib/throttle_machines/storage/redis/gcra.lua +22 -0
  32. data/lib/throttle_machines/storage/redis/increment_counter.lua +9 -0
  33. data/lib/throttle_machines/storage/redis/peek_gcra.lua +16 -0
  34. data/lib/throttle_machines/storage/redis/peek_token_bucket.lua +18 -0
  35. data/lib/throttle_machines/storage/redis/token_bucket.lua +23 -0
  36. data/lib/throttle_machines/storage/redis.rb +231 -0
  37. data/lib/throttle_machines/throttled_error.rb +14 -0
  38. data/lib/throttle_machines/version.rb +5 -0
  39. data/lib/throttle_machines.rb +200 -0
  40. data/lib/throttle_machines_native/throttle_machines_native.so +0 -0
  41. 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
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: []