From c9fe4ca50ac5ade419b116df720f8aa3cff52b20 Mon Sep 17 00:00:00 2001 From: Guillaume ARM Date: Mon, 15 Jun 2026 18:31:42 +0200 Subject: [PATCH] fix: less verbose sandbox daemon --- apis/libsandbox.lua | 34 +++++++++++++++++++----- manifest.json | 2 +- packages/index.json | 4 +-- packages/trapos-sandbox/ccpm.json | 2 +- packages/trapos/ccpm.json | 2 +- tests/sandbox.lua | 43 +++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 11 deletions(-) diff --git a/apis/libsandbox.lua b/apis/libsandbox.lua index 3c0a644..0e8d3e1 100644 --- a/apis/libsandbox.lua +++ b/apis/libsandbox.lua @@ -201,6 +201,10 @@ local function createSandbox() local state = 'connecting'; local lastError = nil; local activeWs = nil; + -- True while looping on retries. Gates the per-attempt chatter (connecting / socket + -- open / connection failed / connection closed) so a long outage logs once on entry + -- and once on recovery instead of flooding the file every reconnectDelay seconds. + local reconnecting = false; local function sendFrame(frame) if not activeWs then @@ -218,7 +222,9 @@ local function createSandbox() local function connect() state = 'connecting'; - log('info', 'connecting', { url = url }); + if not reconnecting then + log('info', 'connecting', { url = url }); + end httpLike.websocketAsync(url); end @@ -228,7 +234,12 @@ local function createSandbox() return; end state = 'connecting'; - log('info', 'reconnect scheduled', { delaySeconds = reconnectDelay }); + -- One line on the false->true transition, then silence: every later retry runs + -- through here too, but we keep trying without re-logging. + if not reconnecting then + reconnecting = true; + log('warn', 'reconnect loop started', { delaySeconds = reconnectDelay }); + end el.setTimeout(connect, reconnectDelay); end @@ -238,7 +249,12 @@ local function createSandbox() end if ok then state = 'ready'; - log('info', 'connected', { traposId = traposId }); + if reconnecting then + reconnecting = false; + log('info', 'reconnect loop ended; reconnected', { traposId = traposId }); + else + log('info', 'connected', { traposId = traposId }); + end queueEvent('trapos_sandbox_connected', traposId); else state = 'unauthorized'; @@ -252,7 +268,9 @@ local function createSandbox() if eventUrl ~= url then return; end activeWs = ws; state = 'awaiting_hello'; - log('info', 'socket open; sending hello', { traposId = traposId }); + if not reconnecting then + log('info', 'socket open; sending hello', { traposId = traposId }); + end sendFrame(api.buildHello(traposId, secret, makeUuid(opts.random))); -- hello-ack watchdog: state-guarded, no stored cancel. If hello already -- resolved this is a harmless no-op; closing triggers reconnect. @@ -276,7 +294,9 @@ local function createSandbox() el.register('websocket_failure', function(eventUrl) if eventUrl ~= url then return; end - log('warn', 'connection failed', { url = url }); + if not reconnecting then + log('warn', 'connection failed', { url = url }); + end scheduleReconnect(); end); @@ -300,7 +320,9 @@ local function createSandbox() end return; end - log('info', 'connection closed', { code = code, reason = reason }); + if not reconnecting then + log('info', 'connection closed', { code = code, reason = reason }); + end scheduleReconnect(); end); diff --git a/manifest.json b/manifest.json index a7d4b5d..7e0a555 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "TrapOS", - "version": "0.9.3", + "version": "0.9.4", "branch": "master", "packages": [ "trapos" diff --git a/packages/index.json b/packages/index.json index df8bbdf..f80d9ab 100644 --- a/packages/index.json +++ b/packages/index.json @@ -6,8 +6,8 @@ "trapos-net": "0.3.0", "trapos-ui": "0.2.2", "trapos-ai": "0.7.0", - "trapos-sandbox": "0.2.5", + "trapos-sandbox": "0.2.6", "trapos-sandbox-legacy": "0.2.2", - "trapos": "0.9.3" + "trapos": "0.9.4" } } diff --git a/packages/trapos-sandbox/ccpm.json b/packages/trapos-sandbox/ccpm.json index dd7159a..543e6dc 100644 --- a/packages/trapos-sandbox/ccpm.json +++ b/packages/trapos-sandbox/ccpm.json @@ -1,6 +1,6 @@ { "name": "trapos-sandbox", - "version": "0.2.5", + "version": "0.2.6", "description": "TrapOS sandbox gateway client: WS daemon (servers/sandbox) + sandbox command", "dependencies": ["trapos-core"], "files": [ diff --git a/packages/trapos/ccpm.json b/packages/trapos/ccpm.json index d5cf4eb..7744e26 100644 --- a/packages/trapos/ccpm.json +++ b/packages/trapos/ccpm.json @@ -1,6 +1,6 @@ { "name": "trapos", - "version": "0.9.3", + "version": "0.9.4", "description": "TrapOS full install meta-package", "dependencies": [ "trapos-boot", diff --git a/tests/sandbox.lua b/tests/sandbox.lua index 28dacdc..e3b0246 100644 --- a/tests/sandbox.lua +++ b/tests/sandbox.lua @@ -234,6 +234,49 @@ testlib.test('startSession reconnects on an abnormal close with a non-displaced testlib.assertEquals(#ctx.http.connects, 2); end); +testlib.test('startSession logs the reconnect loop once on entry and once on recovery', function() + local logs = {}; + local function countMsg(msg) + local n = 0; + for _, entry in ipairs(logs) do + if entry.msg == msg then n = n + 1; end + end + return n; + end + + local ctx = startSession({ + log = function(level, msg, fields) logs[#logs + 1] = { level = level, msg = msg, fields = fields }; end, + }); + + -- Healthy connect: no reconnect-loop logs yet. + ctx.el.fire('websocket_success', ctx.url, fakes.fakeWs()); + ctx.el.fire('websocket_message', ctx.url, encode({ event = 'gateway_response', type = 'hello', ok = true, payload = {} })); + testlib.assertEquals(ctx.session.isReady(), true); + testlib.assertEquals(countMsg('reconnect loop started'), 0); + + -- Drop, then fail the retry several times: the loop is announced exactly once and the + -- per-attempt chatter (connecting / connection failed) does not repeat while looping. + ctx.el.fire('websocket_closed', ctx.url); + local connectingBefore = countMsg('connecting'); + for _ = 1, 3 do + ctx.el.fireTimer(#ctx.el.timers); -- reconnect timer -> connect() + ctx.el.fire('websocket_failure', ctx.url); -- retry fails, schedules the next timer + end + testlib.assertEquals(countMsg('reconnect loop started'), 1); + testlib.assertEquals(countMsg('connection closed'), 1); -- the first drop logged, not the retries + testlib.assertEquals(countMsg('connecting'), connectingBefore); -- suppressed while looping + testlib.assertEquals(countMsg('connection failed'), 0); -- suppressed while looping + + -- Recovery: a single "reconnected" line, no second "loop started". + ctx.el.fireTimer(#ctx.el.timers); -- reconnect timer -> connect() + ctx.el.fire('websocket_success', ctx.url, fakes.fakeWs()); + ctx.el.fire('websocket_message', ctx.url, encode({ event = 'gateway_response', type = 'hello', ok = true, payload = {} })); + + testlib.assertEquals(ctx.session.isReady(), true); + testlib.assertEquals(countMsg('reconnect loop ended; reconnected'), 1); + testlib.assertEquals(countMsg('reconnect loop started'), 1); +end); + testlib.test('startSession treats a displacement close (4000) as terminal', function() local ctx = startSession(); local ws = fakes.fakeWs();