Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class Module:
name="v2-market-stream",
language="Ruby",
dir=ROOT / "v2" / "services",
build_cmd=["ruby", "-c", "market_stream.rb"],
build_cmd=["ruby", "market_stream_test_runner.rb"],
clean_cmd=["echo", "Ruby has no build artifacts to clean"],
build_dir=None,
),
Expand Down
23 changes: 0 additions & 23 deletions diagnostic/build-00000000.json

This file was deleted.

1 change: 0 additions & 1 deletion diagnostic/build-00000000.logd

This file was deleted.

Binary file added diagnostic/build-6c985129-part001.logd
Binary file not shown.
Binary file added diagnostic/build-6c985129-part002.logd
Binary file not shown.
26 changes: 26 additions & 0 deletions diagnostic/build-6c985129.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"generated_at": "2026-06-20T15:53:17.144005+00:00",
"commit": "6c985129",
"diagnostic_logd": [
"diagnostic\\build-6c985129-part001.logd",
"diagnostic\\build-6c985129-part002.logd"
],
"diagnostic_logd_error": null,
"chunked": true,
"chunk_size_bytes": 41943040,
"password": "551a715c6ecfe495fc90",
"decrypt_command": "encryptly unpack diagnostic\\build-6c985129.logd <outdir> --password 551a715c6ecfe495fc90",
"total_modules": 1,
"passed": 0,
"failed": 1,
"modules": [
{
"name": "v2-market-stream",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] \u7cfb\u7edf\u627e\u4e0d\u5230\u6307\u5b9a\u7684\u6587\u4ef6\u3002"
}
],
"pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-6c985129-part001.logd, diagnostic\\build-6c985129-part002.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
}
11 changes: 7 additions & 4 deletions v2/services/market_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@
require 'sinatra/base'
require 'logger'

require_relative 'market_stream_backoff'

# ===─ Fucking Constants =================================================================================─

V2_VERSION = '2.0.0'
Expand Down Expand Up @@ -216,10 +218,11 @@ def schedule_reconnect
# v2 reconnection: exponential backoff with max. We learned. We grew.
return if Constants::WS_MAX_RECONNECTS && @reconnect_attempt >= Constants::WS_MAX_RECONNECTS

delay = [
Constants::WS_RECONNECT_BASE * (2 ** @reconnect_attempt),
Constants::WS_RECONNECT_MAX
].min
delay = MarketStreamBackoff.delay_for(
@reconnect_attempt,
base: Constants::WS_RECONNECT_BASE,
max: Constants::WS_RECONNECT_MAX
)

@reconnect_attempt += 1

Expand Down
23 changes: 23 additions & 0 deletions v2/services/market_stream_backoff.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module MarketStreamBackoff
module_function

def delay_for(attempt, base: 1, max: 120, jitter: 0.0, rng: Random::DEFAULT)
raise ArgumentError, 'attempt must be non-negative' if attempt.negative?
raise ArgumentError, 'base must be positive' unless base.positive?
raise ArgumentError, 'max must be positive' unless max.positive?
raise ArgumentError, 'jitter must be between 0.0 and 1.0' unless jitter.between?(0.0, 1.0)

capped_delay = [base.to_f * (2**attempt), max.to_f].min
return normalized(capped_delay) if jitter.zero?

spread = capped_delay * jitter
jittered_delay = capped_delay + (((rng.rand * 2.0) - 1.0) * spread)
normalized([[jittered_delay, 0.0].max, max.to_f].min)
end

def normalized(value)
value == value.to_i ? value.to_i : value
end
end
46 changes: 46 additions & 0 deletions v2/services/market_stream_backoff_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'minitest/autorun'
require_relative 'market_stream_backoff'

class MarketStreamBackoffTest < Minitest::Test
class FixedRandom
def initialize(*values)
@values = values
end

def rand
@values.shift || 0.5
end
end

def test_initial_delay_uses_base_delay
assert_equal 1, MarketStreamBackoff.delay_for(0, base: 1, max: 120)
end

def test_delay_grows_exponentially
assert_equal 2, MarketStreamBackoff.delay_for(1, base: 1, max: 120)
assert_equal 64, MarketStreamBackoff.delay_for(6, base: 1, max: 120)
end

def test_delay_is_capped_at_maximum
assert_equal 120, MarketStreamBackoff.delay_for(7, base: 1, max: 120)
assert_equal 120, MarketStreamBackoff.delay_for(20, base: 1, max: 120)
end

def test_jitter_respects_lower_and_upper_bounds_before_cap
assert_in_delta 8.0, MarketStreamBackoff.delay_for(0, base: 10, max: 100, jitter: 0.2, rng: FixedRandom.new(0.0)), 0.0001
assert_in_delta 12.0, MarketStreamBackoff.delay_for(0, base: 10, max: 100, jitter: 0.2, rng: FixedRandom.new(1.0)), 0.0001
end

def test_jitter_never_exceeds_maximum_cap
assert_equal 50, MarketStreamBackoff.delay_for(4, base: 10, max: 50, jitter: 0.5, rng: FixedRandom.new(1.0))
end

def test_invalid_parameters_are_rejected
assert_raises(ArgumentError) { MarketStreamBackoff.delay_for(-1) }
assert_raises(ArgumentError) { MarketStreamBackoff.delay_for(0, base: 0) }
assert_raises(ArgumentError) { MarketStreamBackoff.delay_for(0, max: 0) }
assert_raises(ArgumentError) { MarketStreamBackoff.delay_for(0, jitter: 1.1) }
end
end
11 changes: 11 additions & 0 deletions v2/services/market_stream_test_runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require 'rbconfig'

service_path = File.expand_path('market_stream.rb', __dir__)
unless system(RbConfig.ruby, '-c', service_path)
warn 'market_stream.rb syntax check failed'
exit 1
end

require_relative 'market_stream_backoff_test'