All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.clustering.ejb.infinispan.timer.TimerScheduler Maven / Gradle / Ivy

There is a newer version: 33.0.2.Final
Show newest version
/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.wildfly.clustering.ejb.infinispan.timer;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.wildfly.clustering.context.DefaultThreadFactory;
import org.wildfly.clustering.ee.Scheduler;
import org.wildfly.clustering.ee.cache.scheduler.LocalScheduler;
import org.wildfly.clustering.ee.cache.scheduler.ScheduledEntries;
import org.wildfly.clustering.ee.cache.scheduler.SortedScheduledEntries;
import org.wildfly.clustering.ee.cache.tx.TransactionBatch;
import org.wildfly.clustering.ee.infinispan.GroupedKey;
import org.wildfly.clustering.ee.infinispan.scheduler.AbstractCacheEntryScheduler;
import org.wildfly.clustering.ejb.cache.timer.TimerFactory;
import org.wildfly.clustering.ejb.cache.timer.TimerMetaDataFactory;
import org.wildfly.clustering.ejb.infinispan.logging.InfinispanEjbLogger;
import org.wildfly.clustering.ejb.timer.ImmutableTimerMetaData;
import org.wildfly.clustering.ejb.timer.Timer;
import org.wildfly.clustering.ejb.timer.TimerManager;
import org.wildfly.clustering.ejb.timer.TimerMetaData;
import org.wildfly.clustering.ejb.timer.TimerRegistry;
import org.wildfly.clustering.infinispan.distribution.Locality;

/**
 * @author Paul Ferraro
 */
public class TimerScheduler extends AbstractCacheEntryScheduler {
    private static final ThreadFactory THREAD_FACTORY = new DefaultThreadFactory(TimerScheduler.class);

    private final TimerFactory factory;

    public TimerScheduler(TimerFactory factory, TimerManager manager, Supplier locality, Duration closeTimeout, TimerRegistry registry) {
        this(factory, manager, locality, closeTimeout, registry, new SortedScheduledEntries<>(), Executors.newSingleThreadExecutor(THREAD_FACTORY));
    }

    private TimerScheduler(TimerFactory factory, TimerManager manager, Supplier locality, Duration closeTimeout, TimerRegistry registry, ScheduledEntries entries, ExecutorService executor) {
        this(entries, new InvokeTask<>(factory, manager, locality, entries, registry, executor), closeTimeout, registry, executor, factory);
    }

    private  & Consumer>> TimerScheduler(ScheduledEntries entries, T invokeTask, Duration closeTimeout, TimerRegistry registry, ExecutorService executor, TimerFactory factory) {
        this(new LocalScheduler<>(entries, invokeTask, closeTimeout) {
            @Override
            public void cancel(I id) {
                registry.unregister(id);
                super.cancel(id);
            }

            @Override
            public void close() {
                super.close();
                executor.shutdown();
            }
        }, invokeTask, factory);
    }

    private TimerScheduler(Scheduler scheduler, Consumer> injector, TimerFactory factory) {
        super(scheduler, ImmutableTimerMetaData::getNextTimeout);
        this.factory = factory;
        injector.accept(this);
    }

    @Override
    public void schedule(I id) {
        TimerMetaDataFactory metaDataFactory = this.factory.getMetaDataFactory();
        V value = metaDataFactory.findValue(id);
        if (value != null) {
            ImmutableTimerMetaData metaData = metaDataFactory.createImmutableTimerMetaData(value);
            this.schedule(id, metaData);
        }
    }

    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;

        InvokeTask(TimerFactory factory, TimerManager manager, Supplier locality, ScheduledEntries entries, TimerRegistry registry, ExecutorService executor) {
            this.factory = factory;
            this.manager = manager;
            this.locality = locality;
            this.entries = entries;
            this.registry = registry;
            this.executor = executor;
        }

        @Override
        public void accept(Scheduler scheduler) {
            this.scheduler = scheduler;
        }

