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

org.glassfish.concurrent.runtime.ConcurrentRuntime Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021-2025 Contributors to the Eclipse Foundation.
 * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.concurrent.runtime;

import com.sun.enterprise.config.serverbeans.Applications;
import com.sun.enterprise.deployment.annotation.handlers.ContextualResourceDefinition;
import com.sun.enterprise.deployment.types.ConcurrencyContextType;
import com.sun.enterprise.transaction.api.JavaEETransactionManager;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.glassfish.api.invocation.InvocationManager;
import org.glassfish.api.naming.SimpleJndiName;
import org.glassfish.concurrent.runtime.deployer.cfg.ConcurrentServiceCfg;
import org.glassfish.concurrent.runtime.deployer.cfg.ContextServiceCfg;
import org.glassfish.concurrent.runtime.deployer.cfg.ManagedExecutorServiceCfg;
import org.glassfish.concurrent.runtime.deployer.cfg.ManagedScheduledExecutorServiceCfg;
import org.glassfish.concurrent.runtime.deployer.cfg.ManagedThreadFactoryCfg;
import org.glassfish.concurro.AbstractManagedExecutorService;
import org.glassfish.concurro.AbstractManagedThread;
import org.glassfish.concurro.ContextServiceImpl;
import org.glassfish.concurro.ManagedExecutorServiceImpl;
import org.glassfish.concurro.ManagedScheduledExecutorServiceImpl;
import org.glassfish.concurro.ManagedThreadFactoryImpl;
import org.glassfish.concurro.spi.ContextHandle;
import org.glassfish.concurro.spi.ContextSetupProvider;
import org.glassfish.concurro.spi.TransactionSetupProvider;
import org.glassfish.concurro.virtualthreads.VirtualThreadsManagedExecutorService;
import org.glassfish.concurro.virtualthreads.VirtualThreadsManagedScheduledExecutorService;
import org.glassfish.concurro.virtualthreads.VirtualThreadsManagedThreadFactory;
import org.glassfish.internal.data.ApplicationRegistry;
import org.glassfish.internal.deployment.Deployment;
import org.glassfish.resourcebase.resources.api.GenericResourceInfo;
import org.glassfish.resourcebase.resources.api.ResourceInfo;
import org.glassfish.resourcebase.resources.naming.ResourceNamingService;
import org.jvnet.hk2.annotations.Service;

import static com.sun.enterprise.deployment.types.StandardContextType.Classloader;
import static com.sun.enterprise.deployment.types.StandardContextType.JNDI;
import static com.sun.enterprise.deployment.types.StandardContextType.Security;
import static com.sun.enterprise.deployment.types.StandardContextType.WorkArea;
import static java.util.Collections.emptySet;

/**
 * This class provides API to create various Concurrency Utilities objects
 */
@Service
@Singleton
public class ConcurrentRuntime {

    private static final Logger LOG = LogFacade.getLogger();
    private static ConcurrentRuntime singletonInstance;

    private final Map managedExecutorServiceMap = new HashMap<>();
    private final Map managedScheduledExecutorServiceMap = new HashMap<>();
    private final Map contextServiceMap = new HashMap<>();
    private final Map managedThreadFactoryMap = new HashMap<>();

    private ScheduledExecutorService internalScheduler;

    @Inject
    private InvocationManager invocationManager;
    @Inject
    private Deployment deployment;
    @Inject
    private Applications applications;
    @Inject
    private JavaEETransactionManager transactionManager;
    @Inject
    private ApplicationRegistry applicationRegistry;
    @Inject
    private ResourceNamingService resourceNamingService;

    /**
     * Returns the ConcurrentRuntime instance.
     * It follows singleton pattern and only one instance exists at any point
     * of time. External entities need to call this method to get
     * ConcurrentRuntime instance
     *
     * @return ConcurrentRuntime instance
     */
    public static ConcurrentRuntime getRuntime() {
        if (singletonInstance == null) {
            throw new RuntimeException("ConcurrentRuntime not initialized");
        }
        return singletonInstance;
    }

    /**
     * Constructor should be private to follow singleton pattern, but package access for unit testing.
     */
    ConcurrentRuntime() {
        singletonInstance = this;
    }

    InvocationManager getInvocationManager() {
        return invocationManager;
    }

    Deployment getDeployment() {
        return deployment;
    }

    Applications getApplications() {
        return applications;
    }

    JavaEETransactionManager getTransactionManager() {
        return transactionManager;
    }

