You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by tr...@apache.org on 2019/03/29 09:38:45 UTC

[flink] 01/03: [FLINK-11855] Fix race condition in EmbeddedLeaderService#GrantLeadershipCall

This is an automated email from the ASF dual-hosted git repository.

trohrmann pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git

commit ccf27ff845836d74bcb94a9b5baf4bc171ea2ff3
Author: Till Rohrmann <tr...@apache.org>
AuthorDate: Thu Mar 7 17:28:33 2019 +0100

    [FLINK-11855] Fix race condition in EmbeddedLeaderService#GrantLeadershipCall
    
    Fix the race condition between executing EmbeddedLeaderService#GrantLeadershipCall
    and a concurrent shutdown of the leader service by making GrantLeadershipCall not
    accessing mutable state outside of a lock.
    
    This closes #7935.
---
 .../nonha/embedded/EmbeddedLeaderService.java      |  46 +++++----
 .../nonha/embedded/EmbeddedHaServicesTest.java     |  44 +--------
 .../nonha/embedded/EmbeddedLeaderServiceTest.java  | 103 +++++++++++++++++++++
 .../nonha/embedded/TestingLeaderContender.java     |  87 +++++++++++++++++
 4 files changed, 221 insertions(+), 59 deletions(-)

diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderService.java b/flink-runtime/src/main/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderService.java
index 97b80dc..e3e98e1 100644
--- a/flink-runtime/src/main/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderService.java
+++ b/flink-runtime/src/main/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderService.java
@@ -102,6 +102,13 @@ public class EmbeddedLeaderService {
 		}
 	}
 
