From f0d3bab321e384af13d66d4a3194a34af0633b0e Mon Sep 17 00:00:00 2001 From: Uwe Schindler Date: Wed, 9 Sep 2020 18:42:30 +0200 Subject: [PATCH] LUCENE-9512: Move LockFactory stress test to be a unit/integration test (#1842) Co-authored-by: Dawid Weiss --- .../randomization/policies/tests.policy | 3 + lucene/CHANGES.txt | 4 + .../apache/lucene/store/LockStressTest.java | 49 +++++---- .../apache/lucene/store/LockVerifyServer.java | 44 ++++---- .../lucene/store/VerifyingLockFactory.java | 6 +- .../lucene/store/TestStressLockFactories.java | 104 ++++++++++++++++++ 6 files changed, 164 insertions(+), 46 deletions(-) create mode 100644 lucene/core/src/test/org/apache/lucene/store/TestStressLockFactories.java diff --git a/gradle/testing/randomization/policies/tests.policy b/gradle/testing/randomization/policies/tests.policy index 8dedb3b6fd8..c71841149e6 100644 --- a/gradle/testing/randomization/policies/tests.policy +++ b/gradle/testing/randomization/policies/tests.policy @@ -64,6 +64,9 @@ grant { permission java.lang.RuntimePermission "getClassLoader"; permission java.lang.RuntimePermission "setContextClassLoader"; + // TestLockFactoriesMultiJVM opens a random port on 127.0.0.1 (port 0 = ephemeral port range): + permission java.net.SocketPermission "127.0.0.1:0", "accept,listen,resolve"; + // read access to all system properties: permission java.util.PropertyPermission "*", "read"; // write access to only these: diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 0cac671f026..288316544b9 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -252,6 +252,10 @@ Other * LUCENE-9470: Make TestXYMultiPolygonShapeQueries more resilient for CONTAINS queries. (Ignacio Vera) +* LUCENE-9512: Move LockFactory stress test to be a unit/integration + test. (Uwe Schindler, Dawid Weiss, Robert Muir) + + ======================= Lucene 8.6.2 ======================= Bug Fixes diff --git a/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java b/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java index b319df46eed..38997a5c10d 100644 --- a/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java +++ b/lucene/core/src/java/org/apache/lucene/store/LockStressTest.java @@ -30,7 +30,7 @@ import org.apache.lucene.util.SuppressForbidden; /** * Simple standalone tool that forever acquires and releases a - * lock using a specific LockFactory. Run without any args + * lock using a specific {@link LockFactory}. Run without any args * to see usage. * * @see VerifyingLockFactory @@ -38,11 +38,9 @@ import org.apache.lucene.util.SuppressForbidden; */ public class LockStressTest { - static final String LOCK_FILE_NAME = "test.lock"; - + @SuppressForbidden(reason = "System.out required: command line tool") - @SuppressWarnings("try") public static void main(String[] args) throws Exception { if (args.length != 7) { System.out.println("Usage: java org.apache.lucene.store.LockStressTest myID verifierHost verifierPort lockFactoryClassName lockDirName sleepTimeMS count\n" + @@ -65,12 +63,6 @@ public class LockStressTest { int arg = 0; final int myID = Integer.parseInt(args[arg++]); - - if (myID < 0 || myID > 255) { - System.out.println("myID must be a unique int 0..255"); - System.exit(1); - } - final String verifierHost = args[arg++]; final int verifierPort = Integer.parseInt(args[arg++]); final String lockFactoryClassName = args[arg++]; @@ -78,8 +70,21 @@ public class LockStressTest { final int sleepTimeMS = Integer.parseInt(args[arg++]); final int count = Integer.parseInt(args[arg++]); + int exitCode = run(myID, verifierHost, verifierPort, lockFactoryClassName, lockDirPath, sleepTimeMS, count); + System.exit(exitCode); + } + + @SuppressForbidden(reason = "System.out required: command line tool") + @SuppressWarnings("try") + private static int run(int myID, String verifierHost, int verifierPort, String lockFactoryClassName, + Path lockDirPath, int sleepTimeMS, int count) throws IOException, InterruptedException { + if (myID < 0 || myID > 255) { + System.out.println("myID must be a unique int 0..255"); + return 1; + } + final LockFactory lockFactory = getNewLockFactory(lockFactoryClassName); - // we test the lock factory directly, so we don't need it on the directory itsself (the directory is just for testing) + // we test the lock factory directly, so we don't need it on the directory itself (the directory is just for testing) final FSDirectory lockDir = new NIOFSDirectory(lockDirPath, NoLockFactory.INSTANCE); final InetSocketAddress addr = new InetSocketAddress(verifierHost, verifierPort); System.out.println("Connecting to server " + addr + @@ -89,17 +94,17 @@ public class LockStressTest { socket.connect(addr, 500); final OutputStream out = socket.getOutputStream(); final InputStream in = socket.getInputStream(); - + out.write(myID); out.flush(); LockFactory verifyLF = new VerifyingLockFactory(lockFactory, in, out); final Random rnd = new Random(); - + // wait for starting gun - if (in.read() != 43) { + if (in.read() != LockVerifyServer.START_GUN_SIGNAL) { throw new IOException("Protocol violation"); } - + for (int i = 0; i < count; i++) { try (final Lock l = verifyLF.obtainLock(lockDir, LOCK_FILE_NAME)) { if (rnd.nextInt(10) == 0) { @@ -120,14 +125,14 @@ public class LockStressTest { if (i % 500 == 0) { System.out.println((i * 100. / count) + "% done."); } - - Thread.sleep(sleepTimeMS); - } - } - - System.out.println("Finished " + count + " tries."); - } + Thread.sleep(sleepTimeMS); + } + } + + System.out.println("Finished " + count + " tries."); + return 0; + } private static FSLockFactory getNewLockFactory(String lockFactoryClassName) throws IOException { // try to get static INSTANCE field of class diff --git a/lucene/core/src/java/org/apache/lucene/store/LockVerifyServer.java b/lucene/core/src/java/org/apache/lucene/store/LockVerifyServer.java index 3ce0c06e010..c9d77bbb7d2 100644 --- a/lucene/core/src/java/org/apache/lucene/store/LockVerifyServer.java +++ b/lucene/core/src/java/org/apache/lucene/store/LockVerifyServer.java @@ -24,6 +24,7 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.SuppressForbidden; @@ -38,20 +39,12 @@ import org.apache.lucene.util.SuppressForbidden; * @see LockStressTest */ +@SuppressForbidden(reason = "System.out required: command line tool") public class LockVerifyServer { + public static final int START_GUN_SIGNAL = 43; - @SuppressForbidden(reason = "System.out required: command line tool") - public static void main(String[] args) throws Exception { - - if (args.length != 2) { - System.out.println("Usage: java org.apache.lucene.store.LockVerifyServer bindToIp clients\n"); - System.exit(1); - } - - int arg = 0; - final String hostname = args[arg++]; - final int maxClients = Integer.parseInt(args[arg++]); - + // method pkg-private for tests + static void run(String hostname, int maxClients, Consumer startClients) throws Exception { try (final ServerSocket s = new ServerSocket()) { s.setReuseAddress(true); s.setSoTimeout(30000); // initially 30 secs to give clients enough time to startup @@ -59,8 +52,8 @@ public class LockVerifyServer { final InetSocketAddress localAddr = (InetSocketAddress) s.getLocalSocketAddress(); System.out.println("Listening on " + localAddr + "..."); - // we set the port as a sysprop, so the ANT task can read it. For that to work, this server must run in-process: - System.setProperty("lockverifyserver.port", Integer.toString(localAddr.getPort())); + // callback only for the test to start the clients: + startClients.accept(localAddr); final Object localLock = new Object(); final int[] lockedID = new int[1]; @@ -80,22 +73,22 @@ public class LockVerifyServer { } startingGun.await(); - os.write(43); + os.write(START_GUN_SIGNAL); os.flush(); - while(true) { + while (true) { final int command = in.read(); if (command < 0) { return; // closed } - synchronized(localLock) { + synchronized (localLock) { final int currentLock = lockedID[0]; if (currentLock == -2) { return; // another thread got error, so we exit, too! } switch (command) { - case 1: + case VerifyingLockFactory.MSG_LOCK_ACQUIRED: // Locked if (currentLock != -1) { lockedID[0] = -2; @@ -103,7 +96,7 @@ public class LockVerifyServer { } lockedID[0] = id; break; - case 0: + case VerifyingLockFactory.MSG_LOCK_RELEASED: // Unlocked if (currentLock != id) { lockedID[0] = -2; @@ -138,11 +131,18 @@ public class LockVerifyServer { for (Thread t : threads) { t.join(); } - - // cleanup sysprop - System.clearProperty("lockverifyserver.port"); System.out.println("Server terminated."); } } + + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.out.println("Usage: java org.apache.lucene.store.LockVerifyServer bindToIp clients\n"); + System.exit(1); + } + + run(args[0], Integer.parseInt(args[1]), addr -> {}); + } + } diff --git a/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java b/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java index 6a7214ba378..1b9ce23e950 100644 --- a/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java +++ b/lucene/core/src/java/org/apache/lucene/store/VerifyingLockFactory.java @@ -36,6 +36,8 @@ import java.io.OutputStream; */ public final class VerifyingLockFactory extends LockFactory { + public static final int MSG_LOCK_RELEASED = 0; + public static final int MSG_LOCK_ACQUIRED = 1; final LockFactory lf; final InputStream in; @@ -46,7 +48,7 @@ public final class VerifyingLockFactory extends LockFactory { public CheckedLock(Lock lock) throws IOException { this.lock = lock; - verify((byte) 1); + verify((byte) MSG_LOCK_ACQUIRED); } @Override @@ -58,7 +60,7 @@ public final class VerifyingLockFactory extends LockFactory { public void close() throws IOException { try (Lock l = lock) { l.ensureValid(); - verify((byte) 0); + verify((byte) MSG_LOCK_RELEASED); } } diff --git a/lucene/core/src/test/org/apache/lucene/store/TestStressLockFactories.java b/lucene/core/src/test/org/apache/lucene/store/TestStressLockFactories.java new file mode 100644 index 00000000000..a55aa2d9309 --- /dev/null +++ b/lucene/core/src/test/org/apache/lucene/store/TestStressLockFactories.java @@ -0,0 +1,104 @@ +/* + * 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.lucene.store; + +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.lucene.util.LuceneTestCase; +import org.apache.lucene.util.SuppressForbidden; + +@LuceneTestCase.SuppressFileSystems("*") +public class TestStressLockFactories extends LuceneTestCase { + + @SuppressForbidden(reason = "ProcessBuilder only allows to redirect to java.io.File") + private static final ProcessBuilder applyRedirection(ProcessBuilder pb, int client, Path dir) { + if (VERBOSE) { + return pb.inheritIO(); + } else { + return pb + .redirectError(dir.resolve("err-" + client + ".txt").toFile()) + .redirectOutput(dir.resolve("out-" + client + ".txt").toFile()) + .redirectInput(Redirect.INHERIT); + } + } + + private void runImpl(Class impl) throws Exception { + final int clients = TEST_NIGHTLY ? 5 : 2; + final String host = "127.0.0.1"; + final int delay = 1; + final int rounds = (TEST_NIGHTLY ? 30000 : 500) * RANDOM_MULTIPLIER; + + final Path dir = createTempDir(impl.getSimpleName()); + + final List processes = new ArrayList<>(clients); + + LockVerifyServer.run(host, clients, addr -> { + // spawn clients as separate Java processes + for (int i = 0; i < clients; i++) { + try { + processes.add(applyRedirection(new ProcessBuilder( + Paths.get(System.getProperty("java.home"), "bin", "java").toString(), + "-Xmx32M", + "-cp", + System.getProperty("java.class.path"), + LockStressTest.class.getName(), + Integer.toString(i), + addr.getHostString(), + Integer.toString(addr.getPort()), + impl.getName(), + dir.toString(), + Integer.toString(delay), + Integer.toString(rounds) + ), i, dir).start()); + } catch (IOException ioe) { + throw new AssertionError("Failed to start child process.", ioe); + } + } + }); + + // wait for all processes to exit... + try { + for (Process p : processes) { + if (p.waitFor(15, TimeUnit.SECONDS)) { + assertEquals("Process " + p.pid() + " died abnormally?", 0, p.waitFor()); + } + } + } finally { + // kill all processes, which are still alive. + for (Process p : processes) { + if (p.isAlive()) { + p.destroyForcibly().waitFor(); + } + } + } + } + + public void testNativeFSLockFactory() throws Exception { + runImpl(NativeFSLockFactory.class); + } + + public void testSimpleFSLockFactory() throws Exception { + runImpl(SimpleFSLockFactory.class); + } + +}