    ApplicationRegistry getApplicationRegistry() {
        return applicationRegistry;
    }


    public synchronized AbstractManagedExecutorService getManagedExecutorService(ManagedExecutorServiceCfg config) {
        LOG.log(Level.FINEST, "getManagedExecutorService(config={0})", config);
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        if (managedExecutorServiceMap != null && managedExecutorServiceMap.containsKey(jndiName)) {
            return managedExecutorServiceMap.get(jndiName);
        }
        ContextServiceImpl contextService = getContextService(config.getServiceConfig(), true);
        AbstractManagedExecutorService mes = createManagedExecutorService(config, contextService);
        managedExecutorServiceMap.put(jndiName, mes);
        return mes;
    }


    public synchronized AbstractManagedExecutorService createManagedExecutorService(ManagedExecutorServiceCfg config, ContextServiceImpl contextService) {
        LOG.log(Level.FINE, "createManagedExecutorService(config={0}, contextService={1})",
            new Object[] {config, contextService});
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        GlassFishManagedThreadFactory managedThreadFactory = new GlassFishManagedThreadFactory(
                toManagedThreadFactoryName(jndiName),
                null,
                config.getThreadPriority());
        AbstractManagedExecutorService mes = null;
        if (config.getUseVirtualThreads()) {
            try {
                mes = new VirtualThreadsManagedExecutorService(jndiName.toString(),
                        null,
                        config.getHungAfterSeconds() * 1_000L, // in milliseconds
                        config.isLongRunningTasks(),
                        config.getMaximumPoolSize(),
                        config.getTaskQueueCapacity(),
                        contextService,
                        AbstractManagedExecutorService.RejectPolicy.ABORT);
            } catch (Exception e) {
                LOG.severe(() -> "Unable to create ManagedExecutorService with virtual threads: " + e.getMessage() + ", using fallback");
            }
        }
        if (mes == null) {
            mes = new ManagedExecutorServiceImpl(jndiName.toString(),
                    managedThreadFactory,
                    config.getHungAfterSeconds() * 1_000L, // in millis
                    config.isLongRunningTasks(),
                    config.getCorePoolSize(),
                    config.getMaximumPoolSize(),
                    config.getKeepAliveSeconds(), TimeUnit.SECONDS,
                    config.getThreadLifeTimeSeconds(),
                    config.getTaskQueueCapacity(),
                    contextService,
                    AbstractManagedExecutorService.RejectPolicy.ABORT);
        }
        if (config.getHungAfterSeconds() > 0L && !config.isLongRunningTasks()) {
            scheduleInternalTimer(config.getHungLoggerInitialDelaySeconds(), config.getHungLoggerIntervalSeconds(), config.isHungLoggerPrintOnce());
        }
        return mes;
    }


    public synchronized AbstractManagedExecutorService getManagedScheduledExecutorService(ManagedScheduledExecutorServiceCfg config) {
        LOG.log(Level.FINE, "getManagedScheduledExecutorService(config={0})", config);
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        if (managedScheduledExecutorServiceMap != null && managedScheduledExecutorServiceMap.containsKey(jndiName)) {
            return managedScheduledExecutorServiceMap.get(jndiName);
        }
        ContextServiceImpl contextService = getContextService(config.getServiceConfig(), true);
        AbstractManagedExecutorService mes = createManagedScheduledExecutorService(config, contextService);
        managedScheduledExecutorServiceMap.put(jndiName, mes);
        if (config.getHungAfterSeconds() > 0L && !config.isLongRunningTasks()) {
            scheduleInternalTimer(config.getHungLoggerInitialDelaySeconds(), config.getHungLoggerIntervalSeconds(), config.isHungLoggerPrintOnce());
        }
        return mes;
    }

