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 5d8d7924f8851d3eae0cf93c068676182e965822
Author: Mridul Pathak <[email protected]>
AuthorDate: Thu Oct 15 19:19:46 2026 +0530

    Fixed: Close the embedded H2 database cleanly on shutdown (OFBIZ-13686)
    
    OFBiz never closed its datasources, so with DB_CLOSE_ON_EXIT=FALSE every 
exit was an unclean close for the embedded H2 database, after which H2 can 
silently lose committed data on the next open.
    
    - DBCPConnectionFactory.closeAll() now closes the connection pools instead 
of only clearing its cache, so that H2 closes the database once its last 
connection is closed.
    - The new datasource-container of the entity component, loaded for every 
loader, shuts down the transaction factory, and so closes the pools, when it is 
stopped; being declared before the other containers, it is stopped after them.
    - ContainerLoader adds each container to the loaded containers as soon as 
it is initialized, as described in the Container javadoc, and keeps stopping 
the other containers when one throws a RuntimeException.
    - Start stops the loaded containers when OFBiz fails to start, since the 
shutdown hook is disabled for the load-data and test runs.
---
 .../ofbiz/base/container/ContainerLoader.java      | 15 +++---
 framework/entity/ofbiz-component.xml               |  2 +
 .../apache/ofbiz/entity/DataSourceContainer.java   | 61 ++++++++++++++++++++++
 .../entity/connection/DBCPConnectionFactory.java   | 10 +++-
 .../java/org/apache/ofbiz/base/start/Start.java    |  2 +
 5 files changed, 81 insertions(+), 9 deletions(-)

diff --git 
a/framework/base/src/main/java/org/apache/ofbiz/base/container/ContainerLoader.java
 
b/framework/base/src/main/java/org/apache/ofbiz/base/container/ContainerLoader.java
index d0db7c4b32..ba7decfaa5 100644
--- 
a/framework/base/src/main/java/org/apache/ofbiz/base/container/ContainerLoader.java
+++ 
b/framework/base/src/main/java/org/apache/ofbiz/base/container/ContainerLoader.java
@@ -18,7 +18,6 @@
  
*******************************************************************************/
 package org.apache.ofbiz.base.container;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
