Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
8de14d7
Initial commit for the merge of hazelcast branches
ironcerocloudbees May 20, 2026
1f2e850
Changes done, testing
ironcerocloudbees May 20, 2026
6b34efe
Fixing maven tests running in hazelcast mode
ironcerocloudbees May 20, 2026
51beeac
First candidate for Hazelcast implementation
ironcerocloudbees May 21, 2026
2c68b30
Peer review comment #1: Event/Notification Claim Strategies Not Wired…
ironcerocloudbees May 25, 2026
d30c665
Peer review comment #2: EventIdentifier Uses hashCode() - Not Stable …
ironcerocloudbees May 25, 2026
daa728c
Peer review comment #3: Race Conditions in retriggered() and removePr…
ironcerocloudbees May 25, 2026
3cc4a55
Peer review comment #3: getDistributedMemory() Thread Safety
ironcerocloudbees May 25, 2026
5ac64ad
Peer review comment #5: TCP Member Configuration Bug (Copilot #2)
ironcerocloudbees May 25, 2026
d041dbe
Peer review comment #6: TCP Fallback Port Mismatch
ironcerocloudbees May 25, 2026
d9ff616
Peer review comment #7: MemoryImprintData vs MemoryImprint Design Que…
ironcerocloudbees May 25, 2026
0cf1663
Peer review comment #8: Timestamp Setting Order Issues
ironcerocloudbees May 25, 2026
d18d860
Peer review comment #10: Runtime Exception Handling in executeOnKey C…
ironcerocloudbees May 25, 2026
d54d09f
Peer review comment #11: Complete: Parse TTL Once
ironcerocloudbees May 26, 2026
04b1c27
Peer review comments #12,13,15,16,17,20,21,23
ironcerocloudbees May 26, 2026
2b44bfe
Peer review Robert #2~#6: changing the order of the timestamp set and…
ironcerocloudbees May 26, 2026
c1c8636
Peer review: Avoiding using static singletons
ironcerocloudbees May 26, 2026
101ef62
Peer review: avoiding parsing the numbers on every call
ironcerocloudbees May 26, 2026
debed20
Peer review: making claim result shareable for all the strategies
ironcerocloudbees May 26, 2026
498138a
Peer review: fixing sync problem inside a lambda
ironcerocloudbees May 26, 2026
c40e360
Peer review: fixing javadoc issues
ironcerocloudbees May 26, 2026
31c0159
Peer review: removing unnecessary comments
ironcerocloudbees May 26, 2026
6802a81
Peer review: removing unnecessary comments
ironcerocloudbees May 26, 2026
9b0d53b
Peer review: added explanation on eventsMatch
ironcerocloudbees May 26, 2026
c0569c5
Peer review: removing initialization duplications
ironcerocloudbees May 26, 2026
2423e3a
Peer review: improving initialization
ironcerocloudbees May 27, 2026
8722400
Fixing WorkflowTest REST API Failures
ironcerocloudbees May 27, 2026
1f53523
Fixing issues with SpecGerritTriggerHudsonTest Build Trigger
ironcerocloudbees May 27, 2026
abab9df
Fixing initialization problem with client and event duplication issues
ironcerocloudbees May 27, 2026
323faea
Fixing gerrit feedback problems
ironcerocloudbees May 28, 2026
f963e01
Fixing problem with the gerrit feedback
ironcerocloudbees May 28, 2026
babf20e
HA/HS: Implement cross-replica build abortion for new patchsets (HZ-004)
ironcerocloudbees Jun 1, 2026
751bea2
Peer review 12 (PluginImpl) - direct provider chosen
ironcerocloudbees Jun 2, 2026
ccc1e43
Peer review 1 (LocalBuildMemoryStorage) - Equal should be use instead…
ironcerocloudbees Jun 2, 2026
ccef94e
Peer review 1 (LocalBuildMemoryStorage) - Equal should be use instead…
ironcerocloudbees Jun 2, 2026
6b4a37a
Peer review 2 (HazelcastBuildMemoryStorage) - AbandonedPatchsetInterr…
ironcerocloudbees Jun 2, 2026
ae76e3b
Fix self-cancellation when isAbortNewPatchsets=true with multiple tri…
ironcerocloudbees Jun 2, 2026
c38cbe9
Fixing hazelcast initialization
ironcerocloudbees Jun 2, 2026
121a26d
Fixing race condition problem in the aborting scenario
ironcerocloudbees Jun 3, 2026
cc9f1b5
Peer review 3 (BuildCompletedProcessor) - removing unused processors
ironcerocloudbees Jun 4, 2026
471c06c
Fixing problems in the hazelcast tests
ironcerocloudbees Jun 4, 2026
9d9d2e7
Peer review (EventIdentifier.java) - Reverted changes to get event hash
ironcerocloudbees Jun 8, 2026
8d170cb
Peer review (HazelcastBuildMemoryStorage.java) - removing wrong javadoc
ironcerocloudbees Jun 8, 2026
87b83e1
Updating README.md file with the new development information
ironcerocloudbees Jun 9, 2026
12b9bb2
Merge branch 'distributed-storage' of github.com:jenkinsci/gerrit-tri…
rsandell Jun 24, 2026
80a272a
Peer review comments updates and fixing maven dependencies problem in…
ironcerocloudbees Jun 30, 2026
9c792d4
Fixing junit tests
ironcerocloudbees Jun 30, 2026
c69e3bd
Comment #3: Smelly approach when waiting an arbitary time fixed
ironcerocloudbees Jun 30, 2026
2b24f01
Fixing Javadoc:jar issues
ironcerocloudbees Jul 1, 2026
a21ab4e
Splitting README file
ironcerocloudbees Jul 1, 2026
5e89289
Removing Hazelcast member mode
ironcerocloudbees Jul 1, 2026
49938e1
Changing README files for better nomenclature
ironcerocloudbees Jul 2, 2026
b130eda
Changing nomenclature
ironcerocloudbees Jul 2, 2026
70c66d1
Remove faulty extension
rsandell Jul 2, 2026
f29e7c7
Move MemoryImprint <-> data conversion onto the model (Expert pattern)
rsandell Jul 2, 2026
ee6157f
Fixing regresion bug
ironcerocloudbees Jul 15, 2026
bb35c83
Fixing only client mode for Hazelcast maven test profile
ironcerocloudbees Jul 16, 2026
2f1f5c4
Fixing only client mode for Hazelcast maven test profile
ironcerocloudbees Jul 16, 2026
1c4a0fd
Fix 004 test regarding cancellation policies
ironcerocloudbees Jul 16, 2026
571c59c
Fixing javadoc issues
ironcerocloudbees Jul 16, 2026
40272b1
Merge branch 'hazelcast-impl' into expert-pattern-memoryimprint-data
sboardwell Jul 17, 2026
5b36888
Map queueLeft through the Entry <-> EntryData conversion
rsandell Jul 18, 2026
6e3f041
Add missing queueLeft
sboardwell Jul 20, 2026
b2b97e1
Improve patchset cancellation logic to handle distributed event deliv…
sboardwell Jul 17, 2026
40f747a
Enhance cancellation logic to handle ambiguous queue-item states and …
sboardwell Jul 18, 2026
6d7cd73
Merge pull request #1 from rsandell/expert-pattern-memoryimprint-data
sboardwell Jul 20, 2026
5d222f1
Merge pull request #2 from ironcerocloudbees/pr-1-conflict-resolution
sboardwell Jul 20, 2026
bdcc2d7
Merge branch 'hazelcast-impl' into cancellation-race-fixes
sboardwell Jul 20, 2026
5fca872
Stop gating cancellation-eligibility on queueLeft
sboardwell Jul 20, 2026
f4bc46a
Log the previously-silent queueLeft-via-relocation branch
sboardwell Jul 20, 2026
1d9a0d6
Implement patchset order verification for distributed storage mode
sboardwell Jul 21, 2026
5e676a0
Add disclaimers to readme
stephankfolkes Jul 21, 2026
96f17bd
Update email address
stephankfolkes Jul 21, 2026
93ea925
Cluster name must be different per logical instance
stephankfolkes Jul 21, 2026
37dffa4
Remove patchset order verification logic and related comments for loc…
sboardwell Jul 22, 2026
4c7cf4e
Stop Gerrit server connection in tearDown method
sboardwell Jul 22, 2026
1ef10ac
Merge pull request #3 from ironcerocloudbees/cancellation-race-fixes
sboardwell Jul 22, 2026
dc90b71
Refactor Hazelcast coordination logic to ensure proper cluster format…
sboardwell Jul 22, 2026
6762df9
Simplify pipeline-not-yet-started check to FlowExecution presence only
sboardwell Jul 22, 2026
42fbbbe
Fix Hazelcast test harness port collision and event-scheduling race
sboardwell Jul 22, 2026
fcad4b5
Change of wording for replicas in README
stephankfolkes Jul 22, 2026
c784cdb
Cluster name explanation update
stephankfolkes Jul 22, 2026
53c9201
Update disclaimer
stephankfolkes Jul 22, 2026
3a9f4fa
Apply suggestions from code review
stephankfolkes Jul 23, 2026
6c16a31
Merge pull request #4 from stephankfolkes/hazelcast-impl
sboardwell Jul 23, 2026
130e728
Rename EventIdentifier to EventIdGenerator, remove unused BuildMemoryKey
sboardwell Jul 23, 2026
f4429bb
Move PipelineAbortHelper to hudsontrigger package
sboardwell Jul 23, 2026
b2715e5
Generalise message
sboardwell Jul 23, 2026
392da69
Remove mentions of HZ-* tests and mc* controllers
sboardwell Jul 26, 2026
a7025ef
Refactor locking to fluent withLock/onFailure
sboardwell Jul 27, 2026
892f175
Fix testAbortedPipelineReturnsFalse to assert post-abort state
sboardwell Jul 27, 2026
ffec861
Corrected stale Javadoc to remove == comparison
sboardwell Jul 27, 2026
5887ec3
Add default-run Hazelcast coordination smoke test
sboardwell Jul 27, 2026
6c7fba2
Raise smoke test stream-events wait to 20s for busy CI agents
rsandell Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ hostkey.ser
/eclipse-classes
.vscode/
.factorypath
graphify-out
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Each job can be configured with one Gerrit server.
## Maintainers

