diff --git a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializer.java b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializer.java
index 4e330cb7d51..05f8047edb7 100644
--- a/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializer.java
+++ b/components/core/core-tenants/src/main/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializer.java
@@ -12,30 +12,56 @@
import org.eclipse.dirigible.components.base.ApplicationListenersOrder.ApplicationReadyEventListeners;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
+import java.time.Duration;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+/**
+ * Provisions the tenants a while after the application is ready.
+ *
+ *
+ * The delayed provisioning belongs to this bean's application context: the executor is shut down
+ * with the context, so a provisioning that has not started yet never runs. Left to fire after the
+ * context closed, it reached through the closed context's beans, which Spring re-creates on demand
+ * - an entity manager factory and with it the SystemDB Liquibase update - against a database that
+ * meanwhile belongs to another context, where the stray update could leave the changelog lock held
+ * and block every later boot on it.
+ */
@Order(ApplicationReadyEventListeners.TENANTS_INITIALIZER)
@Component
-class TenantsInitializer implements ApplicationListener {
+class TenantsInitializer implements ApplicationListener, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(TenantsInitializer.class);
+ private static final Duration PROVISIONING_DELAY = Duration.ofSeconds(30);
+
private final TenantsProvisioner tenantsProvisioner;
+ private final Duration provisioningDelay;
+
+ private final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
+
+ @Autowired
TenantsInitializer(TenantsProvisioner tenantsProvisioner) {
+ this(tenantsProvisioner, PROVISIONING_DELAY);
+ }
+
+ TenantsInitializer(TenantsProvisioner tenantsProvisioner, Duration provisioningDelay) {
this.tenantsProvisioner = tenantsProvisioner;
+ this.provisioningDelay = provisioningDelay;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
- ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
- executor.schedule(this::provisionTenants, 30, TimeUnit.SECONDS);
+ executor.schedule(this::provisionTenants, provisioningDelay.toMillis(), TimeUnit.MILLISECONDS);
}
private void provisionTenants() {
@@ -46,4 +72,9 @@ private void provisionTenants() {
LOGGER.debug("Tenants have been initialized.");
}
+ @Override
+ public void destroy() {
+ executor.shutdownNow();
+ }
+
}
diff --git a/components/core/core-tenants/src/test/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializerTest.java b/components/core/core-tenants/src/test/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializerTest.java
new file mode 100644
index 00000000000..44bcc0ef4d9
--- /dev/null
+++ b/components/core/core-tenants/src/test/java/org/eclipse/dirigible/components/tenants/provisioning/TenantsInitializerTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2010-2026 Eclipse Dirigible contributors
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v2.0 which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v20.html
+ *
+ * SPDX-FileCopyrightText: Eclipse Dirigible contributors SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.dirigible.components.tenants.provisioning;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import java.time.Duration;
+import java.util.Set;
+
+import org.eclipse.dirigible.components.tenants.domain.TenantStatus;
+import org.eclipse.dirigible.components.tenants.service.TenantService;
+import org.eclipse.dirigible.components.tenants.tenant.TenantFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The delayed provisioning belongs to the context that scheduled it: once that context is closed, a
+ * provisioning that has not started must never run.
+ */
+class TenantsInitializerTest {
+
+ private static final Duration DELAY = Duration.ofMillis(200);
+
+ private final TenantService tenantService = mock(TenantService.class);
+
+ private final TenantsInitializer initializer =
+ new TenantsInitializer(new TenantsProvisioner(tenantService, Set.of(), Set.of(), new TenantFactory()), DELAY);
+
+ @AfterEach
+ void shutDown() {
+ initializer.destroy();
+ }
+
+ @Test
+ void provisionsTheTenantsAfterTheDelay() {
+ initializer.onApplicationEvent(null);
+
+ verify(tenantService, timeout(5_000)).findByStatus(TenantStatus.INITIAL);
+ }
+
+ @Test
+ void aProvisioningScheduledBeforeTheContextClosedNeverRuns() {
+ initializer.onApplicationEvent(null);
+
+ initializer.destroy();
+
+ verify(tenantService, after(DELAY.multipliedBy(5)
+ .toMillis()).never()).findByStatus(any());
+ }
+
+}