This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch WW-3691-executor
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 47398e8a784deb9a71e5f84ddfc3dbe1e5f2f8d4
Author: Lukasz Lenart <[email protected]>
AuthorDate: Tue Oct 4 10:12:52 2022 +0200

    WW-3691 Converts BackgroundProcess into interface and uses Executor to 
execute BackgroundProcess
---
 .../showcase/wait/ThreadPoolExecutorProvider.java  |  56 +++++++++++
 apps/showcase/src/main/resources/struts-wait.xml   |   3 +
 .../interceptor/ExecuteAndWaitInterceptor.java     |  66 +++++++++----
 .../interceptor/exec/BackgroundProcess.java        |  41 ++++++++
 .../struts2/interceptor/exec/ExecutorProvider.java |  38 ++++++++
 .../StrutsBackgroundProcess.java}                  |  77 ++++++++++-----
 .../interceptor/exec/StrutsExecutorProvider.java   |  53 +++++++++++
 .../interceptor/ExecuteAndWaitInterceptorTest.java |  46 ++++++++-
 .../StrutsBackgroundProcessTest.java}              | 104 +++++++++++++++++----
 9 files changed, 419 insertions(+), 65 deletions(-)

diff --git 
a/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java
 
b/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java
new file mode 100644
index 000000000..ffffc7a1d
--- /dev/null
+++ 
b/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.showcase.wait;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts2.interceptor.exec.ExecutorProvider;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPoolExecutorProvider implements ExecutorProvider {
+
+    private static final Logger LOG = 
LogManager.getLogger(ThreadPoolExecutorProvider.class);
+
+    private final ExecutorService executor;
+
+    public ThreadPoolExecutorProvider() {
+        this.executor = new ThreadPoolExecutor(1, 2, 0L, 
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
+    }
+
+    @Override
+    public void execute(Runnable task) {
+        LOG.info("Executing task: {}", task);
+        executor.execute(task);
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return executor.isShutdown();
+    }
+
+    @Override
+    public void shutdown() {
+        LOG.info("Shutting down executor");
+        executor.shutdown();
+    }
+}
diff --git a/apps/showcase/src/main/resources/struts-wait.xml 
b/apps/showcase/src/main/resources/struts-wait.xml
index 8ede40d74..7b6a204a6 100644
--- a/apps/showcase/src/main/resources/struts-wait.xml
+++ b/apps/showcase/src/main/resources/struts-wait.xml
@@ -24,6 +24,9 @@
        "https://struts.apache.org/dtds/struts-2.5.dtd";>
 
 <struts>
+
+    <bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" 
class="org.apache.struts2.showcase.wait.ThreadPoolExecutorProvider"/>
+
     <package name="wait" extends="struts-default" namespace="/wait">
 
         <action name="example1">
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java
 
b/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java
index b49ebcae7..4a5cb91b4 100644
--- 
a/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java
@@ -22,12 +22,17 @@ import com.opensymphony.xwork2.Action;
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
 import com.opensymphony.xwork2.ActionProxy;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
 import com.opensymphony.xwork2.inject.Container;
 import com.opensymphony.xwork2.inject.Inject;
 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.struts2.ServletActionContext;
+import org.apache.struts2.interceptor.exec.BackgroundProcess;
+import org.apache.struts2.interceptor.exec.ExecutorProvider;
+import org.apache.struts2.interceptor.exec.StrutsBackgroundProcess;
+import org.apache.struts2.interceptor.exec.StrutsExecutorProvider;
 import org.apache.struts2.util.TokenHelper;
 import org.apache.struts2.views.freemarker.FreemarkerResult;
 
@@ -84,7 +89,7 @@ import java.util.Map;
  * <!-- END SNIPPET: description -->
  *
  * <p><u>Interceptor parameters:</u></p>
- *
+ * <p>
  * <!-- START SNIPPET: parameters -->
  *
  * <ul>
@@ -94,11 +99,11 @@ import java.util.Map;
  * <li>delaySleepInterval (optional) - only used with delay. Used for waking 
up at certain intervals to check if the background process is already done. 
Default is 100 millis.</li>
  *
  * </ul>
- *
+ * <p>
  * <!-- END SNIPPET: parameters -->
  *
  * <p><u>Extending the interceptor:</u></p>
- *
+ * <p>
  * <!-- START SNIPPET: extending -->
  * <p>
  * If you wish to make special preparations before and/or after the invocation 
of the background thread, you can extend
@@ -167,9 +172,8 @@ import java.util.Map;
  *     &lt;result 
name="success"&gt;longRunningAction-success.jsp&lt;/result&gt;
  * &lt;/action&gt;
  * </pre>
- *
+ * <p>
  * <!-- END SNIPPET: example -->
- *
  */
 public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
 
@@ -186,22 +190,28 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
     private int threadPriority = Thread.NORM_PRIORITY;
 
     private Container container;
+    private ExecutorProvider executor;
 
     @Inject
     public void setContainer(Container container) {
         this.container = container;
     }
 
+    @Inject(required = false)
+    public void setExecutorProvider(ExecutorProvider executorProvider) {
+        this.executor = executorProvider;
+    }
+
     /**
      * Creates a new background process
      *
-     * @param name The process name
+     * @param name             The process name
      * @param actionInvocation The action invocation
-     * @param threadPriority The thread priority
+     * @param threadPriority   The thread priority
      * @return The new process
      */
     protected BackgroundProcess getNewBackgroundProcess(String name, 
ActionInvocation actionInvocation, int threadPriority) {
-        return new BackgroundProcess(name + "BackgroundThread", 
actionInvocation, threadPriority);
+        return new StrutsBackgroundProcess(actionInvocation, name + 
"_background-process", threadPriority);
     }
 
     /**
@@ -209,7 +219,6 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
      * are mapped to requests.
      *
      * @param proxy action proxy
-     *
      * @return the name of the background thread
      */
     protected String getBackgroundProcessName(ActionProxy proxy) {
@@ -223,10 +232,10 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
         ActionProxy proxy = actionInvocation.getProxy();
         String name = getBackgroundProcessName(proxy);
         ActionContext context = actionInvocation.getInvocationContext();
-        Map session = context.getSession();
+        Map<String, Object> session = context.getSession();
         HttpSession httpSession = 
ServletActionContext.getRequest().getSession(true);
 
-        Boolean secondTime  = true;
+        Boolean secondTime = true;
         if (executeAfterValidationPass) {
             secondTime = (Boolean) context.get(KEY);
             if (secondTime == null) {
@@ -250,8 +259,13 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
             }
 
             if ((!executeAfterValidationPass || secondTime) && bp == null) {
-                bp = getNewBackgroundProcess(name, actionInvocation, 
threadPriority);
+                bp = getNewBackgroundProcess(name, actionInvocation, 
threadPriority).prepare();
                 session.put(KEY + name, bp);
+                if (executor.isShutdown()) {
+                    LOG.warn("Executor is shutting down, cannot execute a new 
process");
+                    return actionInvocation.invoke();
+                }
+                executor.execute(bp);
                 performInitialDelay(bp); // first time let some time pass 
before showing wait page
                 secondTime = false;
             }
@@ -259,16 +273,16 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
             if ((!executeAfterValidationPass || !secondTime) && bp != null && 
!bp.isDone()) {
                 actionInvocation.getStack().push(bp.getAction());
 
-                               final String token = TokenHelper.getToken();
-                               if (token != null) {
-                                       
TokenHelper.setSessionToken(TokenHelper.getTokenName(), token);
+                final String token = TokenHelper.getToken();
+                if (token != null) {
+                    TokenHelper.setSessionToken(TokenHelper.getTokenName(), 
token);
                 }
 
-                Map results = proxy.getConfig().getResults();
+                Map<String, ResultConfig> results = 
proxy.getConfig().getResults();
                 if (!results.containsKey(WAIT)) {
-                       LOG.warn("ExecuteAndWait interceptor has detected that 
no result named 'wait' is available. " +
-                            "Defaulting to a plain built-in wait page. It is 
highly recommend you " +
-                            "provide an action-specific or global result named 
'{}'.", WAIT);
+                    LOG.warn("ExecuteAndWait interceptor has detected that no 
result named 'wait' is available. " +
+                        "Defaulting to a plain built-in wait page. It is 
highly recommend you " +
+                        "provide an action-specific or global result named 
'{}'.", WAIT);
                     // no wait result? hmm -- let's try to do dynamically put 
it in for you!
 
                     //we used to add a fake "wait" result here, since the 
configuration is unmodifiable, that is no longer
@@ -286,7 +300,7 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
                 session.remove(KEY + name);
                 actionInvocation.getStack().push(bp.getAction());
 
-                // if an exception occured during action execution, throw it 
here
+                // if an exception occurred during action execution, throw it 
here
                 if (bp.getException() != null) {
                     throw bp.getException();
                 }
@@ -369,5 +383,17 @@ public class ExecuteAndWaitInterceptor extends 
MethodFilterInterceptor {
         this.executeAfterValidationPass = executeAfterValidationPass;
     }
 
+    @Override
+    public void init() {
+        super.init();
+        if (executor == null) {
+            executor = new StrutsExecutorProvider();
+        }
+    }
 
+    @Override
+    public void destroy() {
+        super.destroy();
+        executor.shutdown();
+    }
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java 
b/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java
new file mode 100644
index 000000000..732c3d0af
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.interceptor.exec;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.struts2.interceptor.ExecuteAndWaitInterceptor;
+
+/**
+ * Interface used to create a background process which will be executed by
+ * {@link ExecuteAndWaitInterceptor}
+ */
+public interface BackgroundProcess extends Runnable {
+
+    BackgroundProcess prepare();
+
+    Object getAction();
+
+    ActionInvocation getInvocation();
+
+    String getResult();
+
+    Exception getException();
+
+    boolean isDone();
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java 
b/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java
new file mode 100644
index 000000000..a6b06bde9
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.interceptor.exec;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Interface mimics {@link ExecutorService} to be used with
+ * {@link org.apache.struts2.interceptor.ExecuteAndWaitInterceptor}
+ * to execute {@link BackgroundProcess}
+ *
+ * @since 6.1.0
+ */
+public interface ExecutorProvider {
+
+    void execute(Runnable task);
+
+    boolean isShutdown();
+
+    void shutdown();
+
+}
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java 
b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java
similarity index 65%
rename from 
core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java
rename to 
core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java
index eed1811e0..1b29129b8 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java
@@ -16,21 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.struts2.interceptor;
-
-import java.io.Serializable;
+package org.apache.struts2.interceptor.exec;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.ActionInvocation;
+import org.apache.struts2.interceptor.exec.BackgroundProcess;
+
+import java.io.Serializable;
 
 /**
  * Background thread to be executed by the ExecuteAndWaitInterceptor.
- *
  */
-public class BackgroundProcess implements Serializable {
+public class StrutsBackgroundProcess implements BackgroundProcess, 
Serializable {
 
     private static final long serialVersionUID = 3884464776311686443L;
 
+    private final String threadName;
+    private final int threadPriority;
+
+    transient private Thread processThread;
     //WW-4900 transient since 2.5.15
     transient protected ActionInvocation invocation;
     transient protected Exception exception;
@@ -41,32 +45,47 @@ public class BackgroundProcess implements Serializable {
     /**
      * Constructs a background process
      *
-     * @param threadName The thread name
      * @param invocation The action invocation
-     * @param threadPriority The thread priority
+     * @param threadName The name of background thread
+     * @param threadPriority The priority of background thread
      */
-    public BackgroundProcess(String threadName, final ActionInvocation 
invocation, int threadPriority) {
+    public StrutsBackgroundProcess(ActionInvocation invocation, String 
threadName, int threadPriority) {
         this.invocation = invocation;
+        this.threadName = threadName;
+        this.threadPriority = threadPriority;
+    }
+
+    @Override
+    public BackgroundProcess prepare() {
         try {
-            final Thread t = new Thread(new Runnable() {
-                public void run() {
-                    try {
-                        beforeInvocation();
-                        result = invocation.invokeActionOnly();
-                        afterInvocation();
-                    } catch (Exception e) {
-                        exception = e;
-                    }
-
-                    done = true;
+            processThread = new Thread(() -> {
+                try {
+                    beforeInvocation();
+                    result = invocation.invokeActionOnly();
+                    afterInvocation();
+                } catch (Exception e) {
+                    exception = e;
                 }
+
+                done = true;
             });
-            t.setName(threadName);
-            t.setPriority(threadPriority);
-            t.start();
+            processThread.setName(threadName);
+            processThread.setPriority(threadPriority);
         } catch (Exception e) {
+            done = true;
             exception = e;
         }
+        return this;
+    }
+
+    @Override
+    public void run() {
+        if (processThread == null) {
+            done = true;
+            exception = new IllegalStateException("Background thread " + 
threadName + " has not been prepared!");
+            return;
+        }
+        processThread.start();
     }
 
     /**
@@ -93,8 +112,9 @@ public class BackgroundProcess implements Serializable {
     /**
      * Retrieves the action.
      *
-     * @return  the action.
+     * @return the action.
      */
+    @Override
     public Object getAction() {
         return invocation.getAction();
     }
@@ -104,6 +124,7 @@ public class BackgroundProcess implements Serializable {
      *
      * @return the action invocation
      */
+    @Override
     public ActionInvocation getInvocation() {
         return invocation;
     }
@@ -111,8 +132,9 @@ public class BackgroundProcess implements Serializable {
     /**
      * Gets the result of the background process.
      *
-     * @return  the result; <tt>null</tt> if not done.
+     * @return the result; <tt>null</tt> if not done.
      */
+    @Override
     public String getResult() {
         return result;
     }
@@ -122,6 +144,7 @@ public class BackgroundProcess implements Serializable {
      *
      * @return the exception or <tt>null</tt> if no exception was thrown.
      */
+    @Override
     public Exception getException() {
         return exception;
     }
@@ -131,7 +154,13 @@ public class BackgroundProcess implements Serializable {
      *
      * @return <tt>true</tt> if finished, <tt>false</tt> otherwise
      */
+    @Override
     public boolean isDone() {
         return done;
     }
+
+    @Override
+    public String toString() {
+        return "StrutsBackgroundProcess { name = " + processThread.getName() + 
" }";
+    }
 }
diff --git 
a/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java
 
b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java
new file mode 100644
index 000000000..7370f318a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.interceptor.exec;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class StrutsExecutorProvider implements ExecutorProvider {
+
+    private static final Logger LOG = 
LogManager.getLogger(StrutsExecutorProvider.class);
+
+    private final ExecutorService executor;
+
+    public StrutsExecutorProvider() {
+        this.executor = Executors.newSingleThreadExecutor();
+    }
+
+    @Override
+    public void execute(Runnable task) {
+        LOG.debug("Executing task: {}", task);
+        executor.execute(task);
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return executor.isShutdown();
+    }
+
+    @Override
+    public void shutdown() {
+        LOG.debug("Shutting down executor");
+        executor.shutdown();
+    }
+}
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java
 
b/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java
index 08c034b9b..87595d9b5 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java
@@ -36,9 +36,9 @@ import 
com.opensymphony.xwork2.interceptor.ParametersInterceptor;
 import com.opensymphony.xwork2.mock.MockResult;
 import com.opensymphony.xwork2.ognl.OgnlUtil;
 import com.opensymphony.xwork2.util.location.LocatableProperties;
-import org.apache.struts2.ServletActionContext;
 import org.apache.struts2.StrutsInternalTestCase;
 import org.apache.struts2.dispatcher.HttpParameters;
+import org.apache.struts2.interceptor.exec.ExecutorProvider;
 import org.apache.struts2.views.jsp.StrutsMockHttpServletRequest;
 import org.apache.struts2.views.jsp.StrutsMockHttpSession;
 
@@ -49,6 +49,8 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * Test case for ExecuteAndWaitInterceptor.
@@ -78,6 +80,20 @@ public class ExecuteAndWaitInterceptorTest extends 
StrutsInternalTestCase {
         assertEquals("success", result2);
     }
 
+    public void testExecutorProvider() throws Exception {
+        waitInterceptor.setExecutorProvider(new TestExecutorProvider());
+
+        ActionProxy proxy = buildProxy("action1");
+        String result = proxy.execute();
+        assertEquals("wait", result);
+
+        Thread.sleep(1000);
+
+        ActionProxy proxy2 = buildProxy("action1");
+        String result2 = proxy2.execute();
+        assertEquals("success", result2);
+    }
+
     public void testTwoWait() throws Exception {
         waitInterceptor.setDelay(0);
         waitInterceptor.setDelaySleepInterval(0);
@@ -226,7 +242,11 @@ public class ExecuteAndWaitInterceptorTest extends 
StrutsInternalTestCase {
             .withServletRequest(request)
             .getContextMap();
 
+        container.inject(waitInterceptor);
         container.inject(parametersInterceptor);
+
+        waitInterceptor.init();
+        parametersInterceptor.init();
     }
 
     protected void tearDown() throws Exception {
@@ -250,8 +270,6 @@ public class ExecuteAndWaitInterceptorTest extends 
StrutsInternalTestCase {
         }
 
         public void loadPackages() throws ConfigurationException {
-
-
             // interceptors
             waitInterceptor = new ExecuteAndWaitInterceptor();
             parametersInterceptor = new ParametersInterceptor();
@@ -273,5 +291,27 @@ public class ExecuteAndWaitInterceptorTest extends 
StrutsInternalTestCase {
         }
 
     }
+
 }
 
+class TestExecutorProvider implements ExecutorProvider {
+
+    private final ExecutorService executor = 
Executors.newSingleThreadExecutor();
+
+    @Override
+    public void execute(Runnable task) {
+        executor.execute(task);
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return executor.isShutdown();
+    }
+
+    @Override
+    public void shutdown() {
+        executor.shutdown();
+    }
+}
+
+
diff --git 
a/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java 
b/core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java
similarity index 50%
rename from 
core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java
rename to 
core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java
index b811b1dd5..68572c8d1 100644
--- 
a/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java
+++ 
b/core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java
@@ -16,10 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.struts2.interceptor;
+package org.apache.struts2.interceptor.exec;
 
 import com.mockobjects.servlet.MockHttpServletRequest;
 import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.ActionInvocation;
 import com.opensymphony.xwork2.mock.MockActionInvocation;
 import org.apache.struts2.StrutsInternalTestCase;
 
@@ -27,31 +28,43 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Test case for BackgroundProcessTest.
  */
-public class BackgroundProcessTest extends StrutsInternalTestCase {
+public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
+
+    private final ExecutorService executor = 
Executors.newSingleThreadExecutor();
 
     public void testSerializeDeserialize() throws Exception {
         final NotSerializableException expectedException = new 
NotSerializableException(new MockHttpServletRequest());
         final Semaphore lock = new Semaphore(1);
         lock.acquire();
-        MockActionInvocationWithActionInvoker invocation = new 
MockActionInvocationWithActionInvoker(new Callable<String>() {
-            @Override
-            public String call() throws Exception {
-                lock.release();
-                throw expectedException;
-            }
+        MockActionInvocationWithActionInvoker invocation = new 
MockActionInvocationWithActionInvoker(() -> {
+            lock.release();
+            throw expectedException;
         });
         invocation.setInvocationContext(ActionContext.getContext());
 
-        BackgroundProcess bp = new 
BackgroundProcess("BackgroundProcessTest.testSerializeDeserialize", invocation
-                , Thread.MIN_PRIORITY);
-        if(!lock.tryAcquire(1500L, TimeUnit.MILLISECONDS)) {
+        StrutsBackgroundProcess bp = (StrutsBackgroundProcess) new 
StrutsBackgroundProcess(
+            invocation,
+            "BackgroundProcessTest.testSerializeDeserialize",
+            Thread.MIN_PRIORITY
+        )
+            .prepare();
+        executor.execute(bp);
+
+        if (!lock.tryAcquire(1500L, TimeUnit.MILLISECONDS)) {
             lock.release();
             fail("background thread did not release lock on timeout");
         }
@@ -66,12 +79,12 @@ public class BackgroundProcessTest extends 
StrutsInternalTestCase {
         ObjectOutputStream oos = new ObjectOutputStream(baos);
         oos.writeObject(bp);
         oos.close();
-        byte b[] = baos.toByteArray();
+        byte[] b = baos.toByteArray();
         baos.close();
 
         ByteArrayInputStream bais = new ByteArrayInputStream(b);
         ObjectInputStream ois = new ObjectInputStream(bais);
-        BackgroundProcess deserializedBp = (BackgroundProcess) 
ois.readObject();
+        StrutsBackgroundProcess deserializedBp = (StrutsBackgroundProcess) 
ois.readObject();
         ois.close();
         bais.close();
 
@@ -81,11 +94,44 @@ public class BackgroundProcessTest extends 
StrutsInternalTestCase {
         assertEquals(bp.done, deserializedBp.done);
     }
 
+    public void testMultipleProcesses() throws InterruptedException {
+        MockActionInvocationWithActionInvoker invocation = new 
MockActionInvocationWithActionInvoker(() -> {
+            Thread.sleep(150);
+            return "done";
+        });
+
+        List<BackgroundProcess> bps = new ArrayList<>();
+        for (int i = 0; i < 10000; i++) {
+            String name = String.format("Order: %s", i);
+            BackgroundProcess bp = new LockBackgroundProcess(invocation, 
name).prepare();
+            bps.add(bp);
+            executor.execute(bp);
+        }
 
-    private class MockActionInvocationWithActionInvoker extends 
MockActionInvocation {
-        private Callable<String> actionInvoker;
+        Thread.sleep(150);
 
-        MockActionInvocationWithActionInvoker(Callable<String> actionInvoker){
+        for (BackgroundProcess bp : bps) {
+            assertTrue("Process is still active: " + bp.toString(), 
bp.isDone());
+        }
+    }
+
+    public void testUnpreparedProcess() throws ExecutionException, 
InterruptedException, TimeoutException {
+        // given
+        MockActionInvocationWithActionInvoker invocation = new 
MockActionInvocationWithActionInvoker(() -> "done");
+        BackgroundProcess bp = new StrutsBackgroundProcess(invocation, 
"Unprepared", Thread.NORM_PRIORITY);
+
+        // when
+        executor.submit(bp).get(1000, TimeUnit.MILLISECONDS);
+
+        // then
+        assertTrue(bp.isDone());
+        assertEquals("Background thread Unprepared has not been prepared!", 
bp.getException().getMessage());
+    }
+
+    private static class MockActionInvocationWithActionInvoker extends 
MockActionInvocation {
+        private final Callable<String> actionInvoker;
+
+        MockActionInvocationWithActionInvoker(Callable<String> actionInvoker) {
             this.actionInvoker = actionInvoker;
         }
 
@@ -95,10 +141,32 @@ public class BackgroundProcessTest extends 
StrutsInternalTestCase {
         }
     }
 
-    private class NotSerializableException extends Exception {
+    private static class NotSerializableException extends Exception {
         private MockHttpServletRequest notSerializableField;
+
         NotSerializableException(MockHttpServletRequest notSerializableField) {
             this.notSerializableField = notSerializableField;
         }
     }
-}
\ No newline at end of file
+
+}
+
+class LockBackgroundProcess extends StrutsBackgroundProcess {
+
+    private final Object lock = LockBackgroundProcess.class;
+
+    public LockBackgroundProcess(ActionInvocation invocation, String name) {
+        super(invocation, name, Thread.NORM_PRIORITY);
+    }
+
+    @Override
+    public void run() {
+        synchronized (lock) {
+            try {
+                super.run();
+            } finally {
+                lock.notify();
+            }
+        }
+    }
+}

Reply via email to