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

mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git

commit bccb166adeca4e2a6f1e5a06d388c1c4bf302eda
Author: Mridul Pathak <[email protected]>
AuthorDate: Mon Oct 19 21:35:55 2026 +0530

    Improved: Prevent the datasources from being reopened during shutdown 
(OFBIZ-13686)
    
    A thread still running after the datasources are closed can no longer 
reopen the embedded H2 database and leave it unclosed.
    
    - DBCPConnectionFactory.getConnection() fails once closeAll() has run, 
instead of creating a new pool.
    - JobPoller.stop() waits up to 5 seconds for the interrupted jobs to end 
and return their connections before the datasources get closed.
    - DOCKER.adoc warns that H2 is not closed when docker stop kills the 
container before OFBiz has finished shutting down, and how to give it more time.
    
    Thanks: Jacopo Cappellato (jacopoc) for the review and suggestion
---
 DOCKER.adoc                                                  |  7 +++++++
 .../ofbiz/entity/connection/DBCPConnectionFactory.java       | 12 ++++++++++++
 .../main/java/org/apache/ofbiz/service/job/JobPoller.java    | 10 ++++++++++
 3 files changed, 29 insertions(+)

diff --git a/DOCKER.adoc b/DOCKER.adoc
index 001ecd49b7..d7bb598986 100644
--- a/DOCKER.adoc
+++ b/DOCKER.adoc
@@ -169,6 +169,13 @@ variables.
 To use the embedded H2 database, ensure all database related
 environment variables are unset.
 
+OFBiz closes the H2 database at the end of its shutdown. `+docker stop+`
+kills the container if it has not stopped within 10 seconds, in which case
+H2 is not closed and can lose recently committed data the next time it is
+opened. If the shutdown can take longer, for instance while jobs are still
+running, give the container more time with `+docker stop -t <seconds>+`, or
+`+stop_grace_period+` with Docker Compose.
+
 ===== PostgreSQL
 
 To use a Postgres database set the `+OFBIZ_POSTGRES_HOST+` environment
diff --git 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
index ecad030d93..0212b57649 100644
--- 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
+++ 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
@@ -58,10 +58,15 @@ public class DBCPConnectionFactory implements 
ConnectionFactory {
     // from DebugManagedDataSource
     protected static final ConcurrentHashMap<String, DebugManagedDataSource<? 
extends Connection>> DS_CACHE =
             new ConcurrentHashMap<>();
+    // set by closeAll() when OFBiz shuts down, so that no new pool gets 
created afterwards (see OFBIZ-13686)
+    private static volatile boolean closed = false;
 
     @SuppressWarnings("deprecation")
     @Override
     public Connection getConnection(GenericHelperInfo helperInfo, JdbcElement 
abstractJdbc) throws SQLException, GenericEntityException {
+        if (closed) {
+            throw new SQLException("The datasources have been closed, OFBiz is 
shutting down");
+        }
         String cacheKey = helperInfo.getHelperFullName();
         DebugManagedDataSource<? extends Connection> mds = 
DS_CACHE.get(cacheKey);
         if (mds != null) {
@@ -164,12 +169,19 @@ public class DBCPConnectionFactory implements 
ConnectionFactory {
         // cache the pool
         DS_CACHE.putIfAbsent(cacheKey, mds);
         mds = DS_CACHE.get(cacheKey);
+        if (closed) {
+            // closeAll() ran while this pool was being created
+            DS_CACHE.remove(cacheKey, mds);
+            mds.close();
+            throw new SQLException("The datasources have been closed, OFBiz is 
shutting down");
+        }
 
         return TransactionUtil.getCursorConnection(helperInfo, 
mds.getConnection());
     }
 
     @Override
     public void closeAll() {
+        closed = true;
         // Closing the pools closes every pooled connection, which is what 
lets an embedded database such as H2
         // close its files cleanly: H2 only does so when its last connection 
is closed (see OFBIZ-13686).
         DS_CACHE.forEach((helperName, mds) -> {
diff --git 
a/framework/service/src/main/java/org/apache/ofbiz/service/job/JobPoller.java 
b/framework/service/src/main/java/org/apache/ofbiz/service/job/JobPoller.java
index 7b1b55f584..c41e9022f2 100644
--- 
a/framework/service/src/main/java/org/apache/ofbiz/service/job/JobPoller.java
+++ 
b/framework/service/src/main/java/org/apache/ofbiz/service/job/JobPoller.java
@@ -52,6 +52,7 @@ public final class JobPoller implements ServiceConfigListener 
{
     private static final ConcurrentHashMap<String, JobManager> JOB_MANAGERS = 
new ConcurrentHashMap<>();
     private static final ThreadPoolExecutor EXECUTOR = 
createThreadPoolExecutor();
     private static final JobPoller INSTANCE = new JobPoller();
+    private static final long SHUTDOWN_TIMEOUT_SECONDS = 5;
 
     /**
      * Returns the <code>JobPoller</code> instance.
@@ -252,6 +253,15 @@ public final class JobPoller implements 
ServiceConfigListener {
                 Debug.logWarning(e, MODULE);
             }
         }
+        // Give the interrupted jobs a short time to end and return their 
connections before the datasources get closed
+        // (see OFBIZ-13686); the wait is kept short so that a container 
runtime does not kill OFBiz before that
+        try {
+            if (!EXECUTOR.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, 
TimeUnit.SECONDS)) {
+                Debug.logWarning("Some jobs were still running after " + 
SHUTDOWN_TIMEOUT_SECONDS + " seconds", MODULE);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
         Debug.logInfo("JobPoller shutdown completed.", MODULE);
     }
 

Reply via email to