Skip to content

Commit 082be54

Browse files
committed
finished interprocess mutex
1 parent 53bacfa commit 082be54

24 files changed

+594
-607
lines changed

coordination/src/main/java/tech/ydb/coordination/recipes/election/LeaderElection.java

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import java.util.List;
66
import java.util.Optional;
77
import java.util.concurrent.Callable;
8+
import java.util.concurrent.ExecutionException;
89
import java.util.concurrent.ExecutorService;
9-
import java.util.concurrent.Executors;
1010
import java.util.concurrent.Future;
1111
import java.util.concurrent.atomic.AtomicReference;
1212
import java.util.stream.Collectors;
@@ -16,36 +16,40 @@
1616
import org.slf4j.Logger;
1717
import org.slf4j.LoggerFactory;
1818

19-
import tech.ydb.common.retry.RetryForever;
2019
import tech.ydb.coordination.CoordinationClient;
2120
import tech.ydb.coordination.CoordinationSession;
2221
import tech.ydb.coordination.description.SemaphoreDescription;
2322
import tech.ydb.coordination.recipes.locks.LockInternals;
2423
import tech.ydb.coordination.recipes.util.Listenable;
24+
import tech.ydb.coordination.recipes.util.ListenableContainer;
2525
import tech.ydb.coordination.recipes.util.SessionListenableProvider;
2626
import tech.ydb.coordination.recipes.util.SemaphoreObserver;
2727
import tech.ydb.coordination.settings.DescribeSemaphoreMode;
2828
import tech.ydb.coordination.settings.WatchSemaphoreMode;
29+
import tech.ydb.core.Status;
2930
import tech.ydb.core.StatusCode;
3031