* Robert Sandell
- robert.sandell@cloudbees.com
- sandell.robert@gmail.com

* Tomas Westling
Expand Down Expand Up @@ -52,6 +51,13 @@ Run checkstyle

mvn checkstyle:checkstyle

# Distributed Event Management support

The plugin supports an distributed event management for the memory that will track the events (for example using Hazelcast client).
See [README_DISTRIBUTED_EVENT_MANAGEMENT.md](docs/README_DISTRIBUTED_EVENT_MANAGEMENT.md) for configuration
properties and deployment examples.


# License

The MIT License
Expand Down
107 changes: 107 additions & 0 deletions docs/README_DISTRIBUTED_EVENT_MANAGEMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Distributed Event Management support

The plugin supports Distributed Event Management support where two or more replicas or nodes of a logical Jenkins(*) instance
run in parallel (sharing the Gerrit memory of the plugin). When enabled, a Hazelcast
cluster coordinates the instances so that:

- Each Gerrit event is processed by **exactly one** instance (event claiming)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any interaction with / relationship to #1017?

implements an alternative HTTPS polling mechanism (reducing reliance on SSH), and introduces a new webhook endpoint for receiving Gerrit events directly over HTTP

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@panicking claims that they shouldn't break eachother. I am about to review it to check.

- Build state is shared across instances (distributed build memory)
- Gerrit feedback (votes and comments) are sent **exactly once** per build event

