Class: DhanHQ::RateLimiter
- Inherits:
-
Object
- Object
- DhanHQ::RateLimiter
- Defined in:
- lib/DhanHQ/rate_limiter.rb
Overview
Coarse-grained in-memory throttler matching the platform rate limits.
Constant Summary collapse
- RATE_LIMITS =
Per-interval thresholds keyed by API type.
{ order_api: { per_second: 25, per_minute: 250, per_hour: 1000, per_day: 7000 }, data_api: { per_second: 5, per_minute: Float::INFINITY, per_hour: Float::INFINITY, per_day: 100_000 }, quote_api: { per_second: 1, per_minute: Float::INFINITY, per_hour: Float::INFINITY, per_day: Float::INFINITY }, option_chain: { per_second: 1.0 / 3, per_minute: 20, per_hour: 600, per_day: 4800 }, non_trading_api: { per_second: 20, per_minute: Float::INFINITY, per_hour: Float::INFINITY, per_day: Float::INFINITY } }.freeze
Class Attribute Summary collapse
-
.mutexes ⇒ Object
readonly
Returns the value of attribute mutexes.
-
.shared_limiters ⇒ Object
readonly
Returns the value of attribute shared_limiters.
Class Method Summary collapse
-
.for(api_type) ⇒ Object
Get or create a shared rate limiter instance for the given API type.
Instance Method Summary collapse
-
#initialize(api_type) ⇒ RateLimiter
constructor
Creates a rate limiter for a given API type.
-
#throttle! ⇒ void
Blocks until the current request is allowed by the configured limits.
Constructor Details
#initialize(api_type) ⇒ RateLimiter
Creates a rate limiter for a given API type. Note: For proper rate limiting coordination, use RateLimiter.for(api_type) instead of RateLimiter.new(api_type) to get a shared instance.
36 37 38 39 40 41 42 43 44 45 |
# File 'lib/DhanHQ/rate_limiter.rb', line 36 def initialize(api_type) @api_type = api_type @buckets = Concurrent::Hash.new @buckets[:last_request_time] = Time.at(0) if api_type == :option_chain # Track request timestamps for per-second limiting @request_times = [] @window_start = Time.now initialize_buckets start_cleanup_threads end |
Class Attribute Details
.mutexes ⇒ Object (readonly)
Returns the value of attribute mutexes.
23 24 25 |
# File 'lib/DhanHQ/rate_limiter.rb', line 23 def mutexes @mutexes end |
.shared_limiters ⇒ Object (readonly)
Returns the value of attribute shared_limiters.
23 24 25 |
# File 'lib/DhanHQ/rate_limiter.rb', line 23 def shared_limiters @shared_limiters end |
Class Method Details
.for(api_type) ⇒ Object
Get or create a shared rate limiter instance for the given API type
26 27 28 |
# File 'lib/DhanHQ/rate_limiter.rb', line 26 def for(api_type) @shared_limiters[api_type] ||= new(api_type) end |
Instance Method Details
#throttle! ⇒ void
This method returns an undefined value.
Blocks until the current request is allowed by the configured limits.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/DhanHQ/rate_limiter.rb', line 50 def throttle! mutex.synchronize do if @api_type == :option_chain last_request_time = @buckets[:last_request_time] sleep_time = 3 - (Time.now - last_request_time) if sleep_time.positive? if ENV["DHAN_DEBUG"] == "true" puts "Sleeping for #{sleep_time.round(2)} seconds due to option_chain rate limit" end sleep(sleep_time) end @buckets[:last_request_time] = Time.now return end # For per-second limits, use timestamp-based sliding window per_second_limit = RATE_LIMITS[@api_type][:per_second] if per_second_limit && per_second_limit != Float::INFINITY now = Time.now # Remove requests older than 1 second @request_times.reject! { |t| now - t >= 1.0 } # Check if we've hit the per-second limit if @request_times.size >= per_second_limit # Calculate how long to wait until the oldest request is 1 second old oldest_time = @request_times.min wait_time = 1.0 - (now - oldest_time) if wait_time.positive? sleep(wait_time) # Recalculate after sleep now = Time.now @request_times.reject! { |t| now - t >= 1.0 } end end # Record this request time @request_times << Time.now end # Check other limits (per_minute, per_hour, per_day) loop do break if allow_request? sleep(0.1) end record_request end end |