{"record":{"id":"0dd841bf5564e8b9","repo":"apache/druid","slug":"could-not-assign-role-s-to-user-s-due-to-con","errorCode":null,"errorMessage":"Could not assign role [%s] to user [%s] due to concurrent update contention.","messagePattern":"Could not assign role \\[(.+?)\\] to user \\[(.+?)\\] due to concurrent update contention\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java","lineNumber":783,"sourceCode":"  }\n\n  private void assignUserRoleInternal(String prefix, String userName, String roleName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (assignUserRoleOnce(prefix, userName, roleName)) {\n        return;\n      } else {\n        attempts++;\n      }\n      try {\n        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));\n      }\n      catch (InterruptedException ie) {\n        throw new RuntimeException(ie);\n      }\n    }\n    throw new ISE(\"Could not assign role [%s] to user [%s] due to concurrent update contention.\", roleName, userName);\n  }\n\n  private void unassignUserRoleInternal(String prefix, String userName, String roleName)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (unassignUserRoleOnce(prefix, userName, roleName)) {\n        return;\n      } else {\n        attempts++;\n      }\n      try {\n        Thread.sleep(ThreadLocalRandom.current().nextLong(UPDATE_RETRY_DELAY));\n      }\n      catch (InterruptedException ie) {\n        throw new RuntimeException(ie);\n      }\n    }","sourceCodeStart":765,"sourceCodeEnd":801,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-basic-security/src/main/java/org/apache/druid/security/basic/authorization/db/updater/CoordinatorBasicAuthorizerMetadataStorageUpdater.java#L765-L801","documentation":"Assigning a role to a user failed after exhausting numRetries compare-and-swap attempts because concurrent writers kept changing the user/role maps first. Each retry re-reads the user map, re-adds the role to the user's roles set, and attempts the CAS; persistent loss throws this ISE naming the role and user. It is a contention failure, not evidence that the user or role is invalid.","triggerScenarios":"Calling assignUserRole while other writers repeatedly mutate authorization metadata for the same prefix so every tryUpdateUserMap/role-map CAS fails through all retries.","commonSituations":"Bulk user onboarding scripts assigning roles in parallel; simultaneous role assignment and group mapping updates; multiple coordinators acting as writers.","solutions":["Retry assignUserRole after a delay.","Restrict basic-security metadata writes to a single leader/process.","Stagger or serialize bulk user-role assignment operations.","Raise numRetries for bursty concurrent update workloads.","Verify metadata storage latency and connectivity."],"exampleFix":"// before\nassignments.forEach(a -> client.assignUserRole(prefix, a.user, a.role));\n// after: serialized with retry\nassignments.forEach(a ->\n  RetryUtils.retry(() -> client.assignUserRole(prefix, a.user, a.role),\n                   e -> e instanceof IllegalStateException, MAX_ATTEMPTS));","handlingStrategy":"retry","validationCode":"// Verify user and role exist before assignment\nMap<String, BasicAuthorizerUser> users =\n    BasicAuthUtils.deserializeAuthorizerUserMap(mapper, getCurrentUserMapBytes(prefix));\nMap<String, BasicAuthorizerRole> roles =\n    BasicAuthUtils.deserializeAuthorizerRoleMap(mapper, getCurrentRoleMapBytes(prefix));\nif (users.get(userName) == null || roles.get(roleName) == null)\n  throw new IllegalArgumentException(\"user or role missing\");","typeGuard":null,"tryCatchPattern":"try {\n  updater.assignUserRole(prefix, userName, roleName);\n} catch (IJSE e) {\n  RetryUtils.retry(() -> updater.assignUserRole(prefix, userName, roleName),\n                   ex -> ex instanceof IllegalStateException, MAX_ATTEMPTS);\n}","preventionTips":["Stagger bulk user-role assignments","Only the leader writes metadata","Use randomized backoff on retries","Raise numRetries for concurrent workloads","Pre-validate user and role existence"],"tags":["druid","basic-security","zookeeper","concurrency"],"backgroundTag":"concurrent-update-contention","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"}