throttle_machines 0.1.2 → 0.2.4

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
  SHA256:
3
- metadata.gz: ff1906a9e597bee94979f637bf351aa53160ec2b349005ed731069bdb85cd032
4
- data.tar.gz: 2293e5d59855593686cc2a8df0df560c5286452bc63bbcea4e13fca04d824c92
3
+ metadata.gz: 6450b4499ed28a6520bdc5d51a2c5bc7fcc55ba8e1f1e327def0ca9ab5c80d62
4
+ data.tar.gz: af1addd1f524c9f5663f3f241ce15bfb1aa9a6b5365463f3c9d453a9e03f0b05
5
5
  SHA512:
6
- metadata.gz: 7c1fa93375186f6036c1efcba1292207ba0c831ee0d5e5bb73b7c985e2d45b3c52362d79d590ad1904a25568040e00e5929373381fb48da57155a646e8588e24
7
- data.tar.gz: ae5ffa6881f0e7913227de2991ae8ffd17af31525040e43741491691cddde6ee8e4d27a9a2ad5dd5d2537335237e008f3397e9c4d3f45fab123cdac850ee7c73
6
+ metadata.gz: 564076859ef7219e48060400a26295cffc830a74fbf82d757766753e19d2020fce390ce8b91b9daf996147689dd47fec0911037a34a0208cd160ec3d6009ac70
7
+ data.tar.gz: 39590d460f59b66a700487146ea3309aa8df4d9f3b31e0248acb1b0748bc04b3a26f9a55f52f18b4beb3fca7618a8142092b4d434f45a4ce833a265d8e2c5fdb
data/README.md CHANGED
@@ -33,6 +33,9 @@ gem 'redis'
33
33
 
34
34
  # For planetary Rails integration
35
35
  gem 'rails' # or just railties
