diff --git a/diagnostic/build-2b54872c.json b/diagnostic/build-2b54872c.json new file mode 100644 index 00000000..84a30192 --- /dev/null +++ b/diagnostic/build-2b54872c.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-22T05:06:42.079395+00:00", + "commit": "2b54872c", + "diagnostic_logd": null, + "diagnostic_logd_error": "bc664e44bba11102c467", + "chunked": false, + "chunk_size_bytes": null, + "password": null, + "decrypt_command": null, + "total_modules": 10, + "passed": 2, + "failed": 8, + "modules": [ + { + "name": "backend", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cargo'" + }, + { + "name": "frontend", + "status": "PASS", + "elapsed_seconds": 1.692, + "artifact": "/Users/qingfeng/Desktop/\u81ea\u52a8\u642c\u7816/zeroeye-9904099-parse-errors/frontend/dist", + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\nvite v6.4.3 building for production...\ntransforming...\n\u2713 100 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.62 kB \u2502 gzip: 0.34 kB\ndist/assets/state-BkjSKDbY.js 8.91 kB \u2502 gzip: 3.55 kB \u2502 map: 57.15 kB\ndist/assets/vendor-CREcWLHI.js 48.93 kB \u2502 gzip: 17.22 kB \u2502 map: 481.27 kB\ndist/assets/index-CyxcoTyU.js 231.32 kB \u2502 gzip: 72.02 kB \u2502 map: 1,044.42 kB\n\u2713 built in 516ms" + }, + { + "name": "market", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'go'" + }, + { + "name": "frailbox", + "status": "FAIL", + "elapsed_seconds": 0.031, + "artifact": null, + "output": "gcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/arena.c -o build/src/arena.o\nsrc/arena.c:17:23: error: use of undeclared identifier 'MAP_HUGETLB'\n 17 | mmap_flags |= MAP_HUGETLB;\n | ^\nsrc/arena.c:179:17: warning: comparison of distinct pointer types ('const void *' and 'char *') [-Wcompare-distinct-pointer-types]\n 179 | ptr < (char *)region->start + region->size) {\n | ~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n1 warning and 1 error generated.\nmake: *** [build/src/arena.o] Error 1" + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'cmake'" + }, + { + "name": "compliance", + "status": "FAIL", + "elapsed_seconds": 0.007, + "artifact": null, + "output": "The operation couldn\u2019t be completed. Unable to locate a Java Runtime.\nPlease visit http://www.java.com for information on installing Java." + }, + { + "name": "v2-market-stream", + "status": "PASS", + "elapsed_seconds": 0.038, + "artifact": null, + "output": "Syntax OK" + }, + { + "name": "nfc-scanner", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ghc'" + }, + { + "name": "openapi-tools", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'luac'" + } + ], + "pr_note": "Encrypted diagnostic logd artifact was not created; include this JSON report showing why. 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." +} diff --git a/v2/services/market_stream.rb b/v2/services/market_stream.rb index 6eeb63a4..94eaabd8 100644 --- a/v2/services/market_stream.rb +++ b/v2/services/market_stream.rb @@ -54,6 +54,7 @@ require 'redis' require 'sinatra/base' require 'logger' +require_relative 'market_stream_reconnect_backoff' # ===─ Fucking Constants =================================================================================─ @@ -216,10 +217,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 = MarketStreamReconnectBackoff.delay( + @reconnect_attempt, + base_delay: Constants::WS_RECONNECT_BASE, + max_delay: Constants::WS_RECONNECT_MAX + ) @reconnect_attempt += 1 diff --git a/v2/services/market_stream_reconnect_backoff.rb b/v2/services/market_stream_reconnect_backoff.rb new file mode 100644 index 00000000..e807dc7e --- /dev/null +++ b/v2/services/market_stream_reconnect_backoff.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module MarketStreamReconnectBackoff + module_function + + def delay(attempt, base_delay:, max_delay:, jitter: 0.0, random: Random::DEFAULT) + raise ArgumentError, 'attempt must be non-negative' if attempt.negative? + raise ArgumentError, 'base_delay must be positive' unless base_delay.positive? + raise ArgumentError, 'max_delay must be positive' unless max_delay.positive? + raise ArgumentError, 'jitter must be between 0.0 and 1.0' unless jitter.between?(0.0, 1.0) + + base = [base_delay * (2**attempt), max_delay].min + return normalize(base) if jitter.zero? + + offset = base * jitter * ((random.rand * 2.0) - 1.0) + normalize([[base + offset, 0.0].max, max_delay].min) + end + + def normalize(value) + value == value.to_i ? value.to_i : value + end +end diff --git a/v2/services/market_stream_reconnect_backoff_test.rb b/v2/services/market_stream_reconnect_backoff_test.rb new file mode 100644 index 00000000..f2f6c287 --- /dev/null +++ b/v2/services/market_stream_reconnect_backoff_test.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative 'market_stream_reconnect_backoff' + +class MarketStreamReconnectBackoffTest < 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, MarketStreamReconnectBackoff.delay(0, base_delay: 1, max_delay: 120) + assert_equal 5, MarketStreamReconnectBackoff.delay(0, base_delay: 5, max_delay: 120) + end + + def test_delay_grows_exponentially + assert_equal 2, MarketStreamReconnectBackoff.delay(1, base_delay: 1, max_delay: 120) + assert_equal 4, MarketStreamReconnectBackoff.delay(2, base_delay: 1, max_delay: 120) + assert_equal 64, MarketStreamReconnectBackoff.delay(6, base_delay: 1, max_delay: 120) + end + + def test_delay_is_capped_at_maximum + assert_equal 120, MarketStreamReconnectBackoff.delay(7, base_delay: 1, max_delay: 120) + assert_equal 120, MarketStreamReconnectBackoff.delay(20, base_delay: 1, max_delay: 120) + end + + def test_jitter_stays_within_symmetric_bounds + low = MarketStreamReconnectBackoff.delay( + 0, + base_delay: 10, + max_delay: 120, + jitter: 0.2, + random: FixedRandom.new(0.0) + ) + high = MarketStreamReconnectBackoff.delay( + 0, + base_delay: 10, + max_delay: 120, + jitter: 0.2, + random: FixedRandom.new(1.0) + ) + + assert_in_delta 8.0, low, 0.0001 + assert_in_delta 12.0, high, 0.0001 + end + + def test_jitter_never_exceeds_maximum_cap + delay = MarketStreamReconnectBackoff.delay( + 4, + base_delay: 10, + max_delay: 50, + jitter: 0.5, + random: FixedRandom.new(1.0) + ) + + assert_equal 50, delay + end +end