31-
// TODO: настройки + переименовать переменные + рекомендации по коду + логгирование + backoff политика
32+
// TODO: backoff политика + документцаия / логгирование / рекомендации по коду
3233
public class LeaderElection implements Closeable, SessionListenableProvider {
3334
private static final Logger logger = LoggerFactory.getLogger(LeaderElection.class);
3435
private static final long MAX_LEASE = 1L;
3536

36-
private final CoordinationClient client;
3737
private final LeaderElectionListener leaderElectionListener;
3838
private final String coordinationNodePath;
3939
private final String electionName;
4040
private final byte[] data;
41+
4142
private final ExecutorService electionExecutor;
43+
private final CoordinationSession coordinationSession;
44+
private final ListenableContainer<CoordinationSession.State> sessionListenable;
4245
private final LockInternals lock;
4346
private final SemaphoreObserver semaphoreObserver;
4447

4548
private AtomicReference<State> state = new AtomicReference<>(State.CREATED);
49+
private Future<Status> sessionConnectionTask = null;
50+
private Future<Void> electionTask = null;
4651
private volatile boolean autoRequeue = false;
4752
private volatile boolean isLeader = false;
48-
private Future<Void> electionTask = null;
4953

5054
private enum State {
5155
CREATED,
@@ -55,62 +59,77 @@ private enum State {
5559

5660
public LeaderElection(
5761
CoordinationClient client,
58-
LeaderElectionListener leaderElectionListener,
5962
String coordinationNodePath,
6063
String electionName,
61-
byte[] data
64+
byte[] data,
65+
LeaderElectionListener leaderElectionListener
6266
) {
6367
this(
6468
client,
65-
leaderElectionListener,
6669
coordinationNodePath,
6770
electionName,
6871
data,
69-
Executors.newSingleThreadExecutor()
72+
leaderElectionListener,
73+
LeaderElectionSettings.newBuilder()
74+
.build()
7075
);
7176
}
7277

7378
public LeaderElection(
7479
CoordinationClient client,
75-
LeaderElectionListener leaderElectionListener,
7680
String coordinationNodePath,
7781
String electionName,
7882
byte[] data,
79-
ExecutorService executorService
83+
LeaderElectionListener leaderElectionListener,
84+
LeaderElectionSettings settings
8085
) {
81-
this.client = client;
82-
this.leaderElectionListener = leaderElectionListener;
8386
this.coordinationNodePath = coordinationNodePath;
8487
this.electionName = electionName;
8588
this.data = data;
86-
this.electionExecutor = executorService;
89+
this.leaderElectionListener = leaderElectionListener;
90+
this.electionExecutor = settings.getExecutorService();
91+
92+
this.coordinationSession = client.createSession(coordinationNodePath);
93+
this.sessionListenable = new ListenableContainer<>();
94+
coordinationSession.addStateListener(sessionListenable::notifyListeners);
8795
this.lock = new LockInternals(
88-
MAX_LEASE,
89-
client,
90-
coordinationNodePath,
91-
electionName
96+
coordinationSession,
97+
electionName,
98+
MAX_LEASE
9299
);
93100
this.semaphoreObserver = new SemaphoreObserver(
94-
lock.getCoordinationSession(),
101+
coordinationSession,
95102
electionName,
96103
WatchSemaphoreMode.WATCH_OWNERS,
97104
DescribeSemaphoreMode.WITH_OWNERS_AND_WAITERS,
98-
new RetryForever(100) // TODO: передавать снаружи
105+
settings.getRetryPolicy()
99106
);
100107
}
101108

109+
private CoordinationSession connectedSession() {
110+
if (sessionConnectionTask == null) {
111+
throw new IllegalStateException("Not started yet");
112+
}
113+
try {
114+
sessionConnectionTask.get().expectSuccess("Unable to connect to session");
115+
} catch (InterruptedException | ExecutionException e) {
116+
throw new RuntimeException(e);
117+
}
118+
return coordinationSession;
119+
}
120+
102121
public void start() {
103122
Preconditions.checkState(state.compareAndSet(State.CREATED, State.STARTED), "Already started or closed");
104-
// TODO: create session?
105-
CoordinationSession coordinationSession = lock.getCoordinationSession();
106-
// TODO: retry on create? Non idempotent - will not be retried automatically
107-
lock.start();
108-
coordinationSession.createSemaphore(electionName, MAX_LEASE).thenAccept(status -> {
109-
if (status.isSuccess() || status.getCode() == StatusCode.ALREADY_EXISTS) {
110-
semaphoreObserver.start();
111-
}
112-
status.expectSuccess("Unable to create semaphore");
113-
// TODO: set status == error
123+
// TODO: handle errors retries and logging?
124+
this.sessionConnectionTask = coordinationSession.connect().thenCompose(connectionStatus -> {
125+
connectionStatus.expectSuccess("Unable to establish session");
126+
return coordinationSession.createSemaphore(electionName, MAX_LEASE).thenApply(semaphoreStatus -> {
127+
if (semaphoreStatus.isSuccess() || semaphoreStatus.getCode() == StatusCode.ALREADY_EXISTS) {
128+
semaphoreObserver.start();
129+
}
130+
semaphoreStatus.expectSuccess("Unable to create semaphore");
131+
return semaphoreStatus;
132+
});
114133
});
115134

116135
if (autoRequeue) {
@@ -173,7 +192,6 @@ private void doWork() throws Exception {
173192
isLeader = false;
174193

175194
try {
176-
lock.getConnectedCoordinationSession(); // asserts that session is connected or throws exception
177195
lock.tryAcquire(
178196
null,
179197
true,
@@ -221,6 +239,7 @@ private boolean isQueued() {
221239

222240
/**
223241
* Не гарантированы все, кроме лидера
242+
*
224243
* @return
225244
*/
226245
public List<ElectionParticipant> getParticipants() {
@@ -257,7 +276,7 @@ private static ElectionParticipant mapParticipant(SemaphoreDescription.Session s
257276

258277
@Override
259278
public Listenable<CoordinationSession.State> getSessionListenable() {
260-
return lock.getSessionListenable();
279+
return sessionListenable;
261280
}
262281

263282
@Override
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package tech.ydb.coordination.recipes.election;
2+
3+
import java.time.Duration;
4+
import java.util.concurrent.ExecutorService;
5+
import java.util.concurrent.Executors;
6+
7+
import tech.ydb.common.retry.RetryPolicy;
8+
import tech.ydb.common.retry.RetryUntilElapsed;
9+
10+
public class LeaderElectionSettings {
11+
public static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(5);
12+
public static final RetryUntilElapsed DEFAULT_RETRY_POLICY = new RetryUntilElapsed(
13+
DEFAULT_CONNECT_TIMEOUT.toMillis(), 250, 5
14+
);
15+
16+
private final ExecutorService executorService;
17+
private final RetryPolicy retryPolicy;
18+
19+
public LeaderElectionSettings(Builder builder) {
20+
this.executorService = builder.executorService;
21+
this.retryPolicy = builder.retryPolicy;
22+
}
23+
24+
public ExecutorService getExecutorService() {
25+
return executorService;
26+
}
27+
28+
public RetryPolicy getRetryPolicy() {
29+
return retryPolicy;
30+
}
31+
32+
public static Builder newBuilder() {
33+
return new Builder();
34+
}
35+
36+
public static class Builder {
37+
private ExecutorService executorService = Executors.newSingleThreadExecutor();
38+
private RetryPolicy retryPolicy = DEFAULT_RETRY_POLICY;
39+
40+
public Builder withExecutorService(ExecutorService executorService) {
41+
this.executorService = executorService;
42+
return this;
43+
}
44+
45+
public Builder withRetryPolicy(RetryPolicy retryPolicy) {
46+
this.retryPolicy = retryPolicy;
47+
return this;
48+
}
49+
50+
public LeaderElectionSettings build() {
51+
return new LeaderElectionSettings(this);
52+
}
53+
}
54+
}

coordination/src/main/java/tech/ydb/coordination/recipes/group/GroupMembershipImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void start() {
7777
session.connect().thenAccept(sessionStatus -> {
7878
sessionStatus.expectSuccess("Unable to establish session");
7979
session.createSemaphore(groupName, MAX_GROUP_SIZE).thenAccept(semaphoreStatus -> {
80-
lockInternals.start();
80+
// TODO: start acquiring task
8181
semaphoreObserver.start();
8282
});
8383
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package tech.ydb.coordination.recipes.group;
2+
3+
public class GroupMembershipSettings {
4+
}

coordination/src/main/java/tech/ydb/coordination/recipes/locks/InterProcessLock.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,51 @@
22

33
import java.time.Duration;
44

5+
import tech.ydb.coordination.recipes.locks.exception.LockAcquireFailedException;
6+
import tech.ydb.coordination.recipes.locks.exception.LockAlreadyAcquiredException;
7+
import tech.ydb.coordination.recipes.locks.exception.LockReleaseFailedException;
8+
import tech.ydb.coordination.recipes.locks.exception.LockStateException;
59
import tech.ydb.coordination.recipes.util.SessionListenableProvider;
610

711
public interface InterProcessLock extends SessionListenableProvider {
8-
void acquire() throws Exception, LockAlreadyAcquiredException, LockAcquireFailedException;
12+
/**
13+
* Acquires the distributed lock, blocking until it is obtained.
14+
*
15+
* @throws Exception if an unexpected error occurs
16+
* @throws LockAlreadyAcquiredException if the lock is already acquired by this process
17+
* @throws LockAcquireFailedException if the lock acquisition fails
18+
* @throws LockStateException if the lock is in invalid state for acquisition
19+
*/
20+
void acquire() throws Exception, LockAlreadyAcquiredException, LockAcquireFailedException, LockStateException;
921

1022
/**
11-
* @return true - if successfully acquired lock, false - if lock waiting time expired
23+
* Attempts to acquire the lock within the given waiting time.
24+
*
25+
* @param waitDuration maximum time to wait for the lock
26+
* @return true if the lock was acquired, false if the waiting time elapsed
27+
* @throws Exception if an unexpected error occurs
28+
* @throws LockAlreadyAcquiredException if the lock is already acquired by this process
29+
* @throws LockAcquireFailedException if the lock acquisition fails
30+
* @throws LockStateException if the lock is in invalid state for acquisition
1231
*/
13-
boolean acquire(Duration waitDuration) throws Exception, LockAlreadyAcquiredException, LockAcquireFailedException;
32+
boolean acquire(Duration waitDuration) throws Exception, LockAlreadyAcquiredException, LockAcquireFailedException,
33+
LockStateException;
1434

1535
/**
16-
* @return false if nothing to release
36+
* Releases the lock if it is held by this process.
37+
*
38+
* @return false if there was nothing to release
39+
* @throws InterruptedException if the thread is interrupted
40+
* @throws LockReleaseFailedException if the lock release fails
41+
* @throws LockStateException if the lock is in invalid state for release
1742
*/
18-
boolean release() throws InterruptedException, LockReleaseFailedException;
43+
boolean release() throws InterruptedException, LockReleaseFailedException, LockStateException;
44+
1945

2046
/**
21-
* @return true if the lock is acquired by a thread in this JVM
47+
* Checks if the lock is currently acquired by this process.
48+
*
49+
* @return true if the lock is acquired by this process
2250
*/
2351
boolean isAcquiredInThisProcess();
2452
}

0 commit comments

Comments
 (0)