添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement . We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account Credentials: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) #11995 Credentials: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) #11995 siropkin opened this issue May 15, 2022 · 3 comments

Describe the bug

If you will try to remove a few credentials (ex, OTP devices) by REST API in parallel mode at one time you will hit an error: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) .

To reproduce this error you need to create more than one OTP devices for a user and then remove these devices by REST API in parallel mode.
In 'parallel mode' means to do something like this:

  await Promise.all(credentials.map(async (credential) => {
    await deleteCredential(credential.id);
  }));

Here is REST API calls example for OTP device removing:

// Get OTP devices
[2022-05-15T06:25:51.444Z] [LOG]   GET <realm>/users/95c1c0b0-7e46-4fe8-a065-11876160509a/credentials
[2022-05-15T06:25:52.406Z] [LOG]   ------------otp devices before delete------------
[2022-05-15T06:25:52.407Z] [LOG]   [
    id: '45d876ff-3127-43db-9634-41471511fd30',
    type: 'otp',
    userLabel: 'mobile',
    createdDate: 1652595739419,
    credentialData: '{"subType":"totp","digits":6,"counter":0,"period":30,"algorithm":"HmacSHA1"}'
    id: '147a59db-f343-4393-97ef-c5c388dafa40',
    type: 'otp',
    userLabel: 'mobile2',
    createdDate: 1652595834311,
    credentialData: '{"subType":"totp","digits":6,"counter":0,"period":30,"algorithm":"HmacSHA1"}'
[2022-05-15T06:25:52.407Z] [LOG]   ------------otp devices before delete------------
// Remove OTP devices in parallel
[2022-05-15T06:25:52.408Z] [ERROR] deleting otp device 45d876ff-3127-43db-9634-41471511fd30
[2022-05-15T06:25:52.408Z] [LOG]   DELETE <realm>/users/95c1c0b0-7e46-4fe8-a065-11876160509a/credentials/45d876ff-3127-43db-9634-41471511fd30
[2022-05-15T06:25:52.409Z] [ERROR] deleting otp device 147a59db-f343-4393-97ef-c5c388dafa40
[2022-05-15T06:25:52.409Z] [LOG]   DELETE <realm>/users/95c1c0b0-7e46-4fe8-a065-11876160509a/credentials/147a59db-f343-4393-97ef-c5c388dafa40
[2022-05-15T06:25:52.943Z] [ERROR] error deleting otp device 45d876ff-3127-43db-9634-41471511fd30: Request failed with status code 500 // << see KC event log below
// Get OTP devices again (as you see - one device is still there)
[2022-05-15T06:25:51.444Z] [LOG]   GET <realm>/users/95c1c0b0-7e46-4fe8-a065-11876160509a/credentials
[2022-05-15T06:25:52.944Z] [LOG]   ------------otp devices after delete------------
[2022-05-15T06:25:53.417Z] [LOG]   [
    id: '45d876ff-3127-43db-9634-41471511fd30',
    type: 'otp',
    userLabel: 'mobile',
    createdDate: 1652595739419,
    credentialData: '{"subType":"totp","digits":6,"counter":0,"period":30,"algorithm":"HmacSHA1"}'
[2022-05-15T06:25:53.417Z] [LOG]   ------------otp devices after delete------------

This is Keycloack event log:

org.keycloak.models.ModelException: javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.keycloak.models.jpa.entities.CredentialEntity#147a59db-f343-4393-97ef-c5c388dafa40] 
at org.keycloak.connections.jpa.PersistenceExceptionConverter.convert(PersistenceExceptionConverter.java:84) 
at org.keycloak.connections.jpa.PersistenceExceptionConverter.invoke(PersistenceExceptionConverter.java:62) 
at com.sun.proxy.$Proxy94.flush(Unknown Source) 
at org.keycloak.models.jpa.JpaUserCredentialStore.removeCredentialEntity(JpaUserCredentialStore.java:175) 
at org.keycloak.models.jpa.JpaUserProvider.removeStoredCredential(JpaUserProvider.java:953) 
at org.keycloak.credential.UserCredentialStoreManager.removeStoredCredential(UserCredentialStoreManager.java:74) 
at org.keycloak.services.resources.admin.UserResource.removeCredential(UserResource.java:645) 
at sun.reflect.GeneratedMethodAccessor2063.invoke(Unknown Source) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
at java.lang.reflect.Method.invoke(Method.java:498) 
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:138) 
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:546) 
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:435) 
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:396) 
at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358) 
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:398) 
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:150) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141) 
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:104) 
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:440) 
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229) 
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135) 
at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358) 
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138) 
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215) 
at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245) 
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61) 
at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) 
at javax.servlet.http.HttpServlet.service(HttpServlet.java:590) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) 
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) 
at org.keycloak.provider.wildfly.WildFlyRequestFilter.lambda$doFilter$0(WildFlyRequestFilter.java:41) 
at org.keycloak.services.filters.AbstractRequestFilter.filter(AbstractRequestFilter.java:43) 
at org.keycloak.provider.wildfly.WildFlyRequestFilter.doFilter(WildFlyRequestFilter.java:39) 
at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) 
at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) 
at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) 
at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) 
at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) 
at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) 
at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78) 
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) 
at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) 
at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) 
at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) 
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) 
at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) 
at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) 
at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) 
at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) 
at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50) 
at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) 
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) 
at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61) 
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) 
at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68) 
at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) 
at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) 
at io.undertow.server.handlers.MetricsHandler.handleRequest(MetricsHandler.java:64) 
at io.undertow.servlet.core.MetricsChainHandler.handleRequest(MetricsChainHandler.java:59) 
at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) 
at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) 
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) 
at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) 
at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) 
at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) 
at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105) 
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530) 
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530) 
at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) 
at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) 
at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) 
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387) 
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:841) 
at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) 
at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1990) 
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486) 
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377) 
at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280) 
at java.lang.Thread.run(Thread.java:748) Caused by: javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.keycloak.models.jpa.entities.CredentialEntity#147a59db-f343-4393-97ef-c5c388dafa40] 
at org.hibernate.internal.ExceptionConverterImpl.wrapStaleStateException(ExceptionConverterImpl.java:226) 
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:93) 
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181) 
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188) 
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1478) 
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1458) 
at sun.reflect.GeneratedMethodAccessor953.invoke(Unknown Source) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
at java.lang.reflect.Method.invoke(Method.java:498) 
at org.keycloak.connections.jpa.PersistenceExceptionConverter.invoke(PersistenceExceptionConverter.java:60) 

Version

13.0.1

Expected behavior

No response

Actual behavior

No response

How to Reproduce?

  • Add more than one OTP device to a user.
  • Try to remove them all in parallel by REST API.
  • Anything else?

    A workaround is to remove credentials in sequence mode:

      for (credentialdevice of credentials) {
        await deleteCredential(credential.id);
      }));

    Thanks for the report. I am adding label "Help wanted" as Keycloak team won't have time to look at this in the near future. It would need to be community contribution to improve this. See "Contributors" section under https://www.keycloak.org/community for more details.

    Another thing is, that this bug is probably specific to old store and hence it may automatically disappear once Keycloak is switched to the new storage by default (Probably somewhen in 2023).

    Due to the amount of issues reported by the community we are not able to prioritise resolving this issue at the moment.

    If you are affected by this issue, upvote it by adding a 👍 to the description. We would also welcome a contribution to fix the issue.