From 4bc755e81802be3ff45f3dd46123f88f62af652a Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Wed, 12 Aug 2026 09:57:58 +0200 Subject: [PATCH] Allowing stateful service loaders Service loaders might be stateful, so caching them might be an isssue. Since internally serviceloader class already caches the classpath search, the right solution is to store a reference to the ServiceLoader itself and call stream for every invocation. That way, we have the best of both world, fresh new instance of the service loader class for every invocation (supporting stateful) and avoid the classpath search for every invocation (the original performance issue to be fixed) Signed-off-by: Francisco Javier Tirado Sarti --- .../impl/WorkflowApplication.java | 26 +++++++++---------- .../executors/DefaultTaskExecutorFactory.java | 17 ++++++------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index 6675902cb..e393dcda0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -114,7 +114,7 @@ public class WorkflowApplication implements AutoCloseable { private final WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory; private final ScheduledExecutorService schedulerExecutorService; private final Set allowedCommands; - private final Map, List> serviceLoadedClasses = new ConcurrentHashMap<>(); + private final Map, ServiceLoader> servicesLoaded = new ConcurrentHashMap<>(); private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; @@ -712,21 +712,19 @@ public Set allowedCommands() { @SuppressWarnings("unchecked") public > List serviceLoadedClasses(Class clazz) { - return (List) - serviceLoadedClasses.computeIfAbsent( - clazz, - c -> - ServiceLoader.load(clazz).stream() - .map(ServiceLoader.Provider::get) - .sorted() - .toList()); + ServiceLoader serviceLoader = servicesLoaded.computeIfAbsent(clazz, ServiceLoader::load); + return (List) serviceLoader.stream().map(ServiceLoader.Provider::get).sorted().toList(); } public > T serviceLoadedClass(Class serviceClass) { - List list = serviceLoadedClasses(serviceClass); - if (list.isEmpty()) { - throw new IllegalStateException("No " + serviceClass + " implementation found"); - } - return list.get(0); + ServiceLoader serviceLoader = + servicesLoaded.computeIfAbsent(serviceClass, ServiceLoader::load); + return (T) + serviceLoader.stream() + .map(ServiceLoader.Provider::get) + .sorted() + .findFirst() + .orElseThrow( + () -> new IllegalStateException("No " + serviceClass + " implementation found")); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java index 55363ac99..85a198d9b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java @@ -18,6 +18,7 @@ import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowMutablePosition; import io.serverlessworkflow.impl.executors.CallTaskExecutor.CallTaskExecutorBuilder; @@ -32,9 +33,7 @@ import io.serverlessworkflow.impl.executors.SwitchExecutor.SwitchExecutorBuilder; import io.serverlessworkflow.impl.executors.TryExecutor.TryExecutorBuilder; import io.serverlessworkflow.impl.executors.WaitExecutor.WaitExecutorBuilder; -import java.util.Collection; -import java.util.ServiceLoader; -import java.util.ServiceLoader.Provider; +import java.util.List; public class DefaultTaskExecutorFactory implements TaskExecutorFactory { @@ -46,9 +45,6 @@ public static TaskExecutorFactory get() { protected DefaultTaskExecutorFactory() {} - private Collection callTasks = - ServiceLoader.load(CallableTaskBuilder.class).stream().map(Provider::get).sorted().toList(); - @Override public TaskExecutorBuilder getTaskExecutor( WorkflowMutablePosition position, Task task, WorkflowDefinition definition) { @@ -57,7 +53,10 @@ public TaskExecutorBuilder getTaskExecutor( TaskBase taskBase = (TaskBase) callTask.get(); if (taskBase != null) { return new CallTaskExecutorBuilder( - position, taskBase, definition, findCallTask(taskBase.getClass())); + position, + taskBase, + definition, + findCallTask(taskBase.getClass(), definition.application())); } } else if (task.getSwitchTask() != null) { return new SwitchExecutorBuilder(position, task.getSwitchTask(), definition); @@ -86,7 +85,9 @@ public TaskExecutorBuilder getTaskExecutor( } @SuppressWarnings("unchecked") - private CallableTaskBuilder findCallTask(Class clazz) { + private CallableTaskBuilder findCallTask( + Class clazz, WorkflowApplication app) { + List callTasks = app.serviceLoadedClasses(CallableTaskBuilder.class); return (CallableTaskBuilder) callTasks.stream() .filter(s -> s.accept(clazz))