    public AbstractManagedExecutorService createManagedScheduledExecutorService(
            ManagedScheduledExecutorServiceCfg config, ContextServiceImpl contextService) {
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        GlassFishManagedThreadFactory managedThreadFactory = new GlassFishManagedThreadFactory(
                toManagedThreadFactoryName(jndiName),
                null,
                config.getThreadPriority());
        AbstractManagedExecutorService mes = null;
        if (config.getUseVirtualThreads()) {
            try {
                mes = new VirtualThreadsManagedScheduledExecutorService(jndiName.toString(),
                        null,
                        config.getHungAfterSeconds() * 1_000L, // in milliseconds
                        config.isLongRunningTasks(),
                        Integer.MAX_VALUE,
                        Integer.MAX_VALUE,
                        contextService,
                        AbstractManagedExecutorService.RejectPolicy.ABORT);
            } catch (Exception e) {
                LOG.severe(() -> "Unable to create ManagedExecutorService with virtual threads: " + e.getMessage() + ", using fallback to platform threads");
            }
        }
        if (mes == null) {
            mes = new ManagedScheduledExecutorServiceImpl(jndiName.toString(),
                    managedThreadFactory,
                    config.getHungAfterSeconds() * 1000L,
                    config.isLongRunningTasks(),
                    config.getCorePoolSize(),
                    config.getKeepAliveSeconds(), TimeUnit.SECONDS,
                    config.getThreadLifeTimeSeconds(),
                    contextService,
                    AbstractManagedExecutorService.RejectPolicy.ABORT);
        }
        return mes;
    }

    public synchronized ManagedThreadFactoryImpl getManagedThreadFactory(ManagedThreadFactoryCfg config) {
        LOG.log(Level.FINE, "getManagedThreadFactory(config={0})", config);
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        if (managedThreadFactoryMap != null && managedThreadFactoryMap.containsKey(jndiName)) {
            return managedThreadFactoryMap.get(jndiName);
        }
        ContextServiceImpl contextService = getContextService(config.getServiceConfig(), true);
        ManagedThreadFactoryImpl managedThreadFactory = createManagedThreadFactory(config, contextService);
        managedThreadFactoryMap.put(jndiName, managedThreadFactory);
        return managedThreadFactory;
    }


    public ManagedThreadFactoryImpl createManagedThreadFactory(ManagedThreadFactoryCfg config, ContextServiceImpl contextService) {
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        if (config.getUseVirtualThreads() && System.getProperty("java.vm.specification.version").compareTo("17") > 0) {
            ManagedThreadFactoryImpl virtFactory = new VirtualThreadsManagedThreadFactory(jndiName.toString(), contextService);
            return virtFactory;
        } else {
            return new GlassFishManagedThreadFactory(jndiName, contextService, config.getThreadPriority());
        }
    }


    public ContextServiceImpl findOrCreateContextService(ContextualResourceDefinition definition, String applicationName, String moduleName) {
        SimpleJndiName jndiName = toContextServiceName(definition.getContext(), definition.getJndiName());
        LOG.log(Level.FINEST, "findOrCreateContextService(jndiName={0}, applicationName={1}, moduleName={2})",
            new Object[] {definition, applicationName, moduleName});
        ResourceInfo contextResourceInfo = new ResourceInfo(jndiName, applicationName, moduleName);
        ContextServiceImpl lookup1 = lookup(contextResourceInfo, jndiName);
        if (lookup1 != null) {
            return lookup1;
        }
        ContextServiceImpl lookup2 = lookup(definition.getContext());
        if (lookup2 != null) {
            return lookup2;
        }
        Set provided = Set.of(Classloader, JNDI, Security, WorkArea);
        ConcurrentServiceCfg config = new ConcurrentServiceCfg(jndiName, provided);
        return contextServiceMap.computeIfAbsent(jndiName, n -> createContextService(jndiName, config, true));
    }


    public synchronized ContextServiceImpl getContextService(ContextServiceCfg config) {
        return contextServiceMap.computeIfAbsent(config.getServiceConfig().getJndiName(),
            n -> createContextService(config));
    }


    public synchronized ContextServiceImpl createContextService(ContextServiceCfg config) {
        LOG.log(Level.FINE, "createContextService(config={0})", config);
        boolean keepTxUnchanged = config.getUnchangedContexts().contains(WorkArea);
        boolean clearTx = config.getClearedContexts().contains(WorkArea);
        TransactionSetupProvider txSetupProvider = createTxSetupProvider(keepTxUnchanged, clearTx);
        ContextSetupProvider ctxSetupProvider = new ContextSetupProviderImpl(config.getPropagatedContexts(),
            config.getClearedContexts(), config.getUnchangedContexts());
        SimpleJndiName jndiName = config.getServiceConfig().getJndiName();
        return new ContextServiceImpl(jndiName.toString(), ctxSetupProvider, txSetupProvider);
    }


    public synchronized ContextServiceImpl getContextService(ConcurrentServiceCfg config, boolean cleanupTransaction) {
        SimpleJndiName contextServiceJndiName = toContextServiceName(config.getContext(), config.getJndiName());
        return contextServiceMap.computeIfAbsent(contextServiceJndiName,
            n -> createContextService(contextServiceJndiName, config, cleanupTransaction));
    }


