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;