+	@VisibleForTesting
+	public boolean isShutdown() {
+		synchronized (lock) {
+			return shutdown;
+		}
+	}
+
 	private void fatalError(Throwable error) {
 		LOG.error("Embedded leader election service encountered a fatal error. Shutting down service.", error);
 
@@ -171,7 +178,11 @@ public class EmbeddedLeaderService {
 				service.contender = contender;
 				service.running = true;
 
-				updateLeader();
+				updateLeader().whenComplete((aVoid, throwable) -> {
+					if (throwable != null) {
+						fatalError(throwable);
+					}
+				});
 			}
 			catch (Throwable t) {
 				fatalError(t);
@@ -210,7 +221,11 @@ public class EmbeddedLeaderService {
 					currentLeaderSessionId = null;
 				}
 
-				updateLeader();
+				updateLeader().whenComplete((aVoid, throwable) -> {
+					if (throwable != null) {
+						fatalError(throwable);
+					}
+				});
 			}
 			catch (Throwable t) {
 				fatalError(t);
@@ -280,11 +295,12 @@ public class EmbeddedLeaderService {
 
 				currentLeaderSessionId = leaderSessionId;
 				currentLeaderProposed = leaderService;
+				currentLeaderProposed.isLeader = true;
 
 				LOG.info("Proposing leadership to contender {} @ {}",
 						leaderService.contender, leaderService.contender.getAddress());
 
-				return execute(new GrantLeadershipCall(leaderService, leaderSessionId, LOG));
+				return execute(new GrantLeadershipCall(leaderService.contender, leaderSessionId, LOG));
 			}
 		} else {
 			return CompletableFuture.completedFuture(null);
@@ -373,7 +389,8 @@ public class EmbeddedLeaderService {
 				}
 
 				LOG.info("Revoking leadership of {}.", leaderService.contender);
-				CompletableFuture<Void> revokeLeadershipCallFuture = execute(new RevokeLeadershipCall(leaderService));
+				leaderService.isLeader = false;
+				CompletableFuture<Void> revokeLeadershipCallFuture = execute(new RevokeLeadershipCall(leaderService.contender));
 
 				CompletableFuture<Void> notifyAllListenersFuture = notifyAllListeners(null, null);
 
@@ -506,33 +523,28 @@ public class EmbeddedLeaderService {
 
 	private static class GrantLeadershipCall implements Runnable {
 
-		private final EmbeddedLeaderElectionService leaderElectionService;
+		private final LeaderContender contender;
 		private final UUID leaderSessionId;
 		private final Logger logger;
 
 		GrantLeadershipCall(
-				EmbeddedLeaderElectionService leaderElectionService,
+				LeaderContender contender,
 				UUID leaderSessionId,
 				Logger logger) {
 
-			this.leaderElectionService = checkNotNull(leaderElectionService);
+			this.contender = checkNotNull(contender);
 			this.leaderSessionId = checkNotNull(leaderSessionId);
 			this.logger = checkNotNull(logger);
 		}
 
 		@Override
 		public void run() {
-			leaderElectionService.isLeader = true;
-
-			final LeaderContender contender = leaderElectionService.contender;
-
 			try {
 				contender.grantLeadership(leaderSessionId);
 			}
 			catch (Throwable t) {
 				logger.warn("Error granting leadership to contender", t);
 				contender.handleError(t instanceof Exception ? (Exception) t : new Exception(t));
-				leaderElectionService.isLeader = false;
 			}
 		}
 	}
@@ -540,17 +552,15 @@ public class EmbeddedLeaderService {
 	private static class RevokeLeadershipCall implements Runnable {
 
 		@Nonnull
-		private final EmbeddedLeaderElectionService leaderElectionService;
+		private final LeaderContender contender;
 
-		RevokeLeadershipCall(@Nonnull EmbeddedLeaderElectionService leaderElectionService) {
-			this.leaderElectionService = leaderElectionService;
+		RevokeLeadershipCall(@Nonnull LeaderContender contender) {
+			this.contender = contender;
 		}
 
 		@Override
 		public void run() {
-			leaderElectionService.isLeader = false;
-
-			leaderElectionService.contender.revokeLeadership();
+			contender.revokeLeadership();
 		}
 	}
 }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedHaServicesTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedHaServicesTest.java
index 2455bf0..7f06d60 100644
--- a/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedHaServicesTest.java
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedHaServicesTest.java
@@ -24,7 +24,6 @@ import org.apache.flink.runtime.leaderelection.LeaderContender;
 import org.apache.flink.runtime.leaderelection.LeaderElectionService;
 import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalListener;
 import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService;
-import org.apache.flink.util.ExceptionUtils;
 import org.apache.flink.util.TestLogger;
 
 import org.junit.After;
@@ -33,9 +32,6 @@ import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
 import java.util.UUID;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.atomic.AtomicReference;
 
 import static junit.framework.TestCase.assertTrue;
 import static org.hamcrest.Matchers.is;
@@ -179,12 +175,11 @@ public class EmbeddedHaServicesTest extends TestLogger {
 	@Test
 	public void testConcurrentLeadershipOperations() throws Exception {
 		final LeaderElectionService dispatcherLeaderElectionService = embeddedHaServices.getDispatcherLeaderElectionService();
-		final ArrayBlockingQueue<UUID> offeredSessionIds = new ArrayBlockingQueue<>(2);
-		final TestingLeaderContender leaderContender = new TestingLeaderContender(offeredSessionIds);
+		final TestingLeaderContender leaderContender = new TestingLeaderContender();
 
 		dispatcherLeaderElectionService.start(leaderContender);
 
-		final UUID oldLeaderSessionId = offeredSessionIds.take();
+		final UUID oldLeaderSessionId = leaderContender.getLeaderSessionFuture().get();
 
 		assertThat(dispatcherLeaderElectionService.hasLeadership(oldLeaderSessionId), is(true));
 
@@ -192,7 +187,7 @@ public class EmbeddedHaServicesTest extends TestLogger {
 		assertThat(dispatcherLeaderElectionService.hasLeadership(oldLeaderSessionId), is(false));
 
 		embeddedHaServices.getDispatcherLeaderService().grantLeadership();
-		final UUID newLeaderSessionId = offeredSessionIds.take();
+		final UUID newLeaderSessionId = leaderContender.getLeaderSessionFuture().get();
 
 		assertThat(dispatcherLeaderElectionService.hasLeadership(newLeaderSessionId), is(true));
 
@@ -204,37 +199,4 @@ public class EmbeddedHaServicesTest extends TestLogger {
 		leaderContender.tryRethrowException();
 	}
 
-	private static final class TestingLeaderContender implements LeaderContender {
-
-		private final BlockingQueue<UUID> offeredSessionIds;
-
-		private final AtomicReference<Exception> occurredException;
-
-		private TestingLeaderContender(BlockingQueue<UUID> offeredSessionIds) {
-			this.offeredSessionIds = offeredSessionIds;
-			occurredException = new AtomicReference<>(null);
-		}
-
-		@Override
-		public void grantLeadership(UUID leaderSessionID) {
-			offeredSessionIds.offer(leaderSessionID);
-		}
-
-		@Override
-		public void revokeLeadership() {}
-
-		@Override
-		public String getAddress() {
-			return "foobar";
-		}
-
-		@Override
-		public void handleError(Exception exception) {
-			occurredException.compareAndSet(null, exception);
-		}
-
-		public void tryRethrowException() throws Exception {
-			ExceptionUtils.tryRethrowException(occurredException.get());
-		}
-	}
 }
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderServiceTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderServiceTest.java
new file mode 100644
index 0000000..6ecc967
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/EmbeddedLeaderServiceTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.flink.runtime.highavailability.nonha.embedded;
+
+import org.apache.flink.runtime.leaderelection.LeaderElectionService;
+import org.apache.flink.runtime.testingUtils.TestingUtils;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for the {@link EmbeddedLeaderService}.
+ */
+public class EmbeddedLeaderServiceTest extends TestLogger {
+
+	/**
+	 * Tests that the {@link EmbeddedLeaderService} can handle a concurrent grant
+	 * leadership call and a shutdown.
+	 */
+	@Test
+	public void testConcurrentGrantLeadershipAndShutdown() throws Exception {
+		final EmbeddedLeaderService embeddedLeaderService = new EmbeddedLeaderService(TestingUtils.defaultExecutor());
+
+		try {
+			final LeaderElectionService leaderElectionService = embeddedLeaderService.createLeaderElectionService();
+
+			final TestingLeaderContender contender = new TestingLeaderContender();
+
+			leaderElectionService.start(contender);
+			leaderElectionService.stop();
+
+			try {
+				// check that no exception occurred
+				contender.getLeaderSessionFuture().get(10L, TimeUnit.MILLISECONDS);
+			} catch (TimeoutException ignored) {
+				// we haven't participated in the leader election
+			}
+
+			// the election service should still be running
+			Assert.assertThat(embeddedLeaderService.isShutdown(), is(false));
+		} finally {
+			embeddedLeaderService.shutdown();
+		}
+	}
+
+	/**
+	 * Tests that the {@link EmbeddedLeaderService} can handle a concurrent revoke
+	 * leadership call and a shutdown.
+	 */
+	@Test
+	public void testConcurrentRevokeLeadershipAndShutdown() throws Exception {
+		final EmbeddedLeaderService embeddedLeaderService = new EmbeddedLeaderService(TestingUtils.defaultExecutor());
+
+		try {
+			final LeaderElectionService leaderElectionService = embeddedLeaderService.createLeaderElectionService();
+
+			final TestingLeaderContender contender = new TestingLeaderContender();
+
+			leaderElectionService.start(contender);
+
+			// wait for the leadership
+			contender.getLeaderSessionFuture().get();
+
+			final CompletableFuture<Void> revokeLeadershipFuture = embeddedLeaderService.revokeLeadership();
+			leaderElectionService.stop();
+
+			try {
+				// check that no exception occurred
+				revokeLeadershipFuture.get(10L, TimeUnit.MILLISECONDS);
+			} catch (TimeoutException ignored) {
+				// the leader election service has been stopped before revoking could be executed
+			}
+
+			// the election service should still be running
+			Assert.assertThat(embeddedLeaderService.isShutdown(), is(false));
+		} finally {
+			embeddedLeaderService.shutdown();
+		}
+	}
+}
diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/TestingLeaderContender.java b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/TestingLeaderContender.java
new file mode 100644
index 0000000..0b28671
--- /dev/null
+++ b/flink-runtime/src/test/java/org/apache/flink/runtime/highavailability/nonha/embedded/TestingLeaderContender.java
@@ -0,0 +1,87 @@
+/*
+ * 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.flink.runtime.highavailability.nonha.embedded;
+
+import org.apache.flink.runtime.concurrent.FutureUtils;
+import org.apache.flink.runtime.leaderelection.LeaderContender;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * {@link LeaderContender} implementation for testing purposes.
+ */
+final class TestingLeaderContender implements LeaderContender {
+
+	private final Object lock = new Object();
+
+	private CompletableFuture<UUID> leaderSessionFuture;
+
+	TestingLeaderContender() {
+		leaderSessionFuture = new CompletableFuture<>();
+	}
+
+	@Override
+	public void grantLeadership(UUID leaderSessionID) {
+		synchronized (lock) {
+			if (!leaderSessionFuture.isCompletedExceptionally()) {
+				if (!leaderSessionFuture.complete(leaderSessionID)) {
+					leaderSessionFuture = CompletableFuture.completedFuture(leaderSessionID);
+				}
+			}
+		}
+	}
+
+	@Override
+	public void revokeLeadership() {
+		synchronized (lock) {
+			if (leaderSessionFuture.isDone() && !leaderSessionFuture.isCompletedExceptionally()) {
+				leaderSessionFuture = new CompletableFuture<>();
+			}
+		}
+	}
+
+	@Override
+	public String getAddress() {
+		return "foobar";
+	}
+
+	@Override
+	public void handleError(Exception exception) {
+		synchronized (lock) {
+			if (!(leaderSessionFuture.isCompletedExceptionally() || leaderSessionFuture.completeExceptionally(exception))) {
+				leaderSessionFuture = FutureUtils.completedExceptionally(exception);
+			}
+		}
+	}
+
+	public void tryRethrowException() {
+		synchronized (lock) {
+			if (leaderSessionFuture.isCompletedExceptionally()) {
+				leaderSessionFuture.getNow(null);
+			}
+		}
+	}
+
+	CompletableFuture<UUID> getLeaderSessionFuture() {
+		synchronized (lock) {
+			return leaderSessionFuture;
+		}
+	}
+}