By default, the plugin runs in **local mode** and requires no additional configuration.
Local mode is fully backward-compatible with single-instance Jenkins deployments.

Alternative coordination backends can be implemented by extending
[`CoordinationModeProvider`](../src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/spi/CoordinationModeProvider.java)
— a Jenkins `ExtensionPoint` that wires together the storage, event-claiming, and
notification-claiming strategies for a given coordination mode. A higher `@Extension`
ordinal takes precedence over the built-in Hazelcast provider.

## Hazelcast implementation

Hazelcast mode is activated via a JVM system property. Jenkins connects as a lightweight
client to a Hazelcast sidecar container, reusing the cross-pod cluster the sidecar
maintains.

### Configuration Properties

All distributed storage settings are controlled by JVM system properties passed to Jenkins on startup.

| Property | Default | Description |
|---|---|-------------------------------------------------------|
| `gerrit.trigger.coordination.mode` | `local` | Set to `hazelcast` to enable distributed coordination |
| `gerrit.trigger.coordination.hazelcast.client.addresses` | `localhost:5702` | Comma-separated `host:port` list of sidecar addresses |
| `gerrit.trigger.coordination.hazelcast.client.cluster.name` | `gerrit-trigger-cluster` | Cluster name to connect to |

Port `5702` is used by default to avoid potential conflicts with other Hazelcast cluster, which could occupy port `5701`.

