{"record":{"id":"3a58ec7ed31d67cf","repo":"apache/druid","slug":"could-not-set-permissions-for-role-s-due-to-con","errorCode":null,"errorMessage":"Could not set permissions for role [%s] due to concurrent update contention.","messagePattern":"Could not set permissions for role \\[(.+?)\\] 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":864,"sourceCode":"  }\n\n  private void setPermissionsInternal(String prefix, String roleName, List<ResourceAction> permissions)\n  {\n    int attempts = 0;\n    while (attempts < numRetries) {\n      if (setPermissionsOnce(prefix, roleName, permissions)) {\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 set permissions for role [%s] due to concurrent update contention.\", roleName);\n  }\n\n  private boolean deleteUserOnce(String prefix, String userName)\n  {\n    byte[] oldValue = getCurrentUserMapBytes(prefix);\n    Map<String, BasicAuthorizerUser> userMap = BasicAuthUtils.deserializeAuthorizerUserMap(objectMapper, oldValue);\n    if (userMap.get(userName) == null) {\n      throw new BasicSecurityDBResourceException(\"User [%s] does not exist.\", userName);\n    } else {\n      userMap.remove(userName);\n    }\n    byte[] newValue = BasicAuthUtils.serializeAuthorizerUserMap(objectMapper, userMap);\n    return tryUpdateUserMap(prefix, userMap, oldValue, newValue);\n  }\n\n  private boolean createUserOnce(String prefix, String userName)\n  {\n    byte[] oldValue = getCurrentUserMapBytes(prefix);","sourceCodeStart":846,"sourceCodeEnd":882,"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#L846-L882","documentation":"Setting permissions for a basic authorizer role failed after all numRetries compare-and-swap attempts were beaten by concurrent writers of the role metadata. The updater re-reads the role, re-applies the permission list, and retries with randomized delay before throwing this ISE naming the role. It signals persistent contention on the authorizer metadata store.","triggerScenarios":"Calling setPermissions while other writers repeatedly update roles/users/group mappings for the same authorizer prefix so every tryUpdateGroupMappingMap CAS fails through all retries.","commonSituations":"Bulk permission reconfiguration run in parallel across workers; permission updates racing with user-role assignments; multiple coordinators writing concurrently.","solutions":["Retry setPermissions after a delay.","Restrict metadata writes to the elected coordinator leader.","Serialize or batch permission updates.","Increase numRetries for workloads with frequent concurrent updates.","Check metadata storage latency and load."],"exampleFix":"// before\nroles.forEach(r -> client.setPermissions(prefix, r, perms));\n// after: serialized with retry\nfor (final String r : roles) {\n  RetryUtils.retry(() -> client.setPermissions(prefix, r, perms),\n                   e -> e instanceof IllegalStateException, MAX_ATTEMPTS);\n}","handlingStrategy":"retry","validationCode":"// Confirm role exists before setting permissions\nMap<String, BasicAuthorizerRole> roles =\n    BasicAuthUtils.deserializeAuthorizerRoleMap(mapper, getCurrentRoleMapBytes(prefix));\nif (!roles.containsKey(roleName)) throw new IllegalArgumentException(\"role missing\");","typeGuard":null,"tryCatchPattern":"try {\n  updater.setPermissions(prefix, roleName, permissions);\n} catch (IJSE e) {\n  RetryUtils.retry(() -> updater.setPermissions(prefix, roleName, permissions),\n                   ex -> ex instanceof IllegalStateException, MAX_ATTEMPTS);\n}","preventionTips":["Batch/serialize permission updates","Leader-only metadata writes","Randomized backoff on retries","Raise numRetries for concurrent admin traffic","Track metadata store latency"],"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-17T15:17:12.973Z"}