This is an automated email from the ASF dual-hosted git repository.
nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 096c94155b6 IGNITE-28657 Service local start order (#13150)
096c94155b6 is described below
commit 096c94155b69202b06c2471c8e69cbd946095734
Author: Nikolay <[email protected]>
AuthorDate: Tue May 19 16:57:03 2026 +0300
IGNITE-28657 Service local start order (#13150)
---
docs/_docs/services/services.adoc | 2 +
.../java/org/apache/ignite/IgniteServices.java | 6 +
.../service/LazyServiceConfiguration.java | 1 +
.../processors/service/ServiceDeploymentTask.java | 75 +++++----
.../ignite/services/ServiceConfiguration.java | 55 ++++++-
.../service/ServiceLocalStartOrderTest.java | 169 +++++++++++++++++++++
.../testsuites/IgniteServiceGridTestSuite.java | 2 +
7 files changed, 274 insertions(+), 36 deletions(-)
diff --git a/docs/_docs/services/services.adoc
b/docs/_docs/services/services.adoc
index 46801d4e661..d59cdaece88 100644
--- a/docs/_docs/services/services.adoc
+++ b/docs/_docs/services/services.adoc
@@ -62,6 +62,8 @@ The `Service` interface has three methods:
You can deploy your service either programmatically at runtime, or by
providing a service configuration as part of the node configuration.
In the latter case, the service is deployed when the cluster starts.
+If deploying several related services you can specify start order with the
`ServiceConfiguration.setLocalStartOrder(int)`.
+Start order applied on node level during service initialization. Services with
lower `localStartOrder` will be inited first.
=== Deploying Services at Runtime
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteServices.java
b/modules/core/src/main/java/org/apache/ignite/IgniteServices.java
index a3d64d45e70..b3f2e3d0f6c 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteServices.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteServices.java
@@ -444,11 +444,14 @@ public interface IgniteServices extends
IgniteAsyncSupport {
* of failed services will be thrown. It is guaranteed that all services
that were provided to this method and are
* not present in the list of failed services are successfully deployed by
the moment of the exception being thrown.
* Note that if exception is thrown, then partial deployment may have
occurred.
+ * Note, start order guarantees not provided, by default.
+ * Node local start order can be forced with the {@link
ServiceConfiguration#setLocalStartOrder(int)}.
*
* @param cfgs {@link Collection} of service configurations to be deployed.
* @throws ServiceDeploymentException If failed to deploy services.
* @see IgniteServices#deploy(ServiceConfiguration)
* @see IgniteServices#deployAllAsync(Collection)
+ * @see ServiceConfiguration#setLocalStartOrder(int)
*/
public void deployAll(Collection<ServiceConfiguration> cfgs) throws
ServiceDeploymentException;
@@ -463,11 +466,14 @@ public interface IgniteServices extends
IgniteAsyncSupport {
* guaranteed that all services, that were provided to this method and are
not present in the list of failed
* services, are successfully deployed by the moment of the exception
being thrown. Note that if exception is
* thrown, then partial deployment may have occurred.
+ * Note, start order guarantees not provided, by default.
+ * Node local start order can be forced with the {@link
ServiceConfiguration#setLocalStartOrder(int)}.
*
* @param cfgs {@link Collection} of service configurations to be deployed.
* @return a Future representing pending completion of the operation.
* @see IgniteServices#deploy(ServiceConfiguration)
* @see IgniteServices#deployAll(Collection)
+ * @see ServiceConfiguration#setLocalStartOrder(int)
*/
public IgniteFuture<Void> deployAllAsync(Collection<ServiceConfiguration>
cfgs);
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
index c5c35b2f1fc..859106ee73b 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/LazyServiceConfiguration.java
@@ -95,6 +95,7 @@ public class LazyServiceConfiguration extends
ServiceConfiguration {
isStatisticsEnabled = cfg.isStatisticsEnabled();
interceptors = cfg.getInterceptors();
this.interceptorsBytes = interceptorsBytes;
+ locStartOrder = cfg.getLocalStartOrder();
}
/**
diff --git
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
index cdb866d5aa2..dfb12ecc614 100644
---
a/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
+++
b/modules/core/src/main/java/org/apache/ignite/internal/processors/service/ServiceDeploymentTask.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -307,33 +308,38 @@ class ServiceDeploymentTask {
if (!depActions.servicesToDeploy().isEmpty()) {
final Collection<UUID> evtTopNodes =
nodeIds(ctx.discovery().nodes(evtTopVer));
- depActions.servicesToDeploy().forEach((srvcId, desc) -> {
- try {
- ServiceConfiguration cfg = desc.configuration();
+ depActions.servicesToDeploy().entrySet().stream()
+ .sorted(Comparator.comparingInt(e ->
e.getValue().configuration().getLocalStartOrder()))
+ .forEach(entry -> {
+ IgniteUuid srvcId = entry.getKey();
+ ServiceInfo desc = entry.getValue();
- TreeMap<UUID, Integer> oldTop =
filterDeadNodes(evtTopNodes, desc.topologySnapshot());
+ try {
+ ServiceConfiguration cfg = desc.configuration();
- Map<UUID, Integer> top = reassign(srvcId, cfg, evtTopVer,
oldTop);
+ TreeMap<UUID, Integer> oldTop =
filterDeadNodes(evtTopNodes, desc.topologySnapshot());
- expDeps.put(srvcId, top);
+ Map<UUID, Integer> top = reassign(srvcId, cfg,
evtTopVer, oldTop);
- Integer expCnt = top.getOrDefault(ctx.localNodeId(), 0);
+ expDeps.put(srvcId, top);
- if (expCnt > srvcProc.localInstancesCount(srvcId)) {
- srvcProc.deployment().deployerBlockingSectionBegin();
+ Integer expCnt = top.getOrDefault(ctx.localNodeId(),
0);
- try {
- srvcProc.redeploy(srvcId, cfg, top);
- }
- finally {
- srvcProc.deployment().deployerBlockingSectionEnd();
+ if (expCnt > srvcProc.localInstancesCount(srvcId)) {
+
srvcProc.deployment().deployerBlockingSectionBegin();
+
+ try {
+ srvcProc.redeploy(srvcId, cfg, top);
+ }
+ finally {
+
srvcProc.deployment().deployerBlockingSectionEnd();
+ }
}
}
- }
- catch (IgniteCheckedException e) {
- depErrors.computeIfAbsent(srvcId, c -> new
ArrayList<>()).add(e);
- }
- });
+ catch (IgniteCheckedException e) {
+ depErrors.computeIfAbsent(srvcId, c -> new
ArrayList<>()).add(e);
+ }
+ });
}
createAndSendSingleDeploymentsMessage(depId, depErrors);
@@ -484,25 +490,28 @@ class ServiceDeploymentTask {
final Map<IgniteUuid, ServiceInfo> services =
srvcProc.deployedServices();
- fullTops.forEach((srvcId, top) -> {
- Integer expCnt =
top.snapshot().getOrDefault(ctx.localNodeId(), 0);
+ fullTops.entrySet().stream()
+ .sorted(Comparator.comparingInt(e ->
services.get(e.getKey()).configuration().getLocalStartOrder()))
+ .forEach(entry -> {
+ IgniteUuid srvcId = entry.getKey();
+ ServiceTopology top = entry.getValue();
- if (expCnt < srvcProc.localInstancesCount(srvcId)) {
// Undeploy exceed instances
- ServiceInfo desc = services.get(srvcId);
+ Integer expCnt =
top.snapshot().getOrDefault(ctx.localNodeId(), 0);
- assert desc != null;
+ if (expCnt < srvcProc.localInstancesCount(srvcId))
{ // Undeploy exceed instances
+ ServiceInfo desc = services.get(srvcId);
- ServiceConfiguration cfg = desc.configuration();
+ ServiceConfiguration cfg =
desc.configuration();
- try {
- srvcProc.redeploy(srvcId, cfg, top.snapshot());
+ try {
+ srvcProc.redeploy(srvcId, cfg,
top.snapshot());
+ }
+ catch (IgniteCheckedException e) {
+ log.error("Error occured during cancel
exceed service instances: " +
+ "[srvcId=" + srvcId + ", name=" +
desc.name() + ']', e);
+ }
}
- catch (IgniteCheckedException e) {
- log.error("Error occured during cancel exceed
service instances: " +
- "[srvcId=" + srvcId + ", name=" +
desc.name() + ']', e);
- }
- }
- });
+ });
completeSuccess();
}
diff --git
a/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
b/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
index 77d40ae83c3..bd42651e918 100644
---
a/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
+++
b/modules/core/src/main/java/org/apache/ignite/services/ServiceConfiguration.java
@@ -20,8 +20,10 @@ package org.apache.ignite.services;
import java.io.Externalizable;
import java.io.Serializable;
import java.util.Arrays;
+import java.util.Collection;
import org.apache.ignite.IgniteServices;
import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.processors.service.IgniteServiceProcessor;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.internal.S;
@@ -35,14 +37,14 @@ import org.apache.ignite.lang.IgnitePredicate;
* <pre name="code" class="java">
* IgniteConfiguration gridCfg = new IgniteConfiguration();
*
- * GridServiceConfiguration svcCfg1 = new GridServiceConfiguration();
+ * ServiceConfiguration svcCfg1 = new ServiceConfiguration();
*
* svcCfg1.setName("myClusterSingletonService");
* svcCfg1.setMaxPerNodeCount(1);
* svcCfg1.setTotalCount(1);
* svcCfg1.setService(new MyClusterSingletonService());
*
- * GridServiceConfiguration svcCfg2 = new GridServiceConfiguration();
+ * ServiceConfiguration svcCfg2 = new ServiceConfiguration();
*
* svcCfg2.setName("myNodeSingletonService");
* svcCfg2.setMaxPerNodeCount(1);
@@ -88,6 +90,19 @@ public class ServiceConfiguration implements Serializable {
@GridToStringExclude
protected ServiceCallInterceptor[] interceptors;
+ /**
+ * Node local start order.
+ * Note:
+ * <p>
+ * In case static service configuration {@link
IgniteConfiguration#setServiceConfiguration(ServiceConfiguration...)}
+ * order will be applied on node start.
+ * </p>
+ * <p>
+ * In case deploying by the {@link IgniteServices#deployAll(Collection)},
order will be applied for deployed services.
+ * </p>
+ */
+ protected int locStartOrder;
+
/**
* Gets service name.
* <p>
@@ -318,6 +333,34 @@ public class ServiceConfiguration implements Serializable {
return this;
}
+ /**
+ * <p>
+ * In case static service configuration {@link
IgniteConfiguration#setServiceConfiguration(ServiceConfiguration...)}
+ * order will be applied on node start.
+ * </p>
+ * <p>
+ * In case deploying by the {@link IgniteServices#deployAll(Collection)},
order will be applied for deployed services.
+ * </p>
+ *
+ * @return Node local start order. Greater value means service started
later.
+ */
+ public int getLocalStartOrder() {
+ return locStartOrder;
+ }
+
+ /**
+ * Sets node local start order.
+ * Greater value means service started later.
+ *
+ * @param locStartOrder Node local start order.
+ * @return {@code this} for chaining.
+ */
+ public ServiceConfiguration setLocalStartOrder(int locStartOrder) {
+ this.locStartOrder = locStartOrder;
+
+ return this;
+ }
+
/** {@inheritDoc} */
@Override public boolean equals(Object o) {
if (!equalsIgnoreNodeFilter(o))
@@ -370,6 +413,9 @@ public class ServiceConfiguration implements Serializable {
if (svc != null ? !svc.getClass().equals(that.svc.getClass()) :
that.svc != null)
return false;
+ if (locStartOrder != that.locStartOrder)
+ return false;
+
return Arrays.deepEquals(interceptors, that.interceptors);
}
@@ -383,6 +429,9 @@ public class ServiceConfiguration implements Serializable {
String svcCls = svc == null ? "" : svc.getClass().getSimpleName();
String nodeFilterCls = nodeFilter == null ? "" :
nodeFilter.getClass().getSimpleName();
- return S.toString(ServiceConfiguration.class, this, "svcCls", svcCls,
"nodeFilterCls", nodeFilterCls);
+ return S.toString(ServiceConfiguration.class, this,
+ "svcCls", svcCls,
+ "nodeFilterCls", nodeFilterCls,
+ "localStartOrder", locStartOrder);
}
}
diff --git
a/modules/core/src/test/java/org/apache/ignite/internal/processors/service/ServiceLocalStartOrderTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/ServiceLocalStartOrderTest.java
new file mode 100644
index 00000000000..11f84ab8480
--- /dev/null
+++
b/modules/core/src/test/java/org/apache/ignite/internal/processors/service/ServiceLocalStartOrderTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.ignite.internal.processors.service;
+
+import java.util.Arrays;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.resources.IgniteInstanceResource;
+import org.apache.ignite.services.Service;
+import org.apache.ignite.services.ServiceConfiguration;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+
+/** */
+public class ServiceLocalStartOrderTest extends GridCommonAbstractTest {
+ /** */
+ private final int gridCnt = 3;
+
+ /** */
+ private boolean staticConfig;
+
+ /** {@inheritDoc} */
+ @Override protected void afterTest() throws Exception {
+ super.afterTest();
+
+ stopAllGrids();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String
igniteInstanceName) throws Exception {
+ IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+ if (staticConfig)
+ cfg.setServiceConfiguration(serviceConfigs());
+
+ return cfg;
+ }
+
+ /** */
+ @Test
+ public void testStaticConfigDeployment() throws Exception {
+ staticConfig = true;
+
+ try {
+ startGrids(gridCnt);
+
+ check();
+ }
+ finally {
+ staticConfig = false;
+ }
+ }
+
+ /** */
+ @Test
+ public void testRuntimeDeployment() throws Exception {
+ IgniteEx ignite = startGrids(gridCnt);
+
+ doTest(ignite);
+ }
+
+ /** */
+ @Test
+ public void testRuntimeDeploymentFromClient() throws Exception {
+ startGrids(gridCnt);
+
+ doTest(startClientGrid());
+ }
+
+ /** */
+ private void doTest(IgniteEx deployFrom) throws Exception {
+ deployFrom.services().deployAll(Arrays.asList(serviceConfigs()));
+
+ check();
+ }
+
+ /** */
+ private void check() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ for (int node = 0; node < gridCnt; node++) {
+ IgniteEx ign = grid(node);
+
+ String srvcName = name(i);
+
+ assertTrue(waitForCondition(() ->
ign.services().service(srvcName) != null, 10_000));
+ }
+ }
+ }
+
+ /** */
+ private ServiceConfiguration[] serviceConfigs() {
+ ServiceConfiguration[] cfgs = new ServiceConfiguration[5];
+
+ for (int i = 0; i < 5; i++) {
+ cfgs[i] = new ServiceConfiguration()
+ .setName(name(i))
+ .setService(new OrderedServiceImpl(i))
+ .setLocalStartOrder(i)
+ .setMaxPerNodeCount(1)
+ .setTotalCount(gridCnt);
+ }
+
+ return cfgs;
+ }
+
+ /** */
+ private static class OrderedServiceImpl implements OrderedService {
+ /** */
+ private final int order;
+
+ /** */
+ private boolean started;
+
+ /** */
+ @IgniteInstanceResource
+ private Ignite ignite;
+
+ /** */
+ public OrderedServiceImpl(int order) {
+ this.order = order;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void init() throws Exception {
+ for (int i = order - 1; i >= 0; i--) {
+ OrderedService srvc = ignite.services().service(name(i));
+
+ assertNotNull("Must be deployed previously: " + i, srvc);
+ assertTrue(srvc.started());
+ }
+
+ started = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean started() {
+ return started;
+ }
+ }
+
+ /** */
+ private interface OrderedService extends Service {
+ /** */
+ boolean started();
+ }
+
+ /** */
+ private static String name(int i) {
+ return "Ordered" + i;
+ }
+}
diff --git
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
index 034c8ff5c86..63f16fbbf70 100644
---
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
+++
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteServiceGridTestSuite.java
@@ -60,6 +60,7 @@ import
org.apache.ignite.internal.processors.service.ServiceDeploymentProcessing
import
org.apache.ignite.internal.processors.service.ServiceDeploymentProcessingOnNodesLeftTest;
import
org.apache.ignite.internal.processors.service.ServiceHotRedeploymentViaDeploymentSpiTest;
import org.apache.ignite.internal.processors.service.ServiceInfoSelfTest;
+import
org.apache.ignite.internal.processors.service.ServiceLocalStartOrderTest;
import
org.apache.ignite.internal.processors.service.ServicePredicateAccessCacheTest;
import
org.apache.ignite.internal.processors.service.ServiceReassignmentFunctionSelfTest;
import
org.apache.ignite.internal.processors.service.ServiceRedeploymentOnNodeLeftTest;
@@ -125,6 +126,7 @@ import org.junit.runners.Suite;
GridServiceMetricsTest.class,
IgniteServiceCallInterceptorTest.class,
ServiceRedeploymentOnNodeLeftTest.class,
+ ServiceLocalStartOrderTest.class
})
public class IgniteServiceGridTestSuite {
/** */