Cluster name must be different for each logical instance. Multiple replicas or nodes of a logical instance may configure the same cluster name. Different logical instances require separate cluster names.

### Configuration Example

#### Kubernetes — Client Mode with Hazelcast Sidecar

The plugin can connect to Hazelcast cluster as a lightweight client. For example, if we are running
K8s environment with the Jenkins instance inside a pod, we can have a side-container with Hazelcast
to set up the Hazelcast cluster. In this kind of cases, we would the a configuration setup similar
to the following one:

Add the following JVM arguments to the Jenkins instance:

-Dgerrit.trigger.coordination.mode=hazelcast
-Dgerrit.trigger.coordination.hazelcast.client.addresses=localhost:5702
-Dgerrit.trigger.coordination.hazelcast.client.cluster.name=gerrit-trigger-cluster

Add the sidecar container to the instance pod spec:

```yaml
- name: hazelcast
image: hazelcast/hazelcast:5.3.8
ports:
- containerPort: 5702
name: hazelcast
env:
- name: JAVA_OPTS
value: >-
-Dhazelcast.config=/dev/stdin
-Dhazelcast.local.publicAddress=$(POD_IP):5702
- name: HZ_CLUSTERNAME
value: gerrit-trigger-cluster
- name: HZ_NETWORK_PORT_PORT
value: "5702"
```

Grant the pod's service account read access to Kubernetes endpoints so Hazelcast can
discover its peers:

```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: hazelcast-gerrit-trigger
rules:
- apiGroups: [""]
resources: ["endpoints", "pods", "nodes", "services"]
verbs: ["get", "list"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list"]
```

#### Kubernetes — Client Mode with Separate Hazelcast Cluster

For larger deployments or strict separation of concerns, you can decouple the coordination layer by running a standalone Hazelcast cluster. Jenkins still connects as a lightweight client, but routes traffic to the separate cluster via a Kubernetes service instead of a sidecar.

Add the following JVM arguments to the Jenkins instance, updating the client address to point to your standalone Hazelcast Kubernetes service (replace hazelcast-service.default.svc.cluster.local with your actual service DNS and namespace, along with the cluster name for your logical instance):

-Dgerrit.trigger.coordination.mode=hazelcast
-Dgerrit.trigger.coordination.hazelcast.client.addresses=hazelcast-service.default.svc.cluster.local:5702
-Dgerrit.trigger.coordination.hazelcast.client.cluster.name=gerrit-trigger-cluster-<LOGICAL_INSTANCE_NAME>

In this topology:
- You do not need to add the sidecar container to the Jenkins pod spec.
- The Jenkins service account does not need RBAC permissions for peer discovery, as cluster management is handled entirely by the standalone Hazelcast nodes.
- You must deploy and manage the Hazelcast cluster independently (e.g. via the official Hazelcast Helm chart), ensuring you configure it to match your expected `HZ_CLUSTERNAME` and port (`5702`).

