From 904cc30c999b2b7396b84ba9a584c74ee7005624 Mon Sep 17 00:00:00 2001 From: Samuel Just Date: Wed, 23 Jan 2019 14:25:17 -0800 Subject: [PATCH] ZOOKEEPER-3253: client should not send requests with cxid=-4, -2, or -1 - Add test for cxid rollover to 1 - Modify ClientCnxn.SendThread.getXid() to increment from MAX to 1. --- .../java/org/apache/zookeeper/ClientCnxn.java | 9 ++++- .../apache/zookeeper/TestableZooKeeper.java | 34 ++++++++++++++++++ .../org/apache/zookeeper/test/ClientTest.java | 35 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxn.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxn.java index 5c4171ae984..50f55a6e619 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxn.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ClientCnxn.java @@ -1500,7 +1500,8 @@ public void close() throws IOException { } } - private int xid = 1; + // @VisibleForTesting + protected int xid = 1; // @VisibleForTesting volatile States state = States.NOT_CONNECTED; @@ -1510,6 +1511,12 @@ public void close() throws IOException { * the server. Thus, getXid() must be public. */ synchronized public int getXid() { + // Avoid negative cxid values. In particular, cxid values of -4, -2, and -1 are special and + // must not be used for requests -- see SendThread.readResponse. + // Skip from MAX to 1. + if (xid == Integer.MAX_VALUE) { + xid = 1; + } return xid++; } diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/TestableZooKeeper.java b/zookeeper-server/src/test/java/org/apache/zookeeper/TestableZooKeeper.java index bb1bd12e162..34c8c0cba5e 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/TestableZooKeeper.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/TestableZooKeeper.java @@ -26,6 +26,7 @@ import org.apache.jute.Record; import org.apache.zookeeper.admin.ZooKeeperAdmin; +import org.apache.zookeeper.client.HostProvider; import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.RequestHeader; @@ -35,6 +36,39 @@ public TestableZooKeeper(String host, int sessionTimeout, Watcher watcher) throws IOException { super(host, sessionTimeout, watcher); } + + class TestableClientCnxn extends ClientCnxn { + TestableClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper, + ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, boolean canBeReadOnly) + throws IOException { + super(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher, + clientCnxnSocket, 0, new byte[16], canBeReadOnly); + } + + void setXid(int newXid) { + xid = newXid; + } + + int checkXid() { + return xid; + } + } + + protected ClientCnxn createConnection(String chrootPath, + HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper, + ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, + boolean canBeReadOnly) throws IOException { + return new TestableClientCnxn(chrootPath, hostProvider, sessionTimeout, this, + watcher, clientCnxnSocket, canBeReadOnly); + } + + public void setXid(int xid) { + ((TestableClientCnxn)cnxn).setXid(xid); + } + + public int checkXid() { + return ((TestableClientCnxn)cnxn).checkXid(); + } @Override public List getChildWatches() { diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientTest.java index c337e3c9a6c..0b5b3c8afb8 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/ClientTest.java @@ -26,6 +26,8 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.KeeperException.Code; @@ -868,4 +870,37 @@ public void testTryWithResources() throws Exception { Assert.assertFalse(zooKeeper.getState().isAlive()); } + + @Test + public void testCXidRollover() throws Exception { + TestableZooKeeper zk = null; + try { + zk = createClient(); + zk.setXid(Integer.MAX_VALUE - 10); + + zk.create("/testnode", "".getBytes(), Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + for (int i = 0; i < 20; ++i) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicInteger rc = new AtomicInteger(0); + zk.setData("/testnode", "".getBytes(), -1, + new AsyncCallback.StatCallback() { + @Override + public void processResult(int retcode, String path, Object ctx, Stat stat) { + rc.set(retcode); + latch.countDown(); + } + }, null); + Assert.assertTrue("setData should complete within 5s", + latch.await(zk.getSessionTimeout(), TimeUnit.MILLISECONDS)); + Assert.assertEquals("setData should have succeeded", Code.OK.intValue(), rc.get()); + } + zk.delete("/testnode", -1); + Assert.assertTrue("xid should be positive", zk.checkXid() > 0); + } finally { + if (zk != null) { + zk.close(); + } + } + } }