Sort order of updates for user properties (#32853)
This should reduce deadlocks on the user property table if the users are updated concurrently. Closes #32852 Signed-off-by: Alexander Schwartz <aschwart@redhat.com>
This commit is contained in:
parent
8ef7007e3c
commit
2a95d0abfa
7 changed files with 18 additions and 16 deletions
|
@ -18,15 +18,10 @@
|
|||
package org.keycloak.storage.ldap.mappers;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.ldap.LDAPConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO: Possibly add "priority" instead of hardcoding behaviour
|
||||
|
@ -67,7 +62,7 @@ public class LDAPMappersComparator {
|
|||
if (isO2AttrMapper) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
return compareWithStableOrdering(o1, o2);
|
||||
}
|
||||
} else if (!isO2AttrMapper) {
|
||||
return -1;
|
||||
|
@ -82,7 +77,7 @@ public class LDAPMappersComparator {
|
|||
if (isO2UsernameMapper) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
return compareWithStableOrdering(o1, o2);
|
||||
}
|
||||
} else if (!isO2UsernameMapper) {
|
||||
return -1;
|
||||
|
@ -98,13 +93,21 @@ public class LDAPMappersComparator {
|
|||
if (isO2LdapAttr) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
return compareWithStableOrdering(o1, o2);
|
||||
}
|
||||
} else if (!isO2LdapAttr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return compareWithStableOrdering(o1, o2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure a stable ordering, so the mappers are always executed in the same order.
|
||||
* This can avoid database deadlocks as the mappers will modify attributes always in the same order.
|
||||
*/
|
||||
private static int compareWithStableOrdering(ComponentModel o1, ComponentModel o2) {
|
||||
return o1.getId().compareTo(o2.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -459,7 +459,7 @@ public class JpaIdentityProviderStorageProvider implements IdentityProviderStora
|
|||
builder.equal(mapper.get("realmId"), getRealm().getId()),
|
||||
builder.equal(mapper.get("identityProviderAlias"), identityProviderAlias));
|
||||
|
||||
TypedQuery<IdentityProviderMapperEntity> typedQuery = em.createQuery(query.select(mapper).where(predicate));
|
||||
TypedQuery<IdentityProviderMapperEntity> typedQuery = em.createQuery(query.select(mapper).where(predicate).orderBy(builder.asc(mapper.get("id"))));
|
||||
|
||||
return closing(typedQuery.getResultStream().map(this::toModel));
|
||||
}
|
||||
|
|
|
@ -905,7 +905,7 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||
user.setLastName(userRep.getLastName());
|
||||
user.setFederationLink(userRep.getFederationLink());
|
||||
if (userRep.getAttributes() != null) {
|
||||
for (Map.Entry<String, List<String>> entry : userRep.getAttributes().entrySet()) {
|
||||
for (Map.Entry<String, List<String>> entry : userRep.getAttributes().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
List<String> value = entry.getValue();
|
||||
if (value != null) {
|
||||
user.setAttribute(entry.getKey(), new ArrayList<>(value));
|
||||
|
|
|
@ -115,7 +115,7 @@ public final class DefaultUserProfile implements UserProfile {
|
|||
try {
|
||||
Map<String, List<String>> writable = new HashMap<>(attributes.getWritable());
|
||||
|
||||
for (Map.Entry<String, List<String>> attribute : writable.entrySet()) {
|
||||
for (Map.Entry<String, List<String>> attribute : writable.entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
String name = attribute.getKey();
|
||||
List<String> currentValue = user.getAttributeStream(name)
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
|
|
|
@ -35,7 +35,6 @@ import org.keycloak.services.messages.Messages;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -87,7 +86,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
|||
if (federatedUser != null) {
|
||||
federatedUser.setEnabled(true);
|
||||
|
||||
for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) {
|
||||
for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
if (!UserModel.USERNAME.equalsIgnoreCase(attr.getKey())) {
|
||||
federatedUser.setAttribute(attr.getKey(), attr.getValue());
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
|
|||
import org.keycloak.broker.provider.ExchangeExternalToken;
|
||||
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||
import org.keycloak.broker.provider.IdentityProviderMapperSyncModeDelegate;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
|
@ -677,7 +676,7 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
|
|||
}
|
||||
|
||||
// make sure user attributes are updated based on attributes set to the context
|
||||
for (Map.Entry<String, List<String>> attr : context.getAttributes().entrySet()) {
|
||||
for (Map.Entry<String, List<String>> attr : context.getAttributes().entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
||||
if (!UserModel.USERNAME.equalsIgnoreCase(attr.getKey())) {
|
||||
user.setAttribute(attr.getKey(), attr.getValue());
|
||||
}
|
||||
|
|
|
@ -294,6 +294,7 @@ public class UserResource {
|
|||
.getProviderFactoriesStream(RequiredActionProvider.class)
|
||||
.map(ProviderFactory::getId)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.forEach(action -> {
|
||||
if (reqActions.contains(action)) {
|
||||
user.addRequiredAction(action);
|
||||
|
|
Loading…
Reference in a new issue