@@ -71,7 +70,7 @@ public class ContainerLoader {
 
         // Load containers defined in components.
         Debug.logInfo("[Startup] Loading containers...", MODULE);
-        
loadedContainers.addAll(loadContainersFromConfigurations(config.getLoaders(), 
ofbizCommands));
+        loadContainersFromConfigurations(config.getLoaders(), ofbizCommands);
 
         // Start all containers loaded from above steps
         startLoadedContainers();
@@ -90,23 +89,22 @@ public class ContainerLoader {
 
     /**
      * Loads the available containers which are matching the configured 
loaders.
+     * Each container is added to the loaded containers as soon as it is 
initialized, so that it
+     * gets stopped on shutdown even when a container loaded after it fails to 
load.
      * @param loaders  the collection of loaders to match
      * @param ofbizCommands  the parsed commands line arguments used by the 
containers
-     * @return a list of loaded containers.
      * @throws StartupException when a container fails to load.
      */
-    private static List<Container> 
loadContainersFromConfigurations(Collection<String> loaders,
+    private void loadContainersFromConfigurations(Collection<String> loaders,
             List<StartupCommand> ofbizCommands) throws StartupException {
-        List<Container> loadContainers = new ArrayList<>();
         for (ContainerConfig.Configuration containerCfg : 
ComponentConfig.getAllConfigurations()) {
             if (intersects(containerCfg.loaders(), loaders)) {
                 Debug.logInfo("Loading container: " + containerCfg.name(), 
MODULE);
                 Container tmpContainer = loadContainer(containerCfg, 
ofbizCommands);
-                loadContainers.add(tmpContainer);
+                loadedContainers.add(tmpContainer);
                 Debug.logInfo("Loaded container: " + containerCfg.name(), 
MODULE);
             }
         }
-        return loadContainers;
     }
 
 
@@ -167,7 +165,8 @@ public class ContainerLoader {
             Debug.logInfo("Stopping container " + loadedContainer.getName(), 
MODULE);
             try {
                 loadedContainer.stop();
-            } catch (ContainerException e) {
+            } catch (ContainerException | RuntimeException e) {
+                // keep stopping the other containers, the last ones close the 
datasources
                 Debug.logError(e, MODULE);
             }
             Debug.logInfo("Stopped container " + loadedContainer.getName(), 
MODULE);
diff --git a/framework/entity/ofbiz-component.xml 
b/framework/entity/ofbiz-component.xml
index e2bd5adf8c..70f618fb45 100644
--- a/framework/entity/ofbiz-component.xml
+++ b/framework/entity/ofbiz-component.xml
@@ -32,6 +32,8 @@ under the License.
 
     <test-suite loader="main" location="testdef/entitytests.xml"/>
    
+    <!-- declared first so it is stopped last, once every other container has 
stopped using the datasources -->
+    <container name="datasource-container" loaders="main,rmi,load-data,test" 
class="org.apache.ofbiz.entity.DataSourceContainer"/>
     <container name="delegator-container" loaders="main" 
class="org.apache.ofbiz.entity.DelegatorContainer">
         <property name="preload-delegators" value="default"/>
     </container>
diff --git 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/DataSourceContainer.java
 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/DataSourceContainer.java
new file mode 100644
index 0000000000..8600b843b2
--- /dev/null
+++ 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/DataSourceContainer.java
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * 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.ofbiz.entity;
+
+import java.util.List;
+
+import org.apache.ofbiz.base.container.Container;
+import org.apache.ofbiz.base.start.StartupCommand;
+import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.entity.transaction.TransactionFactoryLoader;
+
+/**
+ * Closes the entity engine datasources when OFBiz shuts down.
+ * <p>
+ * An embedded database such as H2 must be closed for its files to be left in 
a consistent state: on the next
+ * open after an unclean close, H2 can silently lose committed data. This 
container is loaded before the others
+ * so that it is stopped last, after every container that may still use the 
datasources.
+ */
+public class DataSourceContainer implements Container {
+
+    private static final String MODULE = DataSourceContainer.class.getName();
+
+    private String name;
+
+    @Override
+    public void init(List<StartupCommand> ofbizCommands, String name, String 
configFile) {
+        this.name = name;
+    }
+
+    @Override
+    public boolean start() {
+        return true;
+    }
+
+    @Override
+    public void stop() {
+        Debug.logInfo("Closing the datasources", MODULE);
+        TransactionFactoryLoader.getInstance().shutdown();
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}
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 98e2759d5b..ecad030d93 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
@@ -170,7 +170,15 @@ public class DBCPConnectionFactory implements 
ConnectionFactory {
 
     @Override
     public void closeAll() {
-        // no methods on the pool to shutdown; so just clearing for GC
+        // 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) -> {
+            try {
+                mds.close();
+            } catch (SQLException e) {
+                Debug.logError(e, "Error closing the connection pool for " + 
helperName, MODULE);
+            }
+        });
         DS_CACHE.clear();
     }
 
diff --git 
a/framework/start/src/main/java/org/apache/ofbiz/base/start/Start.java 
b/framework/start/src/main/java/org/apache/ofbiz/base/start/Start.java
index 62a6765146..33ed1dfb26 100644
--- a/framework/start/src/main/java/org/apache/ofbiz/base/start/Start.java
+++ b/framework/start/src/main/java/org/apache/ofbiz/base/start/Start.java
@@ -88,6 +88,8 @@ public final class Start {
             try {
                 StartupControlPanel.start(INSTANCE.config, 
INSTANCE.serverState, ofbizCommands, INSTANCE.loader);
             } catch (StartupException e) {
+                // stop what was loaded, the shutdown hook being possibly 
disabled
+                StartupControlPanel.shutdownServer(INSTANCE.loader, 
INSTANCE.serverState);
                 StartupControlPanel.fullyTerminateSystem(e);
             }
             break;

Reply via email to