    /**
     * Shut down the runtime service.
     *
     * @param jndiName
     */
    public void shutdownManagedExecutorService(SimpleJndiName jndiName) {
        AbstractManagedExecutorService mes = removeManagedExecutorService(jndiName);
        if (mes != null) {
            mes.shutdownNow();
        }
    }


    /**
     * Shut down the runtime service.
     *
     * @param jndiName
     */
    public void shutdownScheduledManagedExecutorService(SimpleJndiName jndiName) {
        AbstractManagedExecutorService mses = removeManagedScheduledExecutorService(jndiName);
        if (mses != null) {
            mses.shutdownNow();
        }
    }


    /**
     * Stop the runtime thread factory.
     *
     * @param jndiName
     */
    public void shutdownManagedThreadFactory(SimpleJndiName jndiName) {
        ManagedThreadFactoryImpl mtf = removeManagedThreadFactory(jndiName);
        if (mtf != null) {
            mtf.stop();
        }
    }


    /**
     * Remove the context service from the internal map.
     *
     * @param jndiName
     */
    public synchronized void shutdownContextService(SimpleJndiName jndiName) {
        contextServiceMap.remove(jndiName);
    }


    private ContextServiceImpl createContextService(SimpleJndiName contextServiceJndiName, ConcurrentServiceCfg config,
        boolean cleanupTransaction) {
        LOG.log(Level.FINE, "createContextService(contextServiceJndiName={0}, config={1}, cleanupTransaction={2})",
            new Object[] {contextServiceJndiName, config, cleanupTransaction});
        // if the context service is not known, create it
        final Set propagated = config.getContextInfo();
        final Set cleared;
        final boolean clearTx;
        if (cleanupTransaction && !propagated.contains(WorkArea)) {
            // pass the cleanup transaction in list of cleared handlers
            cleared = Set.of(WorkArea);
            clearTx = true;
        } else {
            cleared = emptySet();
            clearTx = false;
        }
        TransactionSetupProvider txSetupProvider = createTxSetupProvider(false, clearTx);
        ContextSetupProvider ctxSetupProvider = new ContextSetupProviderImpl(propagated, cleared, emptySet());
        return new ContextServiceImpl(contextServiceJndiName.toString(), ctxSetupProvider, txSetupProvider);
    }


    private void scheduleInternalTimer(long initialDelay, long interval, boolean logOnce) {
        if (internalScheduler != null) {
            return;
        }
        final SimpleJndiName name = new SimpleJndiName("Glassfish-Internal");
        ManagedThreadFactoryImpl managedThreadFactory = new GlassFishManagedThreadFactory(
                toManagedThreadFactoryName(name),
                null,
                Thread.NORM_PRIORITY);
        ConcurrentServiceCfg config = new ConcurrentServiceCfg(toContextServiceName(name), Classloader, null);
        ContextServiceImpl contextService = getContextService(config, false);
        internalScheduler = new ManagedScheduledExecutorServiceImpl(name.toString(),
                managedThreadFactory,
                0L,
                false,
                1,
                60, TimeUnit.SECONDS,
                0L,
                contextService,
                AbstractManagedExecutorService.RejectPolicy.ABORT);
        internalScheduler.scheduleAtFixedRate(
            new HungTasksLogger(logOnce), initialDelay, interval, TimeUnit.SECONDS);
    }


    private TransactionSetupProvider createTxSetupProvider(boolean keepTransactionUnchanged, boolean clearTransaction) {
        return new TransactionSetupProviderImpl(transactionManager, keepTransactionUnchanged, clearTransaction);
    }


    private ContextServiceImpl lookup(GenericResourceInfo contextResourceInfo, SimpleJndiName jndiName) {
        try {
            return (ContextServiceImpl) resourceNamingService.lookup(contextResourceInfo, jndiName);
        } catch (NamingException e) {
            return null;
        }
    }


    private ContextServiceImpl lookup(String jndiName) {
        LOG.log(Level.FINEST, "lookup(jndiName={0})", jndiName);
        try {
            return InitialContext.doLookup(jndiName);
        } catch (Exception e) {
            return null;
        }
    }


    private synchronized AbstractManagedExecutorService removeManagedExecutorService(SimpleJndiName jndiName) {
        return managedExecutorServiceMap.remove(jndiName);
    }