        @Override
        public boolean test(I id) {
            TimerFactory factory = this.factory;
            TimerManager manager = this.manager;
            Supplier locality = this.locality;
            ScheduledEntries entries = this.entries;
            TimerRegistry registry = this.registry;
            Scheduler scheduler = this.scheduler;
            // Ensure timer is owned by local member
            if (!locality.get().isLocal(new GroupedKey<>(id))) {
                InfinispanEjbLogger.ROOT_LOGGER.debugf("Skipping timeout processing of non-local timer %s", id);
                return true;
            }
            Callable task = new Callable<>() {
                @Override
                public Boolean call() throws Exception {
                    InfinispanEjbLogger.ROOT_LOGGER.debugf("Initiating timeout for timer %s", id);
                    TimerMetaDataFactory metaDataFactory = factory.getMetaDataFactory();
                    try (TransactionBatch batch = manager.getBatcher().createBatch()) {
                        V value = metaDataFactory.findValue(id);
                        if (value == null) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Timer not found %s", id);
                            return true;
                        }

                        TimerMetaData metaData = metaDataFactory.createTimerMetaData(id, value);
                        Optional currentTimeoutReference = metaData.getNextTimeout();

                        // Safeguard : ensure timeout was not already triggered elsewhere
                        if (currentTimeoutReference.isEmpty()) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Unexpected timeout event triggered.", id);
                            return false;
                        }
                        Instant currentTimeout = currentTimeoutReference.get();
                        if (currentTimeout.isAfter(Instant.now())) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Timeout for timer %s initiated prematurely.", id);
                            return false;
                        }

                        Timer timer = factory.createTimer(id, metaData, manager, scheduler);

                        InfinispanEjbLogger.ROOT_LOGGER.debugf("Triggering timeout for timer %s [%s]", id, timer.getMetaData().getContext());

                        // In case we need to reset the last timeout
                        Optional lastTimeout = metaData.getLastTimeout();
                        // Record last timeout - expected to be set prior to triggering timeout
                        metaData.setLastTimeout(currentTimeout);

                        try {
                            timer.invoke();
                        } catch (ExecutionException e) {
                            // Log error and proceed as if it was successful
                            InfinispanEjbLogger.ROOT_LOGGER.error(e.getLocalizedMessage(), e);
                        } catch (RejectedExecutionException e) {
                            // Component is not started or is suspended
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("EJB component is suspended - could not invoke timeout for timer %s", id);
                            // Reset last timeout
                            metaData.setLastTimeout(lastTimeout.orElse(null));
                            return false;
                        }

                        // If timeout callback canceled this timer
                        if (timer.isCanceled()) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Timeout callback canceled timer %s", id);
                            return true;
                        }

                        // Determine next timeout
                        Optional nextTimeout = metaData.getNextTimeout();
                        if (nextTimeout.isEmpty()) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Timer %s has expired", id);
                            registry.unregister(id);
                            factory.getMetaDataFactory().remove(id);
                            return true;
                        }

                        // Only reschedule if timer is still local
                        if (!locality.get().isLocal(new GroupedKey<>(id))) {
                            InfinispanEjbLogger.ROOT_LOGGER.debugf("Timer %s is no longer local", id);
                            return true;
                        }

                        // Reschedule using next timeout
                        InfinispanEjbLogger.ROOT_LOGGER.debugf("Rescheduling timer %s for next timeout %s", id, nextTimeout);
                        entries.add(id, nextTimeout.get());
                        return false;
                    }
                }
            };
            try {
                Future result = this.executor.submit(task);
                return result.get();
            } catch (RejectedExecutionException e) {
                // Scheduler was shutdown
                return false;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                // Timer was canceled by the scheduler
                return false;
            } catch (ExecutionException e) {
                InfinispanEjbLogger.ROOT_LOGGER.info(e.getLocalizedMessage(), e);
                return false;
            }
        }
    }
}