{"record":{"id":"a8c2fd0c973566a2","repo":"apache/druid","slug":"lock-key-s-has-no-session-owner","errorCode":null,"errorMessage":"Lock key [%s] has no session owner","messagePattern":"Lock key \\[(.+?)\\] has no session owner","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"extensions-contrib/consul-extensions/src/main/java/org/apache/druid/consul/discovery/ConsulLeaderSelector.java","lineNumber":511,"sourceCode":"      }\n    }\n  }\n\n  private boolean validateLockOwnership(String expectedSessionId)\n  {\n    try {\n      Response<GetValue> response = consulClient.getKVValue(\n          lockKey,\n          config.getAuth().getAclToken(),\n          buildQueryParams()\n      );\n      if (response == null || response.getValue() == null) {\n        LOGGER.warn(\"Lock key [%s] missing when validating ownership\", lockKey);\n        return false;\n      }\n      String actualSessionId = response.getValue().getSession();\n      if (actualSessionId == null) {\n        LOGGER.warn(\"Lock key [%s] has no session owner\", lockKey);\n        return false;\n      }\n      boolean matches = expectedSessionId.equals(actualSessionId);\n      if (!matches) {\n        LOGGER.warn(\n            \"Lock key [%s] owned by session [%s], expected [%s]\",\n            lockKey,\n            actualSessionId,\n            expectedSessionId\n        );\n      }\n      return matches;\n    }\n    catch (Exception e) {\n      LOGGER.error(e, \"Failed to validate lock ownership for [%s]\", lockKey);\n      return false;\n    }\n  }","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-contrib/consul-extensions/src/main/java/org/apache/druid/consul/discovery/ConsulLeaderSelector.java#L493-L529","documentation":"This warning is logged by ConsulLeaderSelector.validateLockOwnership when the Consul KV lock key exists but carries no Consul session attached (the 'Session' field of the KV entry is null). It means the distributed leadership lock has been orphaned — the session that created it expired or was destroyed but the key was not deleted — so leadership cannot be validated or assumed. validateLockOwnership returns false, causing leaderElectionLoop/becomeLeader to abort promotion and emit an ownership-mismatch metric.","triggerScenarios":"leaderElectionLoop or becomeLeader calls validateLockOwnership(currentSession); consulClient.getKVValue(lockKey, ...) returns a GetValue whose getSession() is null — e.g. the session TTL expired while the lock key remained in Consul, or the key was created/written manually without a session.","commonSituations":"Consul session invalidation after a network partition or long GC pause exceeding the lock-ttl; another operator or script writing the lock key manually (e.g. via consul kv put) without acquiring a session; Consul restart with session persistence issues; clock/TTL misconfiguration causing sessions to expire faster than the election loop renews them.","solutions":["Verify the lock key in Consul (consul kv get <lockKey>) — if its Session is empty, delete the orphaned key (consul kv delete <lockKey>) so the next election loop recreates it with a valid session","Check Consul server health and session TTL configuration; ensure lock-ttl is comfortably larger than the election/renew interval so sessions don't lapse","Restart the Druid node running ConsulLeaderSelector so it creates a fresh session and reacquires the lock","Confirm no external process or human is writing to the lock key path; restrict ACL token write access to that KV prefix"],"exampleFix":"// before (shell inspection / manual repair)\n$ consul kv get -detailed druid/leader-lock   # Session: (empty)\n// after\n$ consul kv delete druid/leader-lock          # let the election loop recreate with a session","handlingStrategy":"retry","validationCode":"// before calling becomeLeader / after TTL expiries, check key ownership in Consul:\nResponse<GetValue> r = consulClient.getKVValue(lockKey, aclToken, qp);\nboolean orphaned = r != null && r.getValue() != null && r.getValue().getSession() == null;\nif (orphaned) { consulClient.deleteKVValue(lockKey, aclToken); }","typeGuard":"static boolean hasSessionOwner(GetValue v) { return v != null && v.getSession() != null; }","tryCatchPattern":"// validateLockOwnership already swallows exceptions; guard at the election-loop level:\nif (!validateLockOwnership(sessionId)) {\n  emitOwnershipMismatchMetric();\n  // back off and retry next election cycle instead of promoting\n  Thread.sleep(retryIntervalMs);\n  continue;\n}","preventionTips":["Set lock-ttl well above the session renew interval so sessions never lapse while the key persists","Monitor the consul/leader/ownership_mismatch metric and alert on sustained occurrences","Prevent manual/external writes to the lock KV path via ACL token restrictions","Watch for long GC pauses or network partitions that exceed session TTL"],"tags":["consul","distributed-lock","leader-election","session-expired"],"backgroundTag":"invalid-state-transition","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}