(*) Jenkins does not support multiple replicas or nodes for a single logical instance, this feature is not tested with Jenkins. This feature is provided for CloudBees CI (Enterprise Jenkins).
This feature is provided as a community effort and is not endorsed or officially supported by CloudBees.
48 changes: 48 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<surefire.rerunFailingTestsCount>3</surefire.rerunFailingTestsCount>
<forkCount>0.5C</forkCount>
<spotbugs.threshold>High</spotbugs.threshold>
<hazelcast.version>5.3.8</hazelcast.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -107,6 +108,11 @@
</exclusions>
<!-- New source is here: https://github.com/sonyxperiadev/gerrit-events -->
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>${hazelcast.version}</version>
</dependency>
<dependency>
<groupId>io.jenkins.plugins</groupId>
<artifactId>gson-api</artifactId>
Expand Down Expand Up @@ -203,6 +209,12 @@
<artifactId>workflow-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<!-- Used for test with matrix permissions -->
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down Expand Up @@ -370,6 +382,42 @@
</plugins>
</build>

<profiles>
<!--
Profile for testing with Hazelcast coordination mode enabled.
Usage: mvn clean test -Ptest-hazelcast

This profile enables Hazelcast distributed coordination during tests to verify:
- HazelcastCoordinationProvider is selected
- HazelcastBuildMemoryStorage works correctly
- Event and notification claiming strategies work

An embedded Hazelcast server is automatically started before any tests run by
HazelcastServerTestListener (discovered via
META-INF/services/org.junit.platform.launcher.TestExecutionListener). This provides
the localhost:5702 endpoint that the plugin client connects to, with no external
Kubernetes or TCP infrastructure required.
-->

@rsandell rsandell Jul 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So thiss means that there will be no hazelcastt specific tests at all during norrmal CI builds?

I remember we discussed that we'll need at least one smoke test durng the normal run to have some indication that future work doesn't break the hazelcast coordination mode.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added with 5887ec3

<profile>
<id>test-hazelcast</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<reuseForks>false</reuseForks>
<forkCount>1</forkCount>
<systemPropertyVariables>
<!-- Enable Hazelcast coordination mode for tests -->
<gerrit.trigger.coordination.mode>hazelcast</gerrit.trigger.coordination.mode>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>

<scm>
<connection>scm:git:https://github.com/${gitHubRepo}.git</connection>
<developerConnection>scm:git:git@github.com:${gitHubRepo}.git</developerConnection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
*/
package com.sonyericsson.hudson.plugins.gerrit.trigger;

import com.sonyericsson.hudson.plugins.gerrit.trigger.coordination.CoordinationModeFactory;
import com.sonyericsson.hudson.plugins.gerrit.trigger.coordination.LocalCoordinationProvider;
import com.sonyericsson.hudson.plugins.gerrit.trigger.dependency.DependencyQueueTaskDispatcher;
import com.sonyericsson.hudson.plugins.gerrit.trigger.replication.ReplicationQueueTaskDispatcher;
import com.sonyericsson.hudson.plugins.gerrit.trigger.spi.CoordinationModeProvider;
import com.sonymobile.tools.gerrit.gerritevents.GerritHandler;
import com.sonymobile.tools.gerrit.gerritevents.GerritSendCommandQueue;
import com.sonyericsson.hudson.plugins.gerrit.trigger.config.Config;
Expand Down Expand Up @@ -583,6 +586,18 @@
logger.info("Starting Gerrit-Trigger Plugin");
logger.trace("Loading configs");
load();

// Initialize coordination providers early (before any code that might use CoordinationModeFactory)
// This must happen before BuildMemory, EventClaimStrategy, or NotificationClaimStrategy are used
// because provider.isAvailable() may check if resources are initialized
initializeCoordinationProviders();

// Eagerly initialize CoordinationModeFactory so discoverMode() runs now (during startup)
// rather than lazily on first event — deferred initialization can add several seconds of
// latency to the first build trigger when ExtensionList.lookup() is called from a
// background event-processing thread.
CoordinationModeFactory.get().getStorage();

