Skip to content

Commit 81e7698

Browse files
c11epmartembilan
authored andcommitted
GH-9398: Fix possibility to have multiple HZ leaders
Fixes: #9398 Adding a check if the Hazelcast node have the leader property set but can't acquire the leadership lock. If this happens, the node should revoke the leadership since another node is the "true leader" (cherry picked from commit c850021) # Conflicts: # spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java
1 parent 33a1e18 commit 81e7698

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

spring-integration-hazelcast/src/main/java/org/springframework/integration/hazelcast/leader/LeaderInitiator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @author Alexey Tsoy
5858
* @author Robert Höglund
5959
* @author Christian Tzolov
60+
* @author Emil Palm
6061
*/
6162
public class LeaderInitiator implements SmartLifecycle, DisposableBean, ApplicationEventPublisherAware {
6263

@@ -320,6 +321,10 @@ public Void call() {
320321
this.leader = true;
321322
handleGranted();
322323
}
324+
if (!acquired && this.leader) {
325+
//If we no longer can acquire the lock but still have the leader status
326+
revokeLeadership();
327+
}
323328
}
324329
}
325330
catch (Exception ex) {

spring-integration-hazelcast/src/test/java/org/springframework/integration/hazelcast/leader/LeaderInitiatorTests.java

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,33 @@
2424
import com.hazelcast.config.Config;
2525
import com.hazelcast.core.Hazelcast;
2626
import com.hazelcast.core.HazelcastInstance;
27-
import org.junit.Test;
28-
import org.junit.runner.RunWith;
27+
import com.hazelcast.cp.CPGroupId;
28+
import com.hazelcast.cp.CPSubsystem;
29+
import com.hazelcast.cp.lock.FencedLock;
30+
import org.junit.jupiter.api.Test;
2931

3032
import org.springframework.beans.factory.annotation.Autowired;
3133
import org.springframework.context.ApplicationListener;
3234
import org.springframework.context.annotation.Bean;
3335
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.integration.leader.Candidate;
3437
import org.springframework.integration.leader.Context;
3538
import org.springframework.integration.leader.DefaultCandidate;
3639
import org.springframework.integration.leader.event.AbstractLeaderEvent;
3740
import org.springframework.integration.leader.event.DefaultLeaderEventPublisher;
3841
import org.springframework.integration.leader.event.LeaderEventPublisher;
3942
import org.springframework.test.annotation.DirtiesContext;
40-
import org.springframework.test.context.ContextConfiguration;
41-
import org.springframework.test.context.junit4.SpringRunner;
43+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
4244

4345
import static org.assertj.core.api.Assertions.assertThat;
4446
import static org.mockito.ArgumentMatchers.any;
47+
import static org.mockito.ArgumentMatchers.anyLong;
48+
import static org.mockito.ArgumentMatchers.anyString;
49+
import static org.mockito.BDDMockito.given;
4550
import static org.mockito.BDDMockito.willAnswer;
51+
import static org.mockito.Mockito.mock;
4652
import static org.mockito.Mockito.spy;
53+
import static org.mockito.Mockito.verify;
4754

4855
/**
4956
* Tests for hazelcast leader election.
@@ -53,9 +60,9 @@
5360
* @author Dave Syer
5461
* @author Artem Bilan
5562
* @author Mael Le Guével
63+
* @author Emil Palm
5664
*/
57-
@RunWith(SpringRunner.class)
58-
@ContextConfiguration
65+
@SpringJUnitConfig
5966
@DirtiesContext
6067
public class LeaderInitiatorTests {
6168

@@ -205,35 +212,82 @@ public void publishOnGranted(Object source, Context context, String role) {
205212
initiator.destroy();
206213
}
207214

215+
@Test
216+
public void testRevokeLeadershipCalledWhenLockNotAcquiredButStillLeader() throws Exception {
217+
// Initialize mocks and objects needed for the revoke leadership when fenced lock is no longer acquired
218+
HazelcastInstance hazelcastInstance = mock();
219+
Candidate candidate = mock();
220+
FencedLock fencedLock = mock();
221+
LeaderEventPublisher leaderEventPublisher = mock();
222+
223+
CPSubsystem cpSubsystem = mock(CPSubsystem.class);
224+
given(candidate.getRole()).willReturn("role");
225+
given(hazelcastInstance.getCPSubsystem()).willReturn(cpSubsystem);
226+
given(cpSubsystem.getLock(anyString())).willReturn(fencedLock);
227+
given(fencedLock.getGroupId())
228+
.willReturn(new CPGroupId() {
229+
230+
@Override
231+
public String getName() {
232+
return "";
233+
}
234+
235+
@Override
236+
public long getId() {
237+
return 0;
238+
}
239+
});
240+
241+
LeaderInitiator leaderInitiator = new LeaderInitiator(hazelcastInstance, candidate);
242+
leaderInitiator.setLeaderEventPublisher(leaderEventPublisher);
243+
244+
// Simulate that the lock is currently held by this thread
245+
given(fencedLock.isLockedByCurrentThread()).willReturn(true, false);
246+
given(fencedLock.tryLock(anyLong(), any(TimeUnit.class))).willReturn(false); // Lock acquisition fails
247+
248+
// Start the LeaderInitiator to trigger the leader election process
249+
leaderInitiator.start();
250+
251+
// Simulate the lock acquisition check process
252+
Thread.sleep(1000); // Give time for the async task to run
253+
254+
// Verify that revokeLeadership was called due to lock not being acquired
255+
// unlock is part of revokeLeadership
256+
verify(fencedLock).unlock();
257+
// verify revoke event is published
258+
verify(leaderEventPublisher).publishOnRevoked(any(Object.class), any(Context.class), anyString());
259+
260+
leaderInitiator.destroy();
261+
}
208262

209263
@Configuration
210264
public static class TestConfig {
211265

212266
@Bean
213-
public TestCandidate candidate() {
267+
TestCandidate candidate() {
214268
return new TestCandidate();
215269
}
216270

217271
@Bean
218-
public Config hazelcastConfig() {
272+
Config hazelcastConfig() {
219273
Config config = new Config();
220274
config.getCPSubsystemConfig()
221275
.setSessionHeartbeatIntervalSeconds(1);
222276
return config;
223277
}
224278

225279
@Bean(destroyMethod = "shutdown")
226-
public HazelcastInstance hazelcastInstance() {
280+
HazelcastInstance hazelcastInstance() {
227281
return Hazelcast.newHazelcastInstance(hazelcastConfig());
228282
}
229283

230284
@Bean
231-
public LeaderInitiator initiator() {
285+
LeaderInitiator initiator() {
232286
return new LeaderInitiator(hazelcastInstance(), candidate());
233287
}
234288

235289
@Bean
236-
public TestEventListener testEventListener() {
290+
TestEventListener testEventListener() {
237291
return new TestEventListener();
238292
}
239293

0 commit comments

Comments
 (0)