Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ public enum ExitCode {
QUORUM_PACKET_ERROR(13),

/** Unable to bind to the quorum (election) port after multiple retry */
UNABLE_TO_BIND_QUORUM_PORT(14);
UNABLE_TO_BIND_QUORUM_PORT(14),

/** Failed to shutdown the request processor pipeline gracefully **/
SHUTDOWN_UNGRACEFULLY(16);

private final int value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.common.Time;
import org.apache.zookeeper.server.ExitCode;
import org.apache.zookeeper.server.Request;
import org.apache.zookeeper.server.RequestProcessor;
import org.apache.zookeeper.server.ServerMetrics;
Expand Down Expand Up @@ -621,6 +622,20 @@ public void shutdown() {
workerPool.join(workerShutdownTimeoutMS);
}

try {
this.join(workerShutdownTimeoutMS);
} catch (InterruptedException e) {
LOG.warn("Interrupted while waiting for CommitProcessor to finish");
Thread.currentThread().interrupt();
}

if (this.isAlive()) {
LOG.warn("CommitProcessor does not shutdown gracefully after "
+ "waiting for {} ms, exit to avoid potential "
+ "inconsistency issue", workerShutdownTimeoutMS);
System.exit(ExitCode.SHUTDOWN_UNGRACEFULLY.getValue());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why aren't we using 'halt' ?

'exit' triggers shutdown hooks and they are sometimes source of deadlocks (I don't know in ZK code)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe we don't use shutdown hook in server code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@eolivelli we still try to halt before checking and exit here, but in case the CommitProcessor thread itself cannot shutdown within given time, we need to quit safely. And as @hanm mentioned we don't use shutdown hook here.

}

if (nextProcessor != null) {
nextProcessor.shutdown();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.jute.BinaryOutputArchive;
Expand Down Expand Up @@ -82,13 +84,18 @@ public class CommitProcessorTest extends ZKTestCase {
File tmpDir;
ArrayList<TestClientThread> testClients = new ArrayList<TestClientThread>();
CommitProcessor commitProcessor;
DelayRequestProcessor delayProcessor;

public void setUp(int numCommitThreads, int numClientThreads, int writePercent) throws Exception {
setUp(numCommitThreads, numClientThreads, writePercent, false);
}

public void setUp(int numCommitThreads, int numClientThreads, int writePercent, boolean withDelayProcessor) throws Exception {
stopped = false;
System.setProperty(CommitProcessor.ZOOKEEPER_COMMIT_PROC_NUM_WORKER_THREADS, Integer.toString(numCommitThreads));
tmpDir = ClientBase.createTmpDir();
ClientBase.setupTestEnv();
zks = new TestZooKeeperServer(tmpDir, tmpDir, 4000);
zks = new TestZooKeeperServer(tmpDir, tmpDir, 4000, withDelayProcessor);
zks.startup();
for (int i = 0; i < numClientThreads; ++i) {
TestClientThread client = new TestClientThread(writePercent);
Expand Down Expand Up @@ -211,6 +218,23 @@ public void testNoCommitWorkersReadOnlyWorkload() throws Exception {
assertTrue("Write requests processed", processedWriteRequests.get() == numClients);
}

@Test
public void testWaitingForWriteToFinishBeforeShutdown() throws Exception {
setUp(1, 0, 0, true);

// send a single write request
TestClientThread client = new TestClientThread(0);
client.sendWriteRequest();

// wait for request being committed
delayProcessor.waitRequestProcessing();

zks.shutdown();

// Make sure we've finished the in-flight request before shutdown returns
assertFalse(commitProcessor.isAlive());
}

@Test
public void testNoCommitWorkersMixedWorkload() throws Exception {
int numClients = 10;
Expand Down Expand Up @@ -287,8 +311,15 @@ private synchronized void failTest(String reason) {

private class TestZooKeeperServer extends ZooKeeperServer {

final boolean withDelayProcessor;

public TestZooKeeperServer(File snapDir, File logDir, int tickTime) throws IOException {
this(snapDir, logDir, tickTime, false);
}

public TestZooKeeperServer(File snapDir, File logDir, int tickTime, boolean withDelayProcessor) throws IOException {
super(snapDir, logDir, tickTime);
this.withDelayProcessor = withDelayProcessor;
}

public PrepRequestProcessor getFirstProcessor() {
Expand All @@ -303,7 +334,12 @@ protected void setupRequestProcessors() {
// ValidateProcessor is set up in a similar fashion to ToBeApplied
// processor, so it can do pre/post validating of requests
ValidateProcessor validateProcessor = new ValidateProcessor(finalProcessor);
commitProcessor = new CommitProcessor(validateProcessor, "1", true, null);
if (withDelayProcessor) {
delayProcessor = new DelayRequestProcessor(validateProcessor);
commitProcessor = new CommitProcessor(delayProcessor, "1", true, null);
} else {
commitProcessor = new CommitProcessor(validateProcessor, "1", true, null);
}
validateProcessor.setCommitProcessor(commitProcessor);
commitProcessor.start();
MockProposalRequestProcessor proposalProcessor = new MockProposalRequestProcessor(commitProcessor);
Expand All @@ -314,6 +350,46 @@ protected void setupRequestProcessors() {

}

private class DelayRequestProcessor implements RequestProcessor {
// delay 1s for each request
static final int DEFAULT_DELAY = 1000;
RequestProcessor nextProcessor;
CountDownLatch waitingProcessRequestBeingCalled;

public DelayRequestProcessor(RequestProcessor nextProcessor) {
this.nextProcessor = nextProcessor;
this.waitingProcessRequestBeingCalled = new CountDownLatch(1);
}

@Override
public void processRequest(Request request) throws RequestProcessorException {
try {
this.waitingProcessRequestBeingCalled.countDown();
LOG.info("Sleeping {} ms for request {}", DEFAULT_DELAY, request);
Thread.sleep(DEFAULT_DELAY);
} catch (InterruptedException e) { /* ignore */ }
nextProcessor.processRequest(request);
}

public void waitRequestProcessing() {
try {
if (!waitingProcessRequestBeingCalled.await(3000, TimeUnit.MILLISECONDS)) {
LOG.info("Did not see request processing in 3s");
}
} catch (InterruptedException e) {
LOG.info("Interrupted when waiting for processRequest being called");
}
}

@Override
public void shutdown() {
LOG.info("shutdown DelayRequestProcessor");
if (nextProcessor != null) {
nextProcessor.shutdown();
}
}
}

private class MockProposalRequestProcessor extends Thread implements RequestProcessor {

private final CommitProcessor commitProcessor;
Expand Down