GerritSendCommandQueue.initialize(pluginConfig);
gerritEventManager = new JenkinsAwareGerritHandler(pluginConfig.getNumberOfReceivingWorkerThreads());
for (GerritServer s : servers) {
Expand All @@ -591,101 +606,194 @@
active = true;
}

/**
* Initialize the active coordination mode provider.
* <p>
* This is called early in plugin startup, before any code that might use
* CoordinationModeFactory. Only initializes the provider that matches the configured
* coordination mode, making it more efficient than calling initialize() on all providers.
* <p>
* <strong>Implementation Note:</strong> We cannot use {@code provider.isAvailable()}
* before initialization because isAvailable() checks if the provider is actually initialized.
* Instead, we check the configured mode directly and initialize the matching provider.
* After initialization, isAvailable() will return true.
* <p>
* Fails gracefully - if a provider's initialization fails, it will not be available
* and the factory will fall back to the next highest-priority provider.
*/
private void initializeCoordinationProviders() {
ExtensionList<CoordinationModeProvider> providers = ExtensionList.lookup(CoordinationModeProvider.class);
String configuredMode = CoordinationModeProvider.getConfiguredMode();
logger.debug("Configured coordination mode: {}", configuredMode);

// Try to initialize the configured (non-local) provider first.
// Each provider's initialize() internally checks the configured mode and is a no-op
// if the mode doesn't match, so we try non-local providers in priority order.
// Local is handled separately below as the explicit fallback.
if (!"local".equalsIgnoreCase(configuredMode)) {
for (CoordinationModeProvider provider : providers) {

Check warning on line 634 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 634 is only partially covered, one branch is missing
if (provider instanceof LocalCoordinationProvider) {

Check warning on line 635 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 635 is only partially covered, one branch is missing
continue;

Check warning on line 636 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 636 is not covered by tests
}
try {
logger.info("Initializing coordination provider: {}", provider.getModeName());
provider.initialize();
if (provider.isAvailable()) {

Check warning on line 641 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 641 is only partially covered, one branch is missing
logger.info("Provider {} initialized successfully", provider.getModeName());
return;
}
} catch (Exception e) {
logger.warn("Failed to initialize {} coordination provider. Falling back to Local.",
provider.getModeName(), e);
break;
}
}

Check warning on line 650 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 645-650 are not covered by tests
}

// Explicit fallback to LocalCoordinationProvider, which is always available.
for (CoordinationModeProvider provider : providers) {

Check warning on line 654 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 654 is only partially covered, one branch is missing
if (provider instanceof LocalCoordinationProvider) {
if ("local".equalsIgnoreCase(configuredMode)) {

Check warning on line 656 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 656 is only partially covered, one branch is missing
logger.info("Initializing LocalCoordinationProvider");
} else {
logger.info("Initializing LocalCoordinationProvider (fallback after failed initialization)");

Check warning on line 659 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 659 is not covered by tests
}
try {
provider.initialize();
} catch (Exception e) {
logger.error("Failed to initialize LocalCoordinationProvider - this should never happen", e);

Check warning on line 664 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 663-664 are not covered by tests
}
return;
}
}

logger.error("LocalCoordinationProvider not found - this should never happen");
}

/**
* Forces initialization of the Dispatchers.
*
* They need to register and listen to GerritEvents. Normally, it is lazy loaded when the first build is started.
*/
@Initializer(after = InitMilestone.SYSTEM_CONFIG_ADAPTED)
public static void initializeDispatchers() {
// Call the following method for force initialization of the Dispatchers because
// it needs to register and listen to GerritEvent. Normally, it is lazy loaded when the first build is started.
try {
ExtensionList.lookupSingleton(ReplicationQueueTaskDispatcher.class);
} catch (IllegalStateException e) {
logger.warn("Failed to initialize Replication Queue.", e);
}
try {
ExtensionList.lookupSingleton(DependencyQueueTaskDispatcher.class);
} catch (IllegalStateException e) {
logger.warn("Failed to initialize Dependency Queue.", e);
}
}

/**
* Load plugin config.
*/
@Override
public void load() {
super.load();
if (pluginConfig == null) {
PluginConfig conf = new PluginConfig();
if (config != null) {
conf.setNumberOfReceivingWorkerThreads(config.getNumberOfReceivingWorkerThreads());
conf.setNumberOfSendingWorkerThreads(config.getNumberOfSendingWorkerThreads());
}
pluginConfig = conf;
}
if (servers.isEmpty()) {
if (config != null) { //have loaded data in old format, so add a new server with the old config to the list.
GerritServer defaultServer = new GerritServer(DEFAULT_SERVER_NAME);
defaultServer.setConfig(config);
servers.add(defaultServer);
}
save();
}
pluginConfig.updateEventFilter();
//For unit/integration testing only...
if (System.getProperty(TEST_SSH_KEYFILE_LOCATION_PROPERTY) != null && !servers.isEmpty()) {
File location = new File(System.getProperty(TEST_SSH_KEYFILE_LOCATION_PROPERTY));
for (GerritServer server : servers) {
((Config)server.getConfig()).setGerritAuthKeyFile(location);
}
}
}

/**
* Registers XStream alias and converters to handle backwards compatibility with old data.
*/
protected static void doXStreamRegistrations() {
logger.trace("doing XStream alias registrations.");

//Register it in all known XStreams just to be sure.
Items.XSTREAM.registerConverter(new TriggerContextConverter());
Jenkins.XSTREAM.registerConverter(new TriggerContextConverter());
//This is where the problems where, reading builds.
Run.XSTREAM.registerConverter(new TriggerContextConverter());
Run.XSTREAM2.addCompatibilityAlias(
"com.sonyericsson.hudson.plugins.gerrit.gerritevents.dto.events.ManualPatchsetCreated",
ManualPatchsetCreated.class);

Items.XSTREAM.aliasPackage("com.sonyericsson.hudson.plugins.gerrit.gerritevents",
"com.sonymobile.tools.gerrit.gerritevents");
Jenkins.XSTREAM.aliasPackage("com.sonyericsson.hudson.plugins.gerrit.gerritevents",
"com.sonymobile.tools.gerrit.gerritevents");
Run.XSTREAM.aliasPackage("com.sonyericsson.hudson.plugins.gerrit.gerritevents",
"com.sonymobile.tools.gerrit.gerritevents");

logger.trace("XStream alias registrations done.");
}

/**
* Stop the plugin.
*/
public void stop() {
active = false;

// Shutdown coordination providers before stopping servers
// This ensures any coordination operations are cleaned up before servers disconnect
shutdownCoordinationProviders();

for (GerritServer s : servers) {
s.stop();
}
if (gerritEventManager != null) {
gerritEventManager.shutdown(false);
//TODO save to registered listeners?
gerritEventManager = null;
}
GerritSendCommandQueue.shutdown();
servers.clear();
}

/**
* Shutdown all coordination mode providers.
* <p>
* Called during plugin shutdown to clean up coordination resources.
* Fails gracefully - errors are logged but don't prevent plugin shutdown.
*/
private void shutdownCoordinationProviders() {
logger.debug("Shutting down coordination providers...");
for (com.sonyericsson.hudson.plugins.gerrit.trigger.spi.CoordinationModeProvider provider
: hudson.ExtensionList.lookup(
com.sonyericsson.hudson.plugins.gerrit.trigger.spi.CoordinationModeProvider.class)) {
try {
logger.debug("Shutting down provider: {}", provider.getModeName());
provider.shutdown();
logger.debug("Provider {} shut down successfully", provider.getModeName());
} catch (Exception e) {
logger.warn("Error shutting down coordination provider: {} (non-critical, continuing shutdown)",
provider.getModeName(), e);
// Continue with other providers even if one fails
}
}
logger.debug("Coordination provider shutdown complete");
}

Check warning on line 795 in src/main/java/com/sonyericsson/hudson/plugins/gerrit/trigger/PluginImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 670-795 are not covered by tests

/**
* Startup hook.
*/
Expand Down
Loading
Loading