36
+
37
+ # For fiber-safe async throttling
38
+ gem 'async'
36
39
  ```
37
40
 
38
41
  Then initialize systems:
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThrottleMachines
4
+ class AsyncBreaker
5
+ class << self
6
+ def new(...)
7
+ ThrottleMachines.load_async_runtime!
8
+ BreakerMachines::AsyncCircuit.new(...)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,16 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent-ruby'
4
-
5
3
  module ThrottleMachines
6
- # Async-aware rate limiter for fiber-safe operations
7
- #
8
- # Like quantum entanglement communications - allows multiple spacecraft
9
- # to communicate simultaneously without interference, each in their own
10
- # quantum state (fiber).
4
+ # Backward-compatible async-aware limiter class.
11
5
  #
12
6
  # Example:
13
- # limiter = ThrottleMachines::AsyncLimiter.new("quantum_comms",
7
+ # limiter = ThrottleMachines::AsyncLimiter.new("api",
14
8
  # limit: 100,
15
9
  # period: 60,
16
10
  # algorithm: :gcra
@@ -18,117 +12,13 @@ module ThrottleMachines
18
12
  #
19
13
  # Async do
20
14
  # if limiter.allowed_async?
21
- # # Non-blocking operation
15
+ # # Currently allowed without consuming capacity
16
+ # end
17
+ #
18
+ # limiter.throttle_async(max_wait: 5) do
19
+ # # Consumes capacity through the same atomic path as throttle!
22
20
  # end
23
21
  # end
24
22
  class AsyncLimiter < Limiter
25
- def initialize(key, limit:, period:, algorithm: :fixed_window, storage: nil)
26
- super
27
- @fiber_storage = Concurrent::Map.new # Thread-safe fiber storage
28
- end
29
-
30
- # Async version of allowed? that's fiber-safe
31
- def allowed_async?
32
- allowed = if defined?(Async::Task) && Async::Task.current?
33
- # In async context, use fiber-local checking
34
- fiber_allowed?
35
- else
36
- # Fall back to regular synchronous check
37
- allowed?
38
- end
39
-
40
- # Don't double-instrument when calling parent allowed?
41
- return allowed unless defined?(Async::Task) && Async::Task.current?
42
-
43
- # Instrument the async check
44
- Instrumentation.rate_limit_checked(self, allowed: allowed, remaining: nil)
45
- allowed
46
- end
47
-
48
- # Non-blocking check with async support
49
- def check_async
50
- if allowed_async?
51
- yield if block_given?
52
- true
53
- else
54
- false
55
- end
56
- end
57
-
58
- # Async throttle with automatic retry
59
- def throttle_async(max_wait: nil)
60
- start_time = current_time
61
-
62
- loop do
63
- if allowed_async?
64
- return yield if block_given?
65
-
66
- return true
67
- end
68
-
69
- wait_time = retry_after
70
-
71
- # Check if we've exceeded max wait time
72
- if max_wait && (current_time - start_time + wait_time) > max_wait
73
- raise ThrottleError, 'Maximum wait time exceeded'
74
- end
75
-
76
- # Non-blocking sleep in async context
77
- if defined?(Async::Task) && Async::Task.current?
78
- Async::Task.current.sleep(wait_time)
79
- else
80
- sleep(wait_time)
81
- end
82
- end
83
- end
84
-
85
- # Get current fiber's state
86
- def fiber_state
87
- fiber_id = Fiber.current.object_id
88
- @fiber_storage.compute_if_absent(fiber_id) do
89
- {
90
- last_check: 0,
91
- tokens: @limit.to_f
92
- }
93
- end
94
- end
95
-
96
- # Clean up fiber storage periodically
97
- def cleanup_fiber_storage
98
- current_fibers = ObjectSpace.each_object(Fiber).map(&:object_id)
99
- @fiber_storage.each_key do |fiber_id|
100
- @fiber_storage.delete(fiber_id) unless current_fibers.include?(fiber_id)
101
- end
102
- end
103
-
104
- private
105
-
106
- def fiber_allowed?
107
- state = fiber_state
108
- now = current_time
109
-
110
- case @algorithm
111
- when :token_bucket
112
- # Refill tokens based on time passed
113
- time_passed = now - state[:last_check]
114
- tokens_to_add = time_passed * (@limit.to_f / @period)
115
- state[:tokens] = [state[:tokens] + tokens_to_add, @limit.to_f].min
116
- state[:last_check] = now
117
-
118
- if state[:tokens] >= 1
119
- state[:tokens] -= 1
120
- true
121
- else
122
- false
123
- end
124
- else
125
- # Delegate to parent for other algorithms
126
- allowed?
127
- end
128
- end
129
-
130
- def current_time
131
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
132
- end
133
23
  end
134
24
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Async/Fiber support for ThrottleMachines.
4
+ #
5
+ # The async gem is optional. When an Async task is active, waits yield back to
6
+ # the scheduler; otherwise the limiter falls back to normal sleep.
7
+
8
+ module ThrottleMachines
9
+ # Fiber-aware rate limiter helpers.
10
+ module AsyncSupport
11
+ def allowed_async?
12
+ allow?
13
+ end
14
+
15
+ alias allow_async? allowed_async?
16
+
17
+ def check_async
18
+ return false unless allowed_async?
19
+
20
+ yield if block_given?
21
+ true
22
+ end
23
+
24
+ def throttle_async(max_wait: nil, &block)
25
+ deadline = max_wait.nil? ? nil : ThrottleMachines.monotonic_time + max_wait.to_f
26
+
27
+ loop do
28
+ begin
29
+ throttle!
30
+ rescue ThrottledError => e
31
+ wait_time = [e.retry_after.to_f, 0.0].max
32
+
33
+ if deadline
34
+ remaining_wait = deadline - ThrottleMachines.monotonic_time
35
+ raise if remaining_wait <= 0 || wait_time > remaining_wait
36
+ end
37
+
38
+ sleep_for(wait_time)
39
+ next
40
+ end
41
+
42
+ return block.call if block
43
+
44
+ return true
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Use Async's fiber-aware sleep when in an async context.
51
+ def sleep_for(duration)
52
+ if (task = current_async_task)
53
+ task.sleep(duration)
54
+ else
55
+ sleep(duration)
56
+ end
57
+ end
58
+
59
+ def current_async_task
60
+ return nil unless defined?(::Async::Task)
61
+
62
+ if ::Async::Task.respond_to?(:current?)
63
+ ::Async::Task.current?
64
+ else
65
+ ::Async::Task.current
66
+ end
67
+ rescue StandardError
68
+ nil
69
+ end
70
+ end
71
+ end
72
+
73
+ ThrottleMachines::Limiter.include(ThrottleMachines::AsyncSupport)
74
+
75
+ warn "[ThrottleMachines] Async support loaded" if ENV['DEBUG_MATRYOSHKA']
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Native speedup for ThrottleMachines using Rust FFI.
4
+ #
5
+ # This module is automatically loaded when the native extension is available.
6
+ # It prepends optimized implementations onto Storage::Memory.
7
+ #
8
+ # Environment controls:
9
+ # DISABLE_MATRYOSHKA_NATIVE=1 - Disable all matryoshka native extensions
10
+ # DISABLE_THROTTLE_MACHINES_NATIVE=1 - Disable only throttle_machines native
11
+ # DEBUG_MATRYOSHKA=1 - Log which backend is loaded
12
+
13
+ return if ENV["DISABLE_MATRYOSHKA_NATIVE"]
14
+ return if ENV["DISABLE_THROTTLE_MACHINES_NATIVE"]
15
+
16
+ begin
17
+ require "throttle_machines_native/throttle_machines_native"
18
+
19
+ module ThrottleMachines
20
+ module Storage
21
+ # Native GCRA implementation using Rust.
22
+ module NativeGCRA
23
+ def check_gcra_limit(key, emission_interval, delay_tolerance, ttl)
24
+ with_write_lock(key) do
25
+ now = current_time
26
+ state = @gcra_states[key] || { tat: 0.0 }
27
+
28
+ # Rust does the math
29
+ ThrottleMachinesNative.gcra_check(
30
+ state[:tat], now, emission_interval, delay_tolerance
31
+ ) => { allowed:, state: new_tat, retry_after: }
32
+
33
+ @gcra_states[key] = { tat: new_tat, expires_at: now + ttl } if allowed
34
+
35
+ { allowed: allowed, retry_after: retry_after, tat: new_tat }
36
+ end
37
+ end
38
+
39
+ def peek_gcra_limit(key, _emission_interval, delay_tolerance)
40
+ with_read_lock(key) do
41
+ now = current_time
42
+ state = @gcra_states[key] || { tat: 0.0 }
43
+
44
+ # Rust does the math
45
+ ThrottleMachinesNative.gcra_peek(
46
+ state[:tat], now, delay_tolerance
47
+ ) => { allowed:, state: tat, retry_after: }
48
+
49
+ { allowed: allowed, retry_after: retry_after, tat: tat }
50
+ end
51
+ end
52
+ end
53
+
54
+ # Native Token Bucket implementation using Rust.
55
+ module NativeTokenBucket
56
+ def check_token_bucket(key, capacity, refill_rate, ttl)
57
+ with_write_lock(key) do
58
+ now = current_time
59
+ bucket = @token_buckets[key] || { tokens: capacity, last_refill: now }
60
+
61
+ # Rust does the math
62
+ ThrottleMachinesNative.token_bucket_check(
63
+ bucket[:tokens], bucket[:last_refill], now, capacity, refill_rate
64
+ ) => { allowed:, state: new_tokens, retry_after: }
65
+
66
+ if allowed
67
+ @token_buckets[key] = { tokens: new_tokens, last_refill: now, expires_at: now + ttl }
68
+ end
69
+
70
+ { allowed: allowed, retry_after: retry_after, tokens_remaining: new_tokens.floor }
71
+ end
72
+ end
73
+
74
+ def peek_token_bucket(key, capacity, refill_rate)
75
+ with_read_lock(key) do
76
+ now = current_time
77
+ bucket = @token_buckets[key] || { tokens: capacity, last_refill: now }
78
+
79
+ # Rust does the math
80
+ ThrottleMachinesNative.token_bucket_peek(
81
+ bucket[:tokens], bucket[:last_refill], now, capacity, refill_rate
82
+ ) => { allowed:, state: tokens, retry_after: }
83
+
84
+ { allowed: allowed, retry_after: retry_after, tokens_remaining: tokens.floor }
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Prepend native implementations
92
+ ThrottleMachines::Storage::Memory.prepend(ThrottleMachines::Storage::NativeGCRA)
93
+ ThrottleMachines::Storage::Memory.prepend(ThrottleMachines::Storage::NativeTokenBucket)
94
+
95
+ warn "[ThrottleMachines] Native speedup loaded" if ENV["DEBUG_MATRYOSHKA"]
96
+ rescue LoadError => e
97
+ warn "[ThrottleMachines] Native speedup unavailable: #{e.message}" if ENV["DEBUG_MATRYOSHKA"]
98
+ end
@@ -2,18 +2,7 @@
2
2
 
3
3
  module ThrottleMachines
4
4
  class RackMiddleware
5
- class Allow2Ban
6
- attr_reader :name, :maxretry, :findtime, :bantime, :block
7
-
8
- def initialize(name, options, &block)
9
- @name = name
10
- @block = block
11
-
12
- @maxretry = options[:maxretry] || 5
13
- @findtime = options[:findtime] || 60
14
- @bantime = options[:bantime] || 300
15
- end
16
-
5
+ class Allow2Ban < BanFilter
17
6
  def matched_by?(request)
18
7
  discriminator = discriminator_for(request)
19
8
  return false unless discriminator
@@ -57,12 +46,6 @@ module ThrottleMachines
57
46
 
58
47
  false # Allow2Ban never blocks directly
59
48
  end
60
-
61
- private
62
-
63
- def discriminator_for(request)
64
- @block.call(request)
65
- end
66
49
  end
67
50
  end
68
51
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThrottleMachines
4
+ class RackMiddleware
5
+ # Base class for Fail2Ban and Allow2Ban filters.
6
+ #
7
+ # Both are configured with the same maxretry/findtime/bantime thresholds and
8
+ # derive a discriminator from the request via the supplied block. The ban
9
+ # logic itself lives in each subclass.
10
+ class BanFilter
11
+ attr_reader :name, :maxretry, :findtime, :bantime, :block
12
+
13
+ def initialize(name, options, &block)
14
+ @name = name
15
+ @block = block
16
+
17
+ @maxretry = options[:maxretry] || 5
18
+ @findtime = options[:findtime] || 60
19
+ @bantime = options[:bantime] || 300
20
+ end
21
+
22
+ private
23
+
24
+ def discriminator_for(request)
25
+ @block.call(request)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,25 +2,12 @@
2
2
 
3
3
  module ThrottleMachines
4
4
  class RackMiddleware
5
- class Blocklist
6
- attr_reader :name, :block
5
+ # Blocks requests matching the configured block (returns true => blocked).
6
+ class Blocklist < ListFilter
7
+ private
7
8
 
8
- def initialize(name, &block)
9
- @name = name
10
- @block = block
11
- end
12
-
13
- def matched_by?(request)
14
- return false unless @block
15
-
16
- if @block.call(request)
17
- request.env['rack.attack.matched'] = @name
18
- request.env['rack.attack.match_type'] = :blocklist
19
- ThrottleMachines::RackMiddleware.instrument(request)
20
- true
21
- else
22
- false
23
- end
9
+ def match_type
10
+ :blocklist
24
11
  end
25
12
  end
26
13
  end
@@ -2,18 +2,7 @@
2
2
 
3
3
  module ThrottleMachines
4
4
  class RackMiddleware
5
- class Fail2Ban
6
- attr_reader :name, :maxretry, :findtime, :bantime, :block
7
-
8
- def initialize(name, options, &block)
9
- @name = name
10
- @block = block
11
-
12
- @maxretry = options[:maxretry] || 5
13
- @findtime = options[:findtime] || 60
14
- @bantime = options[:bantime] || 300
15
- end
16
-
5
+ class Fail2Ban < BanFilter
17
6
  def banned?(request)
18
7
  discriminator = discriminator_for(request)
19
8
  return false unless discriminator
@@ -84,12 +73,6 @@ module ThrottleMachines
84
73
  # Expected - this records the failure
85
74
  end
86
75
  end
87
-
88
- private
89
-
90
- def discriminator_for(request)
91
- @block.call(request)
92
- end
93
76
  end
94
77
  end
95
78
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ThrottleMachines
4
+ class RackMiddleware
5
+ # Base class for Safelist and Blocklist filters.
6
+ #
7
+ # Both match a request against a user-supplied block and, on a match, tag the
8
+ # rack env and emit instrumentation. They differ only in the +match_type+
9
+ # recorded on the env, which subclasses provide.
10
+ class ListFilter
11
+ attr_reader :name, :block
12
+
13
+ def initialize(name, &block)
14
+ @name = name
15
+ @block = block
16
+ end
17
+
18
+ def matched_by?(request)
19
+ return false unless @block
20
+
21
+ if @block.call(request)
22
+ request.env['rack.attack.matched'] = @name
23
+ request.env['rack.attack.match_type'] = match_type
24
+ ThrottleMachines::RackMiddleware.instrument(request)
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # @return [Symbol] the rack.attack match type recorded for this filter
34
+ def match_type
35
+ raise NotImplementedError, "#{self.class} must implement #match_type"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,25 +2,12 @@
2
2
 
3
3
  module ThrottleMachines
4
4
  class RackMiddleware
5
- class Safelist
6
- attr_reader :name, :block
5
+ # Allows (short-circuits) requests matching the configured block.
6
+ class Safelist < ListFilter
7
+ private
7
8
 
8
- def initialize(name, &block)
9
- @name = name
10
- @block = block
11
- end
12
-
13
- def matched_by?(request)
14
- return false unless @block
15
-
16
- if @block.call(request)
17
- request.env['rack.attack.matched'] = @name
18
- request.env['rack.attack.match_type'] = :safelist
19
- ThrottleMachines::RackMiddleware.instrument(request)
20
- true
21
- else
22
- false
23
- end
9
+ def match_type
10
+ :safelist
24
11
  end
25
12
  end
26
13
  end
@@ -35,11 +35,9 @@ module ThrottleMachines
35
35
  @configuration.instance_eval(&block) if block
36
36
  end
37
37
 
38
- # rubocop:disable Rails/Delegate -- Ruby 3.4 compatibility issue with delegate
39
38
  def reset!
40
39
  ThrottleMachines.reset!
41
40
  end
42
- # rubocop:enable Rails/Delegate
43
41
 
44
42
  def clear!
45
43
  @configuration = Configuration.new
@@ -19,4 +19,4 @@ if allow then
19
19
  redis.call('SET', key, new_tat, 'EX', ttl)
20
20
  end
21
21
 
22
- return { allow and 1 or 0, tat }
22
+ return { allow and 1 or 0, tat }
@@ -6,4 +6,4 @@ if ttl <= 0 then
6
6
  redis.call('EXPIRE', KEYS[1], ARGV[2])
7
7
  end
8
8
 
9
- return count
9
+ return count
@@ -13,4 +13,4 @@ end
13
13
  tat = math.max(tat, now)
14
14
  local allow = (tat - now) <= delay_tolerance
15
15
 
16
- return { allow and 1 or 0, tat }
16
+ return { allow and 1 or 0, tat }
@@ -15,4 +15,4 @@ tokens = math.min(tokens + tokens_to_add, capacity)
15
15
  local allow = tokens >= 1
16
16
  local tokens_after = allow and (tokens - 1) or 0
17
17
 
18
- return { allow and 1 or 0, tokens_after }
18
+ return { allow and 1 or 0, tokens_after }
@@ -20,4 +20,4 @@ if allow then
20
20
  redis.call('EXPIRE', key, ttl)
21
21
  end
22
22
 
23
- return { allow and 1 or 0, tokens }
23
+ return { allow and 1 or 0, tokens }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ThrottleMachines
4
- VERSION = '0.1.2'
4
+ VERSION = '0.2.4'
5
5
  end
@@ -17,17 +17,32 @@ loader.setup
17
17
  module ThrottleMachines
18
18
  class Configuration
19
19
  attr_accessor :default_limit, :default_period, :default_storage, :clock,
20
- :instrumentation_enabled, :instrumentation_backend, :_storage_instance
20
+ :default_algorithm, :instrumentation_enabled, :instrumentation_backend,
21
+ :_storage_instance
21
22
 
22
23
  def initialize
23
24
  @default_limit = 100
24
25
  @default_period = 60 # 1 minute
25
26
  @default_storage = :memory
27
+ @default_algorithm = :fixed_window
26
28
  @clock = nil
27
29
  @instrumentation_enabled = true
28
30
  @instrumentation_backend = nil
31
+ @fiber_safe = false
29
32
  @_storage_instance = nil
30
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=
31
46
  end
32
47
 
33
48
  @config = Configuration.new
@@ -46,6 +61,7 @@ module ThrottleMachines
46
61
  # Apply instrumentation settings
47
62
  Instrumentation.enabled = config.instrumentation_enabled
48
63
  Instrumentation.backend = config.instrumentation_backend if config.instrumentation_backend
64
+ load_async_runtime! if config.fiber_safe
49
65
  end
50
66
 
51
67
  def storage
@@ -75,7 +91,7 @@ module ThrottleMachines
75
91
  control
76
92
  end
77
93
 
78
- def limit(key, limit:, period:, algorithm: :fixed_window, &block)
94
+ def limit(key, limit:, period:, algorithm: nil, &block)
79
95
  limiter = limiter(key, limit: limit, period: period, algorithm: algorithm)
80
96
  limiter.throttle!(&block)
81
97
  end
@@ -99,8 +115,28 @@ module ThrottleMachines
99
115
  ChronoMachines.retry(policy_options, &block)
100
116
  end
101
117
 
102
- def limiter(key, limit:, period:, algorithm: :fixed_window)
103
- Limiter.new(key, limit: limit, period: period, algorithm: algorithm, storage: storage)
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."
104
140
  end
105
141
 
106
142
  private
@@ -145,3 +181,20 @@ module ThrottleMachines
145
181
  # Back-compat wrapper: use BreakerMachines as the circuit implementation
146
182
  Breaker = BreakerMachines::Circuit
147
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 CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: throttle_machines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -71,28 +71,84 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: '0.7'
74
+ version: '0.19'
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '0.7'
81
+ version: '0.19'
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: chrono_machines
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - ">="
86
+ - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '0.2'
88
+ version: '0.9'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.9'
96
+ - !ruby/object:Gem::Dependency
97
+ name: minitest
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '5.16'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '5.16'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rb_sys
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.9'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.9'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rake
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
94
129
  - !ruby/object:Gem::Version
95
- version: '0.2'
130
+ version: '13.0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '13.0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rake-compiler
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.3'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.3'
96
152
  description: ThrottleMachines provides ultra-thin, elegant rate limiting with temporal
97
153
  precision for distributed systems.
98
154
  email:
@@ -103,9 +159,10 @@ extra_rdoc_files: []
103
159
  files:
104
160
  - LICENSE
105
161
  - README.md
106
- - Rakefile
107
162
  - lib/throttle_machines.rb
163
+ - lib/throttle_machines/async_breaker.rb
108
164
  - lib/throttle_machines/async_limiter.rb
165
+ - lib/throttle_machines/async_support.rb
109
166
  - lib/throttle_machines/control.rb
110
167
  - lib/throttle_machines/controller_helpers.rb
111
168
  - lib/throttle_machines/dependency_error.rb
@@ -115,11 +172,14 @@ files:
115
172
  - lib/throttle_machines/instrumentation.rb
116
173
  - lib/throttle_machines/limiter.rb
117
174
  - lib/throttle_machines/middleware.rb
175
+ - lib/throttle_machines/native_speedup.rb
118
176
  - lib/throttle_machines/rack_middleware.rb
119
177
  - lib/throttle_machines/rack_middleware/allow2_ban.rb
178
+ - lib/throttle_machines/rack_middleware/ban_filter.rb
120
179
  - lib/throttle_machines/rack_middleware/blocklist.rb
121
180
  - lib/throttle_machines/rack_middleware/configuration.rb
122
181
  - lib/throttle_machines/rack_middleware/fail2_ban.rb
182
+ - lib/throttle_machines/rack_middleware/list_filter.rb
123
183
  - lib/throttle_machines/rack_middleware/request.rb
124
184
  - lib/throttle_machines/rack_middleware/safelist.rb
125
185
  - lib/throttle_machines/rack_middleware/throttle.rb
@@ -143,6 +203,8 @@ metadata:
143
203
  source_code_uri: https://github.com/seuros/throttle_machines
144
204
  changelog_uri: https://github.com/seuros/throttle_machines/blob/main/CHANGELOG.md
145
205
  rubygems_mfa_required: 'true'
206
+ cargo_crate_name: throttle_machines_native
207
+ cargo_manifest_path: ext/throttle_machines_native/ffi/Cargo.toml
146
208
  rdoc_options: []
147
209
  require_paths:
148
210
  - lib
@@ -150,14 +212,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
212
  requirements:
151
213
  - - ">="
152
214
  - !ruby/object:Gem::Version
153
- version: 3.3.0
215
+ version: 4.0.0
154
216
  required_rubygems_version: !ruby/object:Gem::Requirement
155
217
  requirements:
156
218
  - - ">="
157
219
  - !ruby/object:Gem::Version
158
220
  version: '0'
159
221
  requirements: []
160
- rubygems_version: 3.6.9
222
+ rubygems_version: 4.0.20
161
223
  specification_version: 4
162
224
  summary: Advanced Rate limiting for Ruby applications
163
225
  test_files: []
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
5
-
6
- Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.test_files = FileList['test/**/*_test.rb'].exclude('test/dummy/**/*')
9
- t.verbose = false
10
- end
11
-
12
- task default: :test