    private synchronized AbstractManagedExecutorService removeManagedScheduledExecutorService(SimpleJndiName jndiName) {
        return managedScheduledExecutorServiceMap.remove(jndiName);
    }


    private synchronized ManagedThreadFactoryImpl removeManagedThreadFactory(SimpleJndiName jndiName) {
        return managedThreadFactoryMap.remove(jndiName);
    }


    private static SimpleJndiName toContextServiceName(String configuredContextJndiName, SimpleJndiName parentObjectJndiName) {
        return configuredContextJndiName == null ? toContextServiceName(parentObjectJndiName)
            : new SimpleJndiName(configuredContextJndiName);
    }


    private static SimpleJndiName toContextServiceName(final SimpleJndiName jndiName) {
        return new SimpleJndiName(jndiName + "-ContextService");
    }


    private static SimpleJndiName toManagedThreadFactoryName(SimpleJndiName jndiName) {
        return new SimpleJndiName(jndiName + "-ManagedThreadFactory");
    }


    class HungTasksLogger implements Runnable {

        private final Boolean logOnce;
        private final Map> cachedHungThreadsMap = new HashMap<>();

        HungTasksLogger(Boolean logOnce) {
            this.logOnce = logOnce;
        }


        @Override
        public void run() {
            ArrayList executorServices = new ArrayList<>();
            synchronized (ConcurrentRuntime.this) {
                if (managedExecutorServiceMap != null) {
                    Collection mesColl = managedExecutorServiceMap.values();
                    executorServices.addAll(mesColl);
                }
                if (managedScheduledExecutorServiceMap != null) {
                    Collection msesColl = managedScheduledExecutorServiceMap.values();
                    executorServices.addAll(msesColl);
                }
            }
            for (AbstractManagedExecutorService mes : executorServices) {
                Collection hungThreads = mes.getHungThreads();
                logHungThreads(hungThreads, mes.getManagedThreadFactory(), mes.getName());
            }
        }


        private void logHungThreads(Collection hungThreads, ManagedThreadFactoryImpl mtf,
            String mesName) {
            if (!logOnce) {
                logRawHungThreads(hungThreads, mtf, mesName);
                return;
            }
            if (hungThreads == null) {
                cachedHungThreadsMap.remove(mesName);
                return;
            }
            Collection targetHungThreads = new HashSet<>();
            targetHungThreads.addAll(hungThreads);
            Collection cachedHungThreads = cachedHungThreadsMap.get(mesName);
            if (cachedHungThreads != null) {
                targetHungThreads.removeAll(cachedHungThreads);
            }
            logRawHungThreads(targetHungThreads, mtf, mesName);
            cachedHungThreadsMap.put(mesName, hungThreads);
        }


        private void logRawHungThreads(Collection hungThreads, ManagedThreadFactoryImpl mtf,
            String mesName) {
            if (hungThreads != null) {
                for (Thread hungThread : hungThreads) {

                    String taskIdentityName = "virtual";
                    long taskRunTime = 0l;
                    if (hungThread instanceof AbstractManagedThread managedThread) {
                        taskIdentityName = managedThread.getTaskIdentityName();
                        taskRunTime = managedThread.getTaskRunTime(System.currentTimeMillis()) / 1000;
                    }

                    Object[] params = {taskIdentityName, hungThread.getName(),
                            taskRunTime, mtf.getHungTaskThreshold() / 1000,
                        mesName};
                    LOG.log(Level.WARNING, LogFacade.UNRESPONSIVE_TASK, params);
                }
            }
        }
    }

    private static class GlassFishManagedThreadFactory extends ManagedThreadFactoryImpl {

        private static final Logger LOG = Logger.getLogger(GlassFishManagedThreadFactory.class.getName());

        GlassFishManagedThreadFactory(SimpleJndiName name, ContextServiceImpl contextService, int threadPriority) {
            super(name.toString(), contextService, threadPriority);
        }


        @Override
        protected Thread createThread(Runnable runnable, ContextHandle contextHandleForSetup) {
            LOG.log(Level.FINE, "createThread(runnable={0}, contextHandleForSetup={1})",
                new Object[] {runnable, contextHandleForSetup});
            Thread thread = Thread.currentThread();
            ClassLoader originalCL = thread.getContextClassLoader();
            thread.setContextClassLoader(null);
            try {
                return super.createThread(runnable, contextHandleForSetup);
            } finally {
                thread.setContextClassLoader(originalCL);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy