diff --git a/boms/standard-test/pom.xml b/boms/standard-test/pom.xml index c49937782864..b3c1f96beafb 100644 --- a/boms/standard-test/pom.xml +++ b/boms/standard-test/pom.xml @@ -127,6 +127,14 @@ test + + org.assertj + assertj-bom + ${version.org.assertj} + pom + import + + org.codehaus.plexus plexus-utils @@ -229,17 +237,10 @@ org.jboss.shrinkwrap - shrinkwrap-api - ${version.org.jboss.shrinkwrap.shrinkwrap} - test - - - - - org.jboss.shrinkwrap - shrinkwrap-impl-base + shrinkwrap-bom ${version.org.jboss.shrinkwrap.shrinkwrap} - test + pom + import @@ -298,11 +299,13 @@ ${version.org.keycloak} test + org.mockito - mockito-core + mockito-bom ${version.org.mockito} - test + pom + import @@ -311,23 +314,13 @@ ${version.org.syslog4j} test + org.testcontainers - elasticsearch - ${version.org.testcontainers} - test - - - org.testcontainers - kafka - ${version.org.testcontainers} - test - - - org.testcontainers - testcontainers + testcontainers-bom ${version.org.testcontainers} - test + pom + import diff --git a/clustering/ejb/cache/src/main/java/org/wildfly/clustering/ejb/cache/timer/TimerFactory.java b/clustering/ejb/cache/src/main/java/org/wildfly/clustering/ejb/cache/timer/TimerFactory.java index 9fed0d719cd4..26686766805f 100644 --- a/clustering/ejb/cache/src/main/java/org/wildfly/clustering/ejb/cache/timer/TimerFactory.java +++ b/clustering/ejb/cache/src/main/java/org/wildfly/clustering/ejb/cache/timer/TimerFactory.java @@ -8,7 +8,9 @@ import org.wildfly.clustering.ejb.timer.Timer; import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.server.scheduler.Scheduler; + import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; /** * @author Paul Ferraro @@ -19,5 +21,5 @@ public interface TimerFactory { TimerMetaDataFactory getMetaDataFactory(); - Timer createTimer(I id, ImmutableTimerMetaData metaData, TimerManager manager, Scheduler scheduler); + Timer createTimer(I id, ImmutableTimerMetaData metaData, TimerManager manager, Scheduler scheduler); } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/BeanExpirationScheduler.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/BeanExpirationScheduler.java index 165134fe270f..f992fdb4297b 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/BeanExpirationScheduler.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/BeanExpirationScheduler.java @@ -6,6 +6,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.Map; import java.util.concurrent.ThreadFactory; import java.util.function.Consumer; import java.util.function.Predicate; @@ -13,18 +14,22 @@ import org.wildfly.clustering.cache.batch.Batch; import org.wildfly.clustering.context.DefaultThreadFactory; +import org.wildfly.clustering.server.expiration.ExpirationMetaData; import org.wildfly.clustering.server.infinispan.CacheContainerGroup; -import org.wildfly.clustering.server.infinispan.expiration.AbstractExpirationScheduler; +import org.wildfly.clustering.server.infinispan.expiration.ExpirationMetaDataFunction; +import org.wildfly.clustering.server.infinispan.scheduler.AbstractCacheEntryScheduler; import org.wildfly.clustering.ejb.bean.Bean; import org.wildfly.clustering.ejb.bean.BeanExpirationConfiguration; import org.wildfly.clustering.ejb.bean.BeanInstance; import org.wildfly.clustering.ejb.bean.ImmutableBeanMetaData; import org.wildfly.clustering.ejb.cache.bean.BeanFactory; +import org.wildfly.clustering.ejb.cache.bean.BeanMetaDataKey; import org.wildfly.clustering.ejb.cache.bean.ImmutableBeanMetaDataFactory; import org.wildfly.clustering.ejb.infinispan.logging.InfinispanEjbLogger; import org.wildfly.clustering.server.local.scheduler.LocalScheduler; import org.wildfly.clustering.server.local.scheduler.LocalSchedulerConfiguration; import org.wildfly.clustering.server.local.scheduler.ScheduledEntries; +import org.wildfly.clustering.server.scheduler.Scheduler; import org.wildfly.security.manager.WildFlySecurityManager; /** @@ -34,12 +39,12 @@ * @param the bean instance type * @param the metadata value type */ -public class BeanExpirationScheduler, M> extends AbstractExpirationScheduler { +public class BeanExpirationScheduler, M> extends AbstractCacheEntryScheduler, M, ExpirationMetaData> { private static final ThreadFactory THREAD_FACTORY = new DefaultThreadFactory(BeanExpirationScheduler.class, WildFlySecurityManager.getClassLoaderPrivileged(BeanExpirationScheduler.class)); private final ImmutableBeanMetaDataFactory factory; public BeanExpirationScheduler(String name, CacheContainerGroup group, Supplier batchFactory, BeanFactory factory, BeanExpirationConfiguration expiration, Duration closeTimeout) { - super(new LocalScheduler<>(new LocalSchedulerConfiguration<>() { + this(new LocalScheduler<>(new LocalSchedulerConfiguration<>() { @Override public String getName() { return name; @@ -64,7 +69,11 @@ public ThreadFactory getThreadFactory() { public Duration getCloseTimeout() { return closeTimeout; } - })); + }), factory); + } + + private BeanExpirationScheduler(Scheduler scheduler, BeanFactory factory) { + super(scheduler.map(ExpirationMetaDataFunction.INSTANCE)); this.factory = factory.getMetaDataFactory(); } @@ -72,11 +81,17 @@ public Duration getCloseTimeout() { public void schedule(K id) { M value = this.factory.findValue(id); if (value != null) { - ImmutableBeanMetaData metaData = this.factory.createImmutableBeanMetaData(id, value); - this.schedule(id, metaData); + this.schedule(Map.entry(new InfinispanBeanMetaDataKey<>(id), value)); } } + @Override + public void schedule(Map.Entry, M> entry) { + K id = entry.getKey().getId(); + ImmutableBeanMetaData metaData = this.factory.createImmutableBeanMetaData(id, entry.getValue()); + this.schedule(id, metaData); + } + private static class BeanRemoveTask, M> implements Predicate { private final Supplier batchFactory; private final BeanFactory factory; diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanManager.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanManager.java index b75261bba9fc..b2ff234a68f5 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanManager.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanManager.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; -import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -28,15 +27,15 @@ import org.jboss.ejb.client.ClusterAffinity; import org.jboss.ejb.client.NodeAffinity; import org.wildfly.clustering.cache.CacheProperties; -import org.wildfly.clustering.cache.Key; import org.wildfly.clustering.cache.batch.Batch; -import org.wildfly.clustering.cache.infinispan.embedded.distribution.Locality; +import org.wildfly.clustering.cache.infinispan.embedded.distribution.CacheStreamFilter; import org.wildfly.clustering.cache.infinispan.embedded.listener.ListenerRegistration; import org.wildfly.clustering.ejb.bean.Bean; import org.wildfly.clustering.ejb.bean.BeanExpirationConfiguration; import org.wildfly.clustering.ejb.bean.BeanInstance; import org.wildfly.clustering.ejb.bean.BeanManager; import org.wildfly.clustering.ejb.cache.bean.BeanFactory; +import org.wildfly.clustering.ejb.cache.bean.BeanMetaDataKey; import org.wildfly.clustering.ejb.cache.bean.MutableBean; import org.wildfly.clustering.ejb.cache.bean.OnCloseBean; import org.wildfly.clustering.ejb.infinispan.logging.InfinispanEjbLogger; @@ -47,15 +46,14 @@ import org.wildfly.clustering.server.infinispan.dispatcher.CacheContainerCommandDispatcherFactory; import org.wildfly.clustering.server.infinispan.expiration.ScheduleWithExpirationMetaDataCommand; import org.wildfly.clustering.server.infinispan.manager.AffinityIdentifierFactory; -import org.wildfly.clustering.server.infinispan.scheduler.CacheEntryScheduler; +import org.wildfly.clustering.server.infinispan.scheduler.CacheEntriesTask; import org.wildfly.clustering.server.infinispan.scheduler.PrimaryOwnerScheduler; import org.wildfly.clustering.server.infinispan.scheduler.PrimaryOwnerSchedulerConfiguration; import org.wildfly.clustering.server.infinispan.scheduler.ScheduleCommand; -import org.wildfly.clustering.server.infinispan.scheduler.ScheduleLocalEntriesTask; import org.wildfly.clustering.server.infinispan.scheduler.ScheduleWithTransientMetaDataCommand; +import org.wildfly.clustering.server.infinispan.scheduler.Scheduler; import org.wildfly.clustering.server.infinispan.scheduler.SchedulerTopologyChangeListener; import org.wildfly.clustering.server.manager.IdentifierFactory; -import org.wildfly.clustering.server.scheduler.Scheduler; /** * A {@link BeanManager} implementation backed by an infinispan cache. @@ -66,7 +64,7 @@ */ public class InfinispanBeanManager, M> implements BeanManager { - private final Cache, Object> cache; + private final Cache, M> cache; private final CacheProperties properties; private final RetryConfig retryConfig; private final BeanFactory beanFactory; @@ -74,7 +72,7 @@ public class InfinispanBeanManager, M> implements B private final CacheContainerCommandDispatcherFactory dispatcherFactory; private final BeanExpirationConfiguration expiration; private final Supplier batchFactory; - private final Predicate, ? super Object>> filter; + private final Predicate, ? super M>> filter; private final Function primaryOwnerLocator; private final Affinity strongAffinity; @@ -108,7 +106,7 @@ public void start() { this.identifierFactory.start(); Duration stopTimeout = Duration.ofMillis(this.cache.getCacheConfiguration().transaction().cacheStopTimeout()); - CacheEntryScheduler localScheduler = (this.expiration != null) && !this.expiration.getTimeout().isZero() ? new BeanExpirationScheduler<>(this.cache.getName(), this.dispatcherFactory.getGroup(), this.batchFactory, this.beanFactory, this.expiration, stopTimeout) : null; + BeanExpirationScheduler localScheduler = (this.expiration != null) && !this.expiration.getTimeout().isZero() ? new BeanExpirationScheduler<>(this.cache.getName(), this.dispatcherFactory.getGroup(), this.batchFactory, this.beanFactory, this.expiration, stopTimeout) : null; String dispatcherName = String.join("/", this.cache.getName(), this.filter.toString()); this.scheduler = (localScheduler != null) ? (this.dispatcherFactory.getGroup().isSingleton() ? localScheduler : new PrimaryOwnerScheduler<>(new PrimaryOwnerSchedulerConfiguration<>() { @@ -123,7 +121,7 @@ public CacheContainerCommandDispatcherFactory getCommandDispatcherFactory() { } @Override - public CacheEntryScheduler getScheduler() { + public Scheduler getScheduler() { return localScheduler; } @@ -143,11 +141,12 @@ public RetryConfig getRetryConfig() { } })) : null; - BiConsumer scheduleTask = (localScheduler != null) ? new ScheduleLocalEntriesTask<>(this.cache, this.filter, localScheduler) : null; - this.schedulerListenerRegistration = (localScheduler != null) ? new SchedulerTopologyChangeListener<>(this.cache, localScheduler, scheduleTask).register() : null; + Consumer, M>>> scheduleTask = (localScheduler != null) ? CacheEntriesTask.schedule(this.cache, this.filter, localScheduler) : null; + Consumer, M>>> cancelTask = (localScheduler != null) ? CacheEntriesTask.cancel(this.cache, this.filter, localScheduler) : null; + this.schedulerListenerRegistration = (localScheduler != null) ? new SchedulerTopologyChangeListener<>(this.cache, scheduleTask, cancelTask).register() : null; if (scheduleTask != null) { // Schedule expiration of existing beans that we own - scheduleTask.accept(Locality.of(false), Locality.forCurrentConsistentHash(this.cache)); + scheduleTask.accept(CacheStreamFilter.local(this.cache)); } // If bean has expiration configuration, perform expiration task on close Consumer> closeTask = (this.expiration != null) ? bean -> { @@ -262,17 +261,18 @@ public Supplier getBatchFactory() { @Override public int getActiveCount() { - return this.count(EnumSet.of(Flag.CACHE_MODE_LOCAL, Flag.SKIP_CACHE_LOAD)); + return this.count(EnumSet.of(Flag.SKIP_CACHE_LOAD)); } @Override public int getPassiveCount() { - return this.count(EnumSet.of(Flag.CACHE_MODE_LOCAL)) - this.getActiveCount(); + return this.count(Set.of()) - this.getActiveCount(); } private int count(Set flags) { - try (Stream> keys = this.cache.getAdvancedCache().withFlags(flags).keySet().stream()) { - return (int) keys.filter(InfinispanBeanGroupKey.class::isInstance).count(); + CacheStreamFilter, M>> filter = CacheStreamFilter.local(this.cache); + try (Stream, M>> entries = filter.apply(this.cache.getAdvancedCache().withFlags(flags).entrySet().stream())) { + return (int) entries.filter(this.filter).count(); } } } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilter.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilter.java index 9b7a36fb3fe7..e04ddccfb363 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilter.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilter.java @@ -8,15 +8,15 @@ import java.util.Map; import org.infinispan.util.function.SerializablePredicate; -import org.wildfly.clustering.cache.Key; import org.wildfly.clustering.ejb.cache.bean.BeanMetaDataEntry; +import org.wildfly.clustering.ejb.cache.bean.BeanMetaDataKey; /** * Filters a cache for entries specific to a particular bean. * @author Paul Ferraro * @param the bean identifier type */ -public class InfinispanBeanMetaDataFilter implements SerializablePredicate, ? super Object>> { +public class InfinispanBeanMetaDataFilter implements SerializablePredicate> { private static final long serialVersionUID = -1079989480899595045L; private final String beanName; @@ -26,18 +26,29 @@ public InfinispanBeanMetaDataFilter(String beanName) { } @Override - public boolean test(Map.Entry, ? super Object> entry) { - if (entry.getKey() instanceof InfinispanBeanMetaDataKey) { + public boolean test(Map.Entry entry) { + if (entry.getKey() instanceof BeanMetaDataKey) { Object value = entry.getValue(); if (value instanceof BeanMetaDataEntry) { - @SuppressWarnings("unchecked") - BeanMetaDataEntry metaData = (BeanMetaDataEntry) value; + BeanMetaDataEntry metaData = (BeanMetaDataEntry) value; return this.beanName.equals(metaData.getName()); } } return false; } + @Override + public boolean equals(Object object) { + if (!(object instanceof InfinispanBeanMetaDataFilter)) return false; + InfinispanBeanMetaDataFilter filter = (InfinispanBeanMetaDataFilter) object; + return this.beanName.equals(filter.beanName); + } + + @Override + public int hashCode() { + return this.beanName.hashCode(); + } + @Override public String toString() { return this.beanName; diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanSerializationContextInitializer.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanSerializationContextInitializer.java index 40c7a2cb3240..fe0ca9ce3dc8 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanSerializationContextInitializer.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanSerializationContextInitializer.java @@ -9,6 +9,7 @@ import org.kohsuke.MetaInfServices; import org.wildfly.clustering.marshalling.protostream.AbstractSerializationContextInitializer; import org.wildfly.clustering.marshalling.protostream.ProtoStreamMarshaller; +import org.wildfly.clustering.marshalling.protostream.Scalar; import org.wildfly.clustering.marshalling.protostream.SerializationContext; import org.wildfly.clustering.marshalling.protostream.SerializationContextInitializer; @@ -25,5 +26,6 @@ public void registerMarshallers(SerializationContext context) { ProtoStreamMarshaller sessionIdMarshaller = context.getMarshaller(SessionID.class); context.registerMarshaller(sessionIdMarshaller.wrap((Class>) (Class) InfinispanBeanMetaDataKey.class, InfinispanBeanMetaDataKey::getId, InfinispanBeanMetaDataKey::new)); context.registerMarshaller(sessionIdMarshaller.wrap((Class>) (Class) InfinispanBeanGroupKey.class, InfinispanBeanGroupKey::getId, InfinispanBeanGroupKey::new)); + context.registerMarshaller(Scalar.STRING.cast(String.class).toMarshaller(InfinispanBeanMetaDataFilter.class, Object::toString, InfinispanBeanMetaDataFilter::new)); } } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimer.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimer.java index 7b14efc566ad..2956c5a75824 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimer.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimer.java @@ -5,13 +5,14 @@ package org.wildfly.clustering.ejb.infinispan.timer; +import org.wildfly.clustering.cache.CacheEntryRemover; +import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; +import org.wildfly.clustering.ejb.timer.TimeoutListener; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; import org.wildfly.clustering.ejb.timer.Timer; import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.ejb.timer.TimerRegistry; import org.wildfly.clustering.server.scheduler.Scheduler; -import org.wildfly.clustering.cache.CacheEntryRemover; -import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; -import org.wildfly.clustering.ejb.timer.TimeoutListener; /** * @author Paul Ferraro @@ -21,14 +22,14 @@ public class InfinispanTimer implements Timer { private final TimerManager manager; private final I id; private final ImmutableTimerMetaData metaData; - private final Scheduler scheduler; + private final Scheduler scheduler; private final TimeoutListener listener; private final CacheEntryRemover remover; private final TimerRegistry registry; private volatile boolean canceled = false; - public InfinispanTimer(TimerManager manager, I id, ImmutableTimerMetaData metaData, Scheduler scheduler, TimeoutListener listener, CacheEntryRemover remover, TimerRegistry registry) { + public InfinispanTimer(TimerManager manager, I id, ImmutableTimerMetaData metaData, Scheduler scheduler, TimeoutListener listener, CacheEntryRemover remover, TimerRegistry registry) { this.manager = manager; this.id = id; this.metaData = metaData; diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerFactory.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerFactory.java index 989db3121d10..cbdcff2c86b7 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerFactory.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerFactory.java @@ -5,12 +5,13 @@ package org.wildfly.clustering.ejb.infinispan.timer; -import org.wildfly.clustering.ejb.timer.Timer; -import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.ejb.cache.timer.TimerFactory; import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataFactory; import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; import org.wildfly.clustering.ejb.timer.TimeoutListener; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; +import org.wildfly.clustering.ejb.timer.Timer; +import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.ejb.timer.TimerRegistry; import org.wildfly.clustering.server.scheduler.Scheduler; @@ -32,7 +33,7 @@ public InfinispanTimerFactory(TimerMetaDataFactory factory, TimeoutListene } @Override - public Timer createTimer(I id, ImmutableTimerMetaData metaData, TimerManager manager, Scheduler scheduler) { + public Timer createTimer(I id, ImmutableTimerMetaData metaData, TimerManager manager, Scheduler scheduler) { return new InfinispanTimer<>(manager, id, metaData, scheduler, this.listener, this.factory, this.registry); } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerManager.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerManager.java index fb7a4896ab4d..2fd371d32a80 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerManager.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerManager.java @@ -9,7 +9,7 @@ import java.lang.reflect.Method; import java.time.Duration; import java.util.AbstractMap; -import java.util.function.BiConsumer; +import java.util.Map; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -22,6 +22,7 @@ import org.wildfly.clustering.cache.CacheProperties; import org.wildfly.clustering.cache.Key; import org.wildfly.clustering.cache.batch.Batch; +import org.wildfly.clustering.cache.infinispan.embedded.distribution.CacheStreamFilter; import org.wildfly.clustering.cache.infinispan.embedded.distribution.Locality; import org.wildfly.clustering.cache.infinispan.embedded.listener.ListenerRegistration; import org.wildfly.clustering.ejb.cache.timer.ImmutableTimerMetaDataFactory; @@ -31,9 +32,11 @@ import org.wildfly.clustering.ejb.cache.timer.TimerFactory; import org.wildfly.clustering.ejb.cache.timer.TimerIndex; import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataFactory; +import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataKey; import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; import org.wildfly.clustering.ejb.timer.IntervalTimerConfiguration; import org.wildfly.clustering.ejb.timer.ScheduleTimerConfiguration; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; import org.wildfly.clustering.ejb.timer.Timer; import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.ejb.timer.TimerRegistry; @@ -43,16 +46,15 @@ import org.wildfly.clustering.server.infinispan.affinity.UnaryGroupMemberAffinity; import org.wildfly.clustering.server.infinispan.dispatcher.CacheContainerCommandDispatcherFactory; import org.wildfly.clustering.server.infinispan.manager.AffinityIdentifierFactory; -import org.wildfly.clustering.server.infinispan.scheduler.CacheEntryScheduler; +import org.wildfly.clustering.server.infinispan.scheduler.CacheEntriesTask; +import org.wildfly.clustering.server.infinispan.scheduler.CacheKeysTask; import org.wildfly.clustering.server.infinispan.scheduler.PrimaryOwnerScheduler; import org.wildfly.clustering.server.infinispan.scheduler.PrimaryOwnerSchedulerConfiguration; import org.wildfly.clustering.server.infinispan.scheduler.ScheduleCommand; -import org.wildfly.clustering.server.infinispan.scheduler.ScheduleLocalKeysTask; -import org.wildfly.clustering.server.infinispan.scheduler.ScheduleWithPersistentMetaDataCommand; import org.wildfly.clustering.server.infinispan.scheduler.ScheduleWithTransientMetaDataCommand; +import org.wildfly.clustering.server.infinispan.scheduler.Scheduler; import org.wildfly.clustering.server.infinispan.scheduler.SchedulerTopologyChangeListener; import org.wildfly.clustering.server.manager.IdentifierFactory; -import org.wildfly.clustering.server.scheduler.Scheduler; /** * A timer manager backed by an Infinispan cache. @@ -60,7 +62,7 @@ */ public class InfinispanTimerManager implements TimerManager { - private final Cache, ?> cache; + private final Cache, RemappableTimerMetaDataEntry> cache; private final CacheProperties properties; private final RetryConfig retryConfig; private final TimerFactory> factory; @@ -70,8 +72,7 @@ public class InfinispanTimerManager implements TimerManager { private final CacheContainerCommandDispatcherFactory dispatcherFactory; private final TimerRegistry registry; - private volatile Scheduler scheduledTimers; - private volatile Scheduler scheduler; + private volatile Scheduler scheduler; private volatile ListenerRegistration schedulerListenerRegistration; public InfinispanTimerManager(InfinispanTimerManagerConfiguration config) { @@ -96,7 +97,6 @@ public void start() { Supplier locality = () -> Locality.forCurrentConsistentHash(this.cache); TimerScheduler> localScheduler = new TimerScheduler<>(this.cache.getName(), this.factory, this, locality, Duration.ofMillis(this.cache.getCacheConfiguration().transaction().cacheStopTimeout()), this.registry); - this.scheduledTimers = localScheduler; CacheContainerGroup group = this.dispatcherFactory.getGroup(); this.scheduler = group.isSingleton() ? localScheduler : new PrimaryOwnerScheduler<>(new PrimaryOwnerSchedulerConfiguration<>() { @@ -111,7 +111,7 @@ public CacheContainerCommandDispatcherFactory getCommandDispatcherFactory() { } @Override - public CacheEntryScheduler getScheduler() { + public Scheduler getScheduler() { return localScheduler; } @@ -121,8 +121,8 @@ public Function getAffinity() { } @Override - public BiFunction> getScheduleCommandFactory() { - return InfinispanTimerManager.this.properties.isTransactional() ? ScheduleWithPersistentMetaDataCommand::new : ScheduleWithTransientMetaDataCommand::new; + public BiFunction> getScheduleCommandFactory() { + return InfinispanTimerManager.this.properties.isTransactional() ? ScheduleWithPersistentTimeoutMetaDataCommand::new : ScheduleWithTransientMetaDataCommand::new; } @Override @@ -131,18 +131,13 @@ public RetryConfig getRetryConfig() { } }); - TimerRegistry registry = this.registry; - BiConsumer scheduleTask = new ScheduleLocalKeysTask<>(this.cache, TimerMetaDataKeyFilter.INSTANCE, new Consumer() { - @Override - public void accept(I id) { - localScheduler.schedule(id); - registry.register(id); - } - }); + Consumer, RemappableTimerMetaDataEntry>>> scheduleTask = new CacheEntriesTask<>(this.cache, TimerCacheEntryFilter.META_DATA_ENTRY.cast(), localScheduler::schedule); + org.wildfly.clustering.cache.function.Consumer cancel = localScheduler::cancel; + Consumer>> cancelTask = new CacheKeysTask<>(this.cache, TimerCacheKeyFilter.META_DATA_KEY, cancel.map(Key::getId)); - this.schedulerListenerRegistration = new SchedulerTopologyChangeListener<>(this.cache, localScheduler, scheduleTask).register(); + this.schedulerListenerRegistration = new SchedulerTopologyChangeListener<>(this.cache, scheduleTask, cancelTask).register(); - scheduleTask.accept(Locality.of(false), Locality.forCurrentConsistentHash(this.cache)); + scheduleTask.accept(CacheStreamFilter.local(this.cache)); this.identifierFactory.start(); } @@ -156,7 +151,7 @@ public void stop() { registration.close(); } - Scheduler scheduler = this.scheduler; + Scheduler scheduler = this.scheduler; if (scheduler != null) { scheduler.close(); } @@ -197,7 +192,7 @@ private Timer createTimer(I id, RemappableTimerMetaDataEntry entry, TimerI if (metaDataFactory.createValue(id, new AbstractMap.SimpleImmutableEntry<>(entry, index)) == null) return null; // Timer with index already exists ImmutableTimerMetaData metaData = metaDataFactory.createImmutableTimerMetaData(entry); - Timer timer = this.factory.createTimer(id, metaData, this, this.scheduledTimers); + Timer timer = this.factory.createTimer(id, metaData, this, this.scheduler); return timer; } @@ -207,15 +202,14 @@ public Timer getTimer(I id) { RemappableTimerMetaDataEntry entry = metaDataFactory.findValue(id); if (entry != null) { ImmutableTimerMetaData metaData = metaDataFactory.createImmutableTimerMetaData(entry); - return this.factory.createTimer(id, metaData, this, this.scheduledTimers); + return this.factory.createTimer(id, metaData, this, this.scheduler); } return null; } @Override public Stream getActiveTimers() { - // The primary owner scheduler can miss entries, if called during a concurrent topology change event - return this.dispatcherFactory.getGroup().isSingleton() ? this.scheduledTimers.stream() : this.cache.keySet().stream().filter(TimerMetaDataKeyFilter.INSTANCE).map(Key::getId); + return this.cache.keySet().stream().filter(TimerCacheKeyFilter.META_DATA_KEY).map(Key::getId); } @Override diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerSerializationContextInitializer.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerSerializationContextInitializer.java index 9814be99b714..4e983cfab1e7 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerSerializationContextInitializer.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/InfinispanTimerSerializationContextInitializer.java @@ -5,12 +5,14 @@ package org.wildfly.clustering.ejb.infinispan.timer; +import java.time.Instant; import java.util.UUID; import org.kohsuke.MetaInfServices; import org.wildfly.clustering.ejb.cache.timer.TimerIndex; import org.wildfly.clustering.marshalling.protostream.AbstractSerializationContextInitializer; import org.wildfly.clustering.marshalling.protostream.ProtoStreamMarshaller; +import org.wildfly.clustering.marshalling.protostream.Scalar; import org.wildfly.clustering.marshalling.protostream.SerializationContext; import org.wildfly.clustering.marshalling.protostream.SerializationContextInitializer; @@ -24,6 +26,8 @@ public class InfinispanTimerSerializationContextInitializer extends AbstractSeri public void registerMarshallers(SerializationContext context) { context.registerMarshaller(context.getMarshaller(UUID.class).wrap(InfinispanTimerMetaDataKey.class, InfinispanTimerMetaDataKey::getId, InfinispanTimerMetaDataKey::new)); context.registerMarshaller(context.getMarshaller(TimerIndex.class).wrap(InfinispanTimerIndexKey.class, InfinispanTimerIndexKey::getId, InfinispanTimerIndexKey::new)); - context.registerMarshaller(ProtoStreamMarshaller.of(TimerMetaDataKeyFilter.class)); + context.registerMarshaller(ProtoStreamMarshaller.of(TimerCacheKeyFilter.class)); + context.registerMarshaller(ProtoStreamMarshaller.of(TimerCacheEntryFilter.class)); + context.registerMarshaller(Scalar.LONG.cast(Long.class).toMarshaller(Instant::toEpochMilli, Instant::ofEpochMilli).wrap(SimpleTimeoutMetaData.class, SimpleTimeoutMetaData::getNextTimeout, SimpleTimeoutMetaData::new)); } } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/ScheduleWithPersistentTimeoutMetaDataCommand.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/ScheduleWithPersistentTimeoutMetaDataCommand.java new file mode 100644 index 000000000000..048ac9ced560 --- /dev/null +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/ScheduleWithPersistentTimeoutMetaDataCommand.java @@ -0,0 +1,25 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.timer; + +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; +import org.wildfly.clustering.server.infinispan.scheduler.ScheduleWithPersistentMetaDataCommand; + +/** + * Schedule command using persistent meta data. + * @author Paul Ferraro + */ +public class ScheduleWithPersistentTimeoutMetaDataCommand extends ScheduleWithPersistentMetaDataCommand { + + ScheduleWithPersistentTimeoutMetaDataCommand(I id, TimeoutMetaData metaData) { + super(id, metaData); + } + + @Override + protected TimeoutMetaData getMetaData() { + return new SimpleTimeoutMetaData(super.getMetaData()); + } +} diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaData.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaData.java new file mode 100644 index 000000000000..c01062c094db --- /dev/null +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaData.java @@ -0,0 +1,32 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.timer; + +import java.time.Instant; +import java.util.Optional; + +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; + +/** + * @author Paul Ferraro + */ +public class SimpleTimeoutMetaData implements TimeoutMetaData { + + private final Optional nextTimeout; + + SimpleTimeoutMetaData(TimeoutMetaData metaData) { + this(metaData.getNextTimeout()); + } + + SimpleTimeoutMetaData(Optional nextTimeout) { + this.nextTimeout = nextTimeout; + } + + @Override + public Optional getNextTimeout() { + return this.nextTimeout; + } +} diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheEntryFilter.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheEntryFilter.java new file mode 100644 index 000000000000..11942732deab --- /dev/null +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheEntryFilter.java @@ -0,0 +1,36 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.timer; + +import java.util.Map; +import java.util.function.Predicate; + +import org.infinispan.util.function.SerializablePredicate; +import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataKey; + +/** + * Cache entry filters for use with cache streams. + * @author Paul Ferraro + */ +public enum TimerCacheEntryFilter implements SerializablePredicate> { + META_DATA_ENTRY(TimerMetaDataKey.class); + + private final Class keyClass; + + TimerCacheEntryFilter(Class keyClass) { + this.keyClass = keyClass; + } + + @Override + public boolean test(Map.Entry entry) { + return this.keyClass.isInstance(entry.getKey()); + } + + @SuppressWarnings("unchecked") + Predicate> cast() { + return (Predicate>) (Predicate) this; + } +} diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerMetaDataKeyFilter.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheKeyFilter.java similarity index 54% rename from clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerMetaDataKeyFilter.java rename to clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheKeyFilter.java index 36c6999a020a..add2e6166fbb 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerMetaDataKeyFilter.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheKeyFilter.java @@ -11,11 +11,17 @@ /** * @author Paul Ferraro */ -public enum TimerMetaDataKeyFilter implements SerializablePredicate { - INSTANCE; +public enum TimerCacheKeyFilter implements SerializablePredicate { + META_DATA_KEY(TimerMetaDataKey.class); + + private final Class keyClass; + + TimerCacheKeyFilter(Class keyClass) { + this.keyClass = keyClass; + } @Override public boolean test(Object key) { - return key instanceof TimerMetaDataKey; + return this.keyClass.isInstance(key); } } diff --git a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerScheduler.java b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerScheduler.java index 688881ebcb74..e6040742cda5 100644 --- a/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerScheduler.java +++ b/clustering/ejb/infinispan/src/main/java/org/wildfly/clustering/ejb/infinispan/timer/TimerScheduler.java @@ -7,6 +7,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -26,7 +27,7 @@ import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataFactory; import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataKey; import org.wildfly.clustering.ejb.infinispan.logging.InfinispanEjbLogger; -import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; import org.wildfly.clustering.ejb.timer.Timer; import org.wildfly.clustering.ejb.timer.TimerManager; import org.wildfly.clustering.ejb.timer.TimerMetaData; @@ -39,11 +40,12 @@ import org.wildfly.security.manager.WildFlySecurityManager; /** + * A scheduler of timer timeouts. * @author Paul Ferraro * @param the timer identifier type * @param the timer metadata value type */ -public class TimerScheduler extends AbstractCacheEntryScheduler { +public class TimerScheduler extends AbstractCacheEntryScheduler, V, TimeoutMetaData> { private static final ThreadFactory THREAD_FACTORY = new DefaultThreadFactory(TimerScheduler.class, WildFlySecurityManager.getClassLoaderPrivileged(TimerScheduler.class)); private final TimerFactory factory; @@ -56,7 +58,7 @@ private TimerScheduler(String name, TimerFactory factory, TimerManager this(name, entries, new InvokeTask<>(factory, manager, locality, entries, registry, executor), closeTimeout, registry, executor, factory); } - private & Consumer>> TimerScheduler(String name, ScheduledEntries entries, T invokeTask, Duration closeTimeout, TimerRegistry registry, ExecutorService executor, TimerFactory factory) { + private & Consumer>> TimerScheduler(String name, ScheduledEntries entries, T invokeTask, Duration closeTimeout, TimerRegistry registry, ExecutorService executor, TimerFactory factory) { this(new LocalSchedulerConfiguration<>() { @Override public String getName() { @@ -85,8 +87,14 @@ public Duration getCloseTimeout() { }, registry, executor, invokeTask, factory); } - private TimerScheduler(LocalSchedulerConfiguration schedulerConfig, TimerRegistry registry, ExecutorService executor, Consumer> injector, TimerFactory factory) { + private TimerScheduler(LocalSchedulerConfiguration schedulerConfig, TimerRegistry registry, ExecutorService executor, Consumer> injector, TimerFactory factory) { this(new LocalScheduler<>(schedulerConfig) { + @Override + public void schedule(I id, Instant instant) { + super.schedule(id, instant); + registry.register(id); + } + @Override public void cancel(I id) { registry.unregister(id); @@ -101,30 +109,37 @@ public void close() { }, injector, factory); } - private TimerScheduler(Scheduler scheduler, Consumer> injector, TimerFactory factory) { - super(scheduler, ImmutableTimerMetaData::getNextTimeout); + private TimerScheduler(Scheduler scheduler, Consumer> injector, TimerFactory factory) { + super(scheduler.map(TimeoutMetaData::getNextTimeout)); this.factory = factory; injector.accept(this); } @Override public void schedule(I id) { - TimerMetaDataFactory metaDataFactory = this.factory.getMetaDataFactory(); - V value = metaDataFactory.findValue(id); + V value = this.factory.getMetaDataFactory().findValue(id); if (value != null) { - ImmutableTimerMetaData metaData = metaDataFactory.createImmutableTimerMetaData(value); - this.schedule(id, metaData); + this.schedule(Map.entry(new InfinispanTimerMetaDataKey<>(id), value)); } } - private static class InvokeTask implements Predicate, Consumer> { + @Override + public void schedule(Map.Entry, V> entry) { + this.scheduleValue(entry.getKey().getId(), entry.getValue()); + } + + private void scheduleValue(I id, V value) { + this.schedule(id, this.factory.getMetaDataFactory().createImmutableTimerMetaData(value)); + } + + private static class InvokeTask implements Predicate, Consumer> { private final TimerFactory factory; private final TimerManager manager; private final Supplier locality; private final ScheduledEntries entries; private final TimerRegistry registry; private final ExecutorService executor; - private Scheduler scheduler; + private Scheduler scheduler; InvokeTask(TimerFactory factory, TimerManager manager, Supplier locality, ScheduledEntries entries, TimerRegistry registry, ExecutorService executor) { this.factory = factory; @@ -136,7 +151,7 @@ private static class InvokeTask implements Predicate, Consumer scheduler) { + public void accept(Scheduler scheduler) { this.scheduler = scheduler; } @@ -147,7 +162,7 @@ public boolean test(I id) { Supplier locality = this.locality; ScheduledEntries entries = this.entries; TimerRegistry registry = this.registry; - Scheduler scheduler = this.scheduler; + Scheduler scheduler = this.scheduler; TimerMetaDataKey key = new InfinispanTimerMetaDataKey<>(id); // Ensure timer is owned by local member if (!locality.get().isLocal(key)) { diff --git a/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.bean.proto b/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.bean.proto index 7120a82583de..64fc2d014d64 100644 --- a/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.bean.proto +++ b/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.bean.proto @@ -20,3 +20,10 @@ message InfinispanBeanGroupKey { message InfinispanBeanMetaDataKey { org.jboss.ejb.client.SessionID id = 1; } + +/** + * @TypeId(312) + */ +message InfinispanBeanMetaDataFilter { + string beanName = 1; +} diff --git a/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.timer.proto b/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.timer.proto index 278f30cc90a2..9287c8085936 100644 --- a/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.timer.proto +++ b/clustering/ejb/infinispan/src/main/resources/org.wildfly.clustering.ejb.infinispan.timer.proto @@ -29,6 +29,20 @@ message InfinispanTimerIndexKey { /** * @TypeId(342) */ -enum TimerMetaDataKeyFilter { - INSTANCE = 0; +enum TimerCacheKeyFilter { + META_DATA_KEY = 0; +} + +/** + * @TypeId(343) + */ +enum TimerCacheEntryFilter { + META_DATA_ENTRY = 0; +} + +/** + * @TypeId(344) + */ +message SimpleTimeoutMetaData { + uint64 nextTimeout = 1; } diff --git a/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilterTestCase.java b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilterTestCase.java new file mode 100644 index 000000000000..a4d9f6d16bc4 --- /dev/null +++ b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/bean/InfinispanBeanMetaDataFilterTestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.bean; + +import org.jboss.ejb.client.SessionID; +import org.junit.jupiter.params.ParameterizedTest; +import org.wildfly.clustering.ejb.cache.bean.BeanMetaDataKey; +import org.wildfly.clustering.ejb.cache.bean.RemappableBeanMetaDataEntry; +import org.wildfly.clustering.marshalling.MarshallingTesterFactory; +import org.wildfly.clustering.marshalling.Tester; +import org.wildfly.clustering.marshalling.TesterFactory; +import org.wildfly.clustering.marshalling.junit.TesterFactorySource; + +/** + * @author Paul Ferraro + */ +public class InfinispanBeanMetaDataFilterTestCase { + + @ParameterizedTest + @TesterFactorySource({ MarshallingTesterFactory.class }) + public void test(TesterFactory factory) { + Tester, RemappableBeanMetaDataEntry>> tester = factory.createTester(); + tester.accept(new InfinispanBeanMetaDataFilter<>("foo")); + } +} diff --git a/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaDataTestCase.java b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaDataTestCase.java new file mode 100644 index 000000000000..a1ef875d174c --- /dev/null +++ b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/SimpleTimeoutMetaDataTestCase.java @@ -0,0 +1,33 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.timer; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Optional; + +import org.junit.jupiter.params.ParameterizedTest; +import org.wildfly.clustering.ejb.timer.TimeoutMetaData; +import org.wildfly.clustering.marshalling.MarshallingTesterFactory; +import org.wildfly.clustering.marshalling.Tester; +import org.wildfly.clustering.marshalling.TesterFactory; +import org.wildfly.clustering.marshalling.junit.TesterFactorySource; + +/** + * @author Paul Ferraro + */ +public class SimpleTimeoutMetaDataTestCase { + + @ParameterizedTest + @TesterFactorySource({ MarshallingTesterFactory.class }) + public void test(TesterFactory factory) { + Tester tester = factory.createTester((expected, actual) -> assertThat(actual.getNextTimeout()).isEqualTo(expected.getNextTimeout())); + tester.accept(new SimpleTimeoutMetaData(Optional.empty())); + tester.accept(new SimpleTimeoutMetaData(Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)))); + } +} diff --git a/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheStreamFilterTestCase.java b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheStreamFilterTestCase.java new file mode 100644 index 000000000000..b72c0da99134 --- /dev/null +++ b/clustering/ejb/infinispan/src/test/java/org/wildfly/clustering/ejb/infinispan/timer/TimerCacheStreamFilterTestCase.java @@ -0,0 +1,25 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.infinispan.timer; + +import org.junit.jupiter.params.ParameterizedTest; +import org.wildfly.clustering.marshalling.MarshallingTesterFactory; +import org.wildfly.clustering.marshalling.TesterFactory; +import org.wildfly.clustering.marshalling.junit.TesterFactorySource; + +/** + * Validates marshalling of timer cache stream filters. + * @author Paul Ferraro + */ +public class TimerCacheStreamFilterTestCase { + + @ParameterizedTest + @TesterFactorySource({ MarshallingTesterFactory.class }) + public void test(TesterFactory factory) { + factory.createTester(TimerCacheKeyFilter.class).run(); + factory.createTester(TimerCacheEntryFilter.class).run(); + } +} diff --git a/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/ImmutableTimerMetaData.java b/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/ImmutableTimerMetaData.java index 638169dd3fe4..1d151a8b6fa5 100644 --- a/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/ImmutableTimerMetaData.java +++ b/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/ImmutableTimerMetaData.java @@ -14,7 +14,7 @@ * Describes the immutable metadata of a timer. * @author Paul Ferraro */ -public interface ImmutableTimerMetaData { +public interface ImmutableTimerMetaData extends TimeoutMetaData { /** * Returns the type of this timer @@ -53,10 +53,4 @@ public interface ImmutableTimerMetaData { * @return the optional time of the last timeout event */ Optional getLastTimeout(); - - /** - * Returns the time of the next timeout event, or null if there are no future timeout events. - * @return the optional time of the next timeout event - */ - Optional getNextTimeout(); } diff --git a/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/TimeoutMetaData.java b/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/TimeoutMetaData.java new file mode 100644 index 000000000000..33e2f2d8ff45 --- /dev/null +++ b/clustering/ejb/spi/src/main/java/org/wildfly/clustering/ejb/timer/TimeoutMetaData.java @@ -0,0 +1,20 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.clustering.ejb.timer; + +import java.time.Instant; +import java.util.Optional; + +/** + * @author Paul Ferraro + */ +public interface TimeoutMetaData { + /** + * Returns the time of the next timeout event, or null if there are no future timeout events. + * @return the optional time of the next timeout event + */ + Optional getNextTimeout(); +} diff --git a/clustering/faces/mojarra/src/test/java/org/wildfly/clustering/faces/mojarra/context/SessionMapMutexMarshallerTestCase.java b/clustering/faces/mojarra/src/test/java/org/wildfly/clustering/faces/mojarra/context/SessionMapMutexMarshallerTestCase.java index 64b9af6710d5..0a6d8fcd2c88 100644 --- a/clustering/faces/mojarra/src/test/java/org/wildfly/clustering/faces/mojarra/context/SessionMapMutexMarshallerTestCase.java +++ b/clustering/faces/mojarra/src/test/java/org/wildfly/clustering/faces/mojarra/context/SessionMapMutexMarshallerTestCase.java @@ -4,6 +4,8 @@ */ package org.wildfly.clustering.faces.mojarra.context; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.ObjectAssert; import org.junit.jupiter.params.ParameterizedTest; import org.wildfly.clustering.marshalling.MarshallingTesterFactory; import org.wildfly.clustering.marshalling.Tester; @@ -19,7 +21,7 @@ public class SessionMapMutexMarshallerTestCase { @ParameterizedTest @TesterFactorySource(MarshallingTesterFactory.class) public void test(TesterFactory factory) { - Tester tester = factory.createTester(Object::getClass); + Tester tester = factory.createTester(Assertions::assertThat, ObjectAssert::hasSameClassAs); tester.accept(ContextImmutability.createMutex()); } } diff --git a/clustering/pom.xml b/clustering/pom.xml index 9eec17c1f07a..ca364ba083c4 100644 --- a/clustering/pom.xml +++ b/clustering/pom.xml @@ -68,6 +68,11 @@ junit test + + org.assertj + assertj-core + test + org.jboss.byteman byteman diff --git a/docs/src/main/asciidoc/_high-availability/Distributable_Web_Applications.adoc b/docs/src/main/asciidoc/_high-availability/Distributable_Web_Applications.adoc index 6423c97138de..403876a91559 100644 --- a/docs/src/main/asciidoc/_high-availability/Distributable_Web_Applications.adoc +++ b/docs/src/main/asciidoc/_high-availability/Distributable_Web_Applications.adoc @@ -269,7 +269,7 @@ e.g. One of the primary design goals of WildFly's distributed session manager was the parity of HttpSession semantics between distributable and non-distributable web applications. In order to provide predictable behavior suitable for most web applications, the default distributed session manager configuration is quite conservative, generally favoring consistency over availability. -However, these defaults may not be appropriate for your application. +While these defaults are suitable for most applications, they may not be ideal for others. In general, the effective performance of the distributed session manager is constrained by: . Replication/persistence payload size @@ -296,32 +296,24 @@ For read-heavy applications, this can dramatically reduce the replication/persis [[session_concurrency]] === Session concurrency -WildFly's default distributed session manager behavior is also conservative with respect to concurrent access to a given session. -By default, a request acquires exclusive access to its associated session for the duration of a request, and until any async child context is complete. -This maximizes the performance of a single request, as each request corresponds to a single cache transaction; allows for repeatable read semantics to the session; and ensures that subsequent requests are not prone to stale reads, even when handled by another cluster member. +By default, WildFly's distributed session manager configuration permits concurrent access to a given session on a single cluster member at a time, +as mandated by https://jakarta.ee/specifications/servlet/6.1/jakarta-servlet-spec-6.1#distributed-environments[ยง7.7.2 of the Jakarta Servlet specification]. -However, if multiple requests attempt to access the same session concurrently, their processing will be effectively serialized. This might not be feasible, especially for heavily asynchronous web applications. +By default, the first request for a given session acquires exclusive, cluster-wide access to its associated session, and maintains that exclusivity until the last concurrent request for that session has completed. +Concurrent requests for the same session arriving on other cluster members will be blocked until all requests for the given session have completed on the cluster member retaining exclusive access. +This level of concurrency should be sufficient for most applications using a load balancer properly configured with session affinity. -Relaxing transaction isolation from REPEATABLE_READ to READ_COMMITTED on the associated cache configuration will allow concurrent requests to perform lock-free (but potentially stale) reads by deferring locking to the first attempt to write to the session. -This improves the throughput of requests for the same session for highly asynchronous web applications whose session access is read-heavy. - -e.g. -[source] ----- -/subsystem=infinispan/cache-container=web/distributed-cache=dist/component=locking:write-attribute(name=isolation, value=READ_COMMITTED) ----- - -For asynchronous web applications whose session access is write-heavy, merely relaxing transaction isolation is not likely to be sufficient. -These web applications will likely benefit from disabling cache transactions altogether. -When transactions are disabled, cache entries are locked and released for every write to the session, resulting in last-write-wins semantics. -For write-heavy applications, this typically improves the throughput of concurrent requests for the same session, at the cost of longer response times for individual requests. +If your application uses the HttpSession in a sufficiently read-only manner and/or can tolerate potentially dirty reads, you may consider disabling transactions on the Infinispan cache used to store HttpSession attributes and metadata. +Disabling transactions will permit concurrent access to a given session by any cluster member. +When transactions are disabled, however, changes to an HttpSession on one cluster member will not be visible to a concurrent request for the same session on a different cluster member, and updates will have last-write-wins semantics. +Disabling transactions on the default cache configuration for sessions is achieved using the following command: [source] ---- /subsystem=infinispan/cache-container=web/distributed-cache=dist/component=transaction:write-attribute(name=mode, value=NONE) ---- -NOTE: Relaxing transaction isolation currently prevents WildFly from enforcing that a given session is handled by one JVM at a time, a constraint dictated by the servlet specification. +NOTE: Relaxing transaction isolation or disabling transactions prevents WildFly from enforcing that a given session is handled by one JVM at a time, a constraint mandated by the Jakarta Servlet specification. [[session_attribute_immutability]] === Session attribute immutability diff --git a/jpa/hibernate6/pom.xml b/jpa/hibernate6/pom.xml index 51df72d8e781..b6968ed8e036 100644 --- a/jpa/hibernate6/pom.xml +++ b/jpa/hibernate6/pom.xml @@ -42,28 +42,6 @@ - - - junit - junit - test - - - - org.jboss.shrinkwrap - shrinkwrap-api - - - - org.jboss.shrinkwrap - shrinkwrap-impl-base - - - org.junit.jupiter - junit-jupiter - test - - @@ -200,5 +178,26 @@ protoparser test + + junit + junit + test + + + + org.assertj + assertj-core + test + + + org.jboss.shrinkwrap + shrinkwrap-api + test + + + org.junit.jupiter + junit-jupiter + test + diff --git a/pom.xml b/pom.xml index 44fa13df2cb2..54a0bde83935 100644 --- a/pom.xml +++ b/pom.xml @@ -291,6 +291,7 @@ 4.13.2 2.0.0.AM26 4.0.24 + 3.26.3 4.0.3 3.5.1 11.0.24 @@ -313,7 +314,7 @@ 2.0.0.Final 5.10.5 25.0.2 - 3.10.0 + 5.14.2 0.9.30 1.20.4 7.8.0 @@ -470,7 +471,6 @@ 8.0.2.Final 2.4.11.Final 15.0.11.Final - 5.0.12.Final 1.9.3 3.1.0.Final 2.0.3.Final @@ -518,7 +518,7 @@ 4.3.0 9.7.1 1.0.4 - 4.0.5.Final + 5.0.2.Final 27.0.0.Beta6 2.0.7.Final 1.0.0.Final diff --git a/testsuite/integration/clustering/pom.xml b/testsuite/integration/clustering/pom.xml index b8036c8c8677..61a9a38dd321 100644 --- a/testsuite/integration/clustering/pom.xml +++ b/testsuite/integration/clustering/pom.xml @@ -1319,8 +1319,8 @@ org/jboss/as/test/clustering/cluster/singleton/SingletonPartitionTestCase.java org/jboss/as/test/clustering/cluster/web/CoarseImmutableWebFailoverTestCase.java org/jboss/as/test/clustering/cluster/web/CoarseWebFailoverTestCase.java - org/jboss/as/test/clustering/cluster/web/ConcurrentCoarseWebFailoverTestCase.java - org/jboss/as/test/clustering/cluster/web/ConcurrentFineWebFailoverTestCase.java + org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalWebFailoverTestCase.java + org/jboss/as/test/clustering/cluster/web/FineNonTransactionalWebFailoverTestCase.java org/jboss/as/test/clustering/cluster/web/FineImmutableWebFailoverTestCase.java org/jboss/as/test/clustering/cluster/web/FineWebFailoverTestCase.java org/jboss/as/test/clustering/cluster/web/ReplicationForNegotiationAuthenticatorTestCase.java diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalSessionActivationTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalSessionActivationTestCase.java index b80fe132858c..2d029f39d2a1 100644 --- a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalSessionActivationTestCase.java +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalSessionActivationTestCase.java @@ -16,7 +16,7 @@ /** * @author Paul Ferraro */ -@ServerSetup(ConcurrentSessionServerSetup.class) +@ServerSetup(NonTransactionalSessionServerSetup.class) public class CoarseNonTransactionalSessionActivationTestCase extends AbstractSessionActivationTestCase { private static final String MODULE_NAME = CoarseNonTransactionalSessionActivationTestCase.class.getSimpleName(); @@ -44,7 +44,7 @@ private static Archive getDeployment() { WebArchive war = ShrinkWrap.create(WebArchive.class, DEPLOYMENT_NAME); war.addClasses(SessionActivationServlet.class); war.setWebXML(DistributableTestCase.class.getPackage(), "web.xml"); - war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_concurrent_coarse.xml", "jboss-all.xml"); + war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_non-tx_coarse.xml", "jboss-all.xml"); return war; } diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentCoarseWebFailoverTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalWebFailoverTestCase.java similarity index 82% rename from testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentCoarseWebFailoverTestCase.java rename to testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalWebFailoverTestCase.java index adf1f8d654db..af6e815745e7 100644 --- a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentCoarseWebFailoverTestCase.java +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/CoarseNonTransactionalWebFailoverTestCase.java @@ -15,13 +15,13 @@ import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.WebArchive; -@ServerSetup(ConcurrentSessionServerSetup.class) -public class ConcurrentCoarseWebFailoverTestCase extends AbstractWebFailoverTestCase { +@ServerSetup(NonTransactionalSessionServerSetup.class) +public class CoarseNonTransactionalWebFailoverTestCase extends AbstractWebFailoverTestCase { - private static final String MODULE_NAME = ConcurrentCoarseWebFailoverTestCase.class.getSimpleName(); + private static final String MODULE_NAME = CoarseNonTransactionalWebFailoverTestCase.class.getSimpleName(); private static final String DEPLOYMENT_NAME = MODULE_NAME + ".war"; - public ConcurrentCoarseWebFailoverTestCase() { + public CoarseNonTransactionalWebFailoverTestCase() { super(DEPLOYMENT_NAME, TransactionMode.NON_TRANSACTIONAL); } @@ -49,7 +49,7 @@ private static Archive getDeployment() { ClusterTestUtil.addTopologyListenerDependencies(war); // Take web.xml from the managed test. war.setWebXML(DistributableTestCase.class.getPackage(), "web.xml"); - war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_concurrent_coarse.xml", "jboss-all.xml"); + war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_non-tx_coarse.xml", "jboss-all.xml"); return war; } } diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalSessionActivationTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalSessionActivationTestCase.java index 6221d64433e7..6788d98db386 100644 --- a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalSessionActivationTestCase.java +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalSessionActivationTestCase.java @@ -16,7 +16,7 @@ /** * @author Paul Ferraro */ -@ServerSetup(ConcurrentSessionServerSetup.class) +@ServerSetup(NonTransactionalSessionServerSetup.class) public class FineNonTransactionalSessionActivationTestCase extends AbstractSessionActivationTestCase { private static final String MODULE_NAME = FineNonTransactionalSessionActivationTestCase.class.getSimpleName(); @@ -44,7 +44,7 @@ private static Archive getDeployment() { WebArchive war = ShrinkWrap.create(WebArchive.class, DEPLOYMENT_NAME); war.addClasses(SessionActivationServlet.class); war.setWebXML(DistributableTestCase.class.getPackage(), "web.xml"); - war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_concurrent_fine.xml", "jboss-all.xml"); + war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_non-tx_fine.xml", "jboss-all.xml"); return war; } diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentFineWebFailoverTestCase.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalWebFailoverTestCase.java similarity index 83% rename from testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentFineWebFailoverTestCase.java rename to testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalWebFailoverTestCase.java index d4e94a787c19..174df17884e6 100644 --- a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentFineWebFailoverTestCase.java +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/FineNonTransactionalWebFailoverTestCase.java @@ -18,13 +18,13 @@ /** * @author Radoslav Husar */ -@ServerSetup(ConcurrentSessionServerSetup.class) -public class ConcurrentFineWebFailoverTestCase extends AbstractWebFailoverTestCase { +@ServerSetup(NonTransactionalSessionServerSetup.class) +public class FineNonTransactionalWebFailoverTestCase extends AbstractWebFailoverTestCase { - private static final String MODULE_NAME = ConcurrentFineWebFailoverTestCase.class.getSimpleName(); + private static final String MODULE_NAME = FineNonTransactionalWebFailoverTestCase.class.getSimpleName(); private static final String DEPLOYMENT_NAME = MODULE_NAME + ".war"; - public ConcurrentFineWebFailoverTestCase() { + public FineNonTransactionalWebFailoverTestCase() { super(DEPLOYMENT_NAME, TransactionMode.NON_TRANSACTIONAL); } @@ -52,7 +52,7 @@ private static Archive createDeployment() { ClusterTestUtil.addTopologyListenerDependencies(war); // Take web.xml from the managed test. war.setWebXML(DistributableTestCase.class.getPackage(), "web.xml"); - war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_concurrent_fine.xml", "jboss-all.xml"); + war.addAsWebInfResource(DistributableTestCase.class.getPackage(), "jboss-all_non-tx_fine.xml", "jboss-all.xml"); return war; } } diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentSessionServerSetup.java b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/NonTransactionalSessionServerSetup.java similarity index 85% rename from testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentSessionServerSetup.java rename to testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/NonTransactionalSessionServerSetup.java index dd23f9779f14..7aadfba185e9 100644 --- a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/ConcurrentSessionServerSetup.java +++ b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/NonTransactionalSessionServerSetup.java @@ -11,8 +11,8 @@ /** * @author Paul Ferraro */ -public class ConcurrentSessionServerSetup extends CLIServerSetupTask { - public ConcurrentSessionServerSetup() { +public class NonTransactionalSessionServerSetup extends CLIServerSetupTask { + public NonTransactionalSessionServerSetup() { this.builder.node(AbstractClusteringTestCase.THREE_NODES) .setup("/subsystem=infinispan/cache-container=web/distributed-cache=concurrent:add()") .setup("/subsystem=infinispan/cache-container=web/distributed-cache=concurrent/store=file:add(passivation=true, purge=true)") diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_concurrent_coarse.xml b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_non-tx_coarse.xml similarity index 100% rename from testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_concurrent_coarse.xml rename to testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_non-tx_coarse.xml diff --git a/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_concurrent_fine.xml b/testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_non-tx_fine.xml similarity index 100% rename from testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_concurrent_fine.xml rename to testsuite/integration/clustering/src/test/java/org/jboss/as/test/clustering/cluster/web/jboss-all_non-tx_fine.xml