{"record":{"id":"f6e05e438749add4","repo":"iflytek/astron-agent","slug":"executeshutdown-unlock-threw-exception-key-tokentail","errorCode":null,"errorMessage":"ExecuteShutdown unlock threw exception. key={}, tokenTail=***{}","messagePattern":"ExecuteShutdown unlock threw exception\\. key=(.+?), tokenTail=\\*\\*\\*(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/config/spring/ExecuteShutdown.java","lineNumber":64,"sourceCode":"\n        if (!redisUtil.tryLock(LOCK_KEY, LOCK_TTL, token)) {\n            log.info(\"ExecuteShutdown skipped: lock already held by another instance. key={}\", LOCK_KEY);\n            return;\n        }\n\n        try {\n            // Actual shutdown action: Clear canvas hold count\n            workflowService.removeAllCanvasHold();\n            log.info(\"ExecuteShutdown done: removeAllCanvasHold finished.\");\n        } catch (Exception e) {\n            // Do not block shutdown, but need complete logging\n            log.error(\"ExecuteShutdown failed while removing canvas hold.\", e);\n        } finally {\n            boolean released = false;\n            try {\n                released = redisUtil.unlock(LOCK_KEY, token);\n            } catch (Exception e) {\n                log.warn(\"ExecuteShutdown unlock threw exception. key={}, tokenTail=***{}\", LOCK_KEY, tail4(token), e);\n            }\n            if (!released) {\n                log.warn(\"ExecuteShutdown unlock not released (maybe expired or token mismatch). key={}, tokenTail=***{}\",\n                        LOCK_KEY, tail4(token));\n            } else {\n                log.debug(\"ExecuteShutdown lock released. key={}\", LOCK_KEY);\n            }\n        }\n    }\n\n    private boolean shouldSkipByProfile() {\n        final String[] actives = environment.getActiveProfiles();\n        if (actives == null || actives.length == 0) {\n            return false;\n        }\n        for (String p : actives) {\n            for (String skip : SKIP_PROFILES) {\n                if (skip.equalsIgnoreCase(p)) {","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/config/spring/ExecuteShutdown.java#L46-L82","documentation":"In ExecuteShutdown.onShutdown, the distributed Redis lock held by the shutdown hook is released via redisUtil.unlock(LOCK_KEY, token). If the unlock call itself throws (Redis connection down at JVM shutdown, serialization issue, etc.), this warning is logged with the key and the last-4 chars of the token, and the lock is left to expire via its TTL rather than being released. The shutdown still completes.","triggerScenarios":"Redis is unreachable or times out when the shutdown hook runs; redisUtil.unlock throws (JedisConnectionException, pool exhaustion) inside the finally block of onShutdown.","commonSituations":"Kubernetes pod termination during a Redis failover; Redis connection pool already closed by earlier shutdown-hook ordering; network partition between app and Redis at SIGTERM.","solutions":["Rely on the lock's TTL: since unlock failed the lock will expire on its own; verify the LOCK_KEY TTL is short enough that stale holds don't block the next ExecuteShutdown run.","Check why Redis was unavailable at shutdown — connection pool shutdown ordering in Spring context (add a DependsOn / close the Redis client after this hook), or network policy.","Make unlock retry once with a short backoff inside the hook, or use Redisson's lock.unlock() (idempotent, with built-in Lua token check) instead of a hand-rolled unlock."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!redisUtil.hasKey(LOCK_KEY)) {\n    log.info(\"lock already gone; skip unlock\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    if (!redisUtil.unlock(LOCK_KEY, token)) {\n        log.warn(\"unlock rejected; lock will expire via TTL, key={}\", LOCK_KEY);\n    }\n} catch (Exception e) {\n    log.warn(\"unlock failed at shutdown; relying on TTL, key={}\", LOCK_KEY, e);\n}","preventionTips":["Always set a TTL on shutdown locks so failed unlocks self-heal.","Order Spring bean destruction so the Redis connection factory closes after shutdown hooks run.","Monitor this warning rate; a spike means Redis connectivity issues during deployments."],"tags":["redis","distributed-lock","shutdown","unlock"],"backgroundTag":"connection-refused","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}