Class: OctaSpace::Middleware::UrlRotator

Inherits:
Object
  • Object
show all
Defined in:
lib/octaspace/middleware/url_rotator.rb

Overview

Thread-safe round-robin URL rotator with automatic failover

When multiple base_urls are configured, requests are distributed across all healthy URLs. Failed URLs enter a cooldown period before being re-admitted to the rotation.

Examples:

rotator = OctaSpace::Middleware::UrlRotator.new([
  "https://api.octa.space",
  "https://api2.octa.space"
])

url = rotator.next_url
rotator.mark_failed(url)
rotator.stats # => { total: 2, available: 1, failed: ["https://api.octa.space"] }

Constant Summary collapse

FAILURE_COOLDOWN =

Seconds a failed URL is excluded from rotation

30

Instance Method Summary collapse

Constructor Details

#initialize(urls) ⇒ UrlRotator

Returns a new instance of UrlRotator.

Parameters:

  • urls (Array<String>)

    ordered list of API base URLs



25
26
27
28
29
30
# File 'lib/octaspace/middleware/url_rotator.rb', line 25

def initialize(urls)
  @urls = urls.dup.freeze
  @counter = 0
  @failed = {}   # url => Time failed_at
  @mutex = Mutex.new
end

Instance Method Details

#available_urlsArray<String>

Returns currently healthy URLs (excluding cooldown).

Returns:

  • (Array<String>)

    currently healthy URLs (excluding cooldown)



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/octaspace/middleware/url_rotator.rb', line 57

def available_urls
  now = Time.now
  @mutex.synchronize do
    @urls.reject do |url|
      failed_at = @failed[url]
      next false unless failed_at

      if now - failed_at < FAILURE_COOLDOWN
        true
      else
        @failed.delete(url)
        false
      end
    end
  end
end

#mark_failed(url) ⇒ Object

Mark a URL as temporarily failed

Parameters:

  • url (String)


46
47
48
# File 'lib/octaspace/middleware/url_rotator.rb', line 46

def mark_failed(url)
  @mutex.synchronize { @failed[url] = Time.now }
end

#mark_success(url) ⇒ Object

Mark a URL as recovered (remove from failed list)

Parameters:

  • url (String)


52
53
54
# File 'lib/octaspace/middleware/url_rotator.rb', line 52

def mark_success(url)
  @mutex.synchronize { @failed.delete(url) }
end

#next_urlString

Returns next available URL (round-robin), falls back to first if all failed.

Returns:

  • (String)

    next available URL (round-robin), falls back to first if all failed



33
34
35
36
37
38
39
40
41
42
# File 'lib/octaspace/middleware/url_rotator.rb', line 33

def next_url
  available = available_urls
  return @urls.first if available.empty?

  @mutex.synchronize do
    idx = @counter % available.size
    @counter += 1
    available[idx]
  end
end

#reset!Object

Reset state — useful in tests



84
85
86
87
88
89
# File 'lib/octaspace/middleware/url_rotator.rb', line 84

def reset!
  @mutex.synchronize do
    @counter = 0
    @failed.clear
  end
end

#statsHash

Returns diagnostic stats.

Returns:

  • (Hash)

    diagnostic stats



75
76
77
78
79
80
81
# File 'lib/octaspace/middleware/url_rotator.rb', line 75

def stats
  {
    total: @urls.size,
    available: available_urls.size,
    failed: @mutex.synchronize { @failed.keys }
  }
end