| /* |
| * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import jdk.internal.loader.ClassLoaderValue; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| /** |
| * @test |
| * @bug 8152115 |
| * @summary functional and concurrency test for ClassLoaderValue |
| * @modules java.base/jdk.internal.loader |
| * @author Peter Levart |
| */ |
| public class ClassLoaderValueTest { |
| |
| @SuppressWarnings("unchecked") |
| public static void main(String[] args) throws Exception { |
| |
| ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), |
| new ClassLoaderValue<>()}; |
| |
| ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), |
| ClassLoader.getPlatformClassLoader(), |
| null /* bootstrap class loader */}; |
| |
| Integer[] keys = new Integer[32]; |
| for (int i = 0; i < keys.length; i++) { |
| keys[i] = i + 128; |
| } |
| |
| try (AutoCloseable cleanup = () -> { |
| for (ClassLoaderValue<Integer> clv : clvs) { |
| for (ClassLoader ld : lds) { |
| clv.removeAll(ld); |
| } |
| } |
| }) { |
| // 1st just one sequential pass of single-threaded validation |
| // which is easier to debug if it fails... |
| for (ClassLoaderValue<Integer> clv : clvs) { |
| for (ClassLoader ld : lds) { |
| writeValidateOps(clv, ld, keys); |
| } |
| } |
| for (ClassLoaderValue<Integer> clv : clvs) { |
| for (ClassLoader ld : lds) { |
| readValidateOps(clv, ld, keys); |
| } |
| } |
| |
| // 2nd the same in concurrent setting that also validates |
| // failure-isolation between threads and data-isolation between |
| // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage |
| testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); |
| } |
| } |
| |
| static void writeValidateOps(ClassLoaderValue<Integer> clv, |
| ClassLoader ld, |
| Object[] keys) { |
| for (int i = 0; i < keys.length; i++) { |
| Object k = keys[i]; |
| Integer v1 = i; |
| Integer v2 = i + 333; |
| Integer pv; |
| boolean success; |
| |
| pv = clv.sub(k).putIfAbsent(ld, v1); |
| assertEquals(pv, null); |
| assertEquals(clv.sub(k).get(ld), v1); |
| |
| pv = clv.sub(k).putIfAbsent(ld, v2); |
| assertEquals(pv, v1); |
| assertEquals(clv.sub(k).get(ld), v1); |
| |
| success = clv.sub(k).remove(ld, v2); |
| assertEquals(success, false); |
| assertEquals(clv.sub(k).get(ld), v1); |
| |
| success = clv.sub(k).remove(ld, v1); |
| assertEquals(success, true); |
| assertEquals(clv.sub(k).get(ld), null); |
| |
| pv = clv.sub(k).putIfAbsent(ld, v2); |
| assertEquals(pv, null); |
| assertEquals(clv.sub(k).get(ld), v2); |
| |
| pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); |
| assertEquals(pv, v2); |
| assertEquals(clv.sub(k).get(ld), v2); |
| |
| success = clv.sub(k).remove(ld, v1); |
| assertEquals(success, false); |
| assertEquals(clv.sub(k).get(ld), v2); |
| |
| success = clv.sub(k).remove(ld, v2); |
| assertEquals(success, true); |
| assertEquals(clv.sub(k).get(ld), null); |
| |
| pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { |
| try { |
| // nested get for same key should throw |
| clv_k.get(_ld); |
| throw new AssertionError("Unexpected code path"); |
| } catch (IllegalStateException e) { |
| // expected |
| } |
| try { |
| // nested putIfAbsent for same key should throw |
| clv_k.putIfAbsent(_ld, v1); |
| throw new AssertionError("Unexpected code path"); |
| } catch (IllegalStateException e) { |
| // expected |
| } |
| // nested remove for for same key and any value (even null) |
| // should return false |
| assertEquals(clv_k.remove(_ld, null), false); |
| assertEquals(clv_k.remove(_ld, v1), false); |
| assertEquals(clv_k.remove(_ld, v2), false); |
| try { |
| // nested computeIfAbsent for same key should throw |
| clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); |
| throw new AssertionError("Unexpected code path"); |
| } catch (IllegalStateException e) { |
| // expected |
| } |
| // if everything above has been handled, we should succeed... |
| return v2; |
| }); |
| // ... and the result should be reflected in the CLV |
| assertEquals(pv, v2); |
| assertEquals(clv.sub(k).get(ld), v2); |
| |
| success = clv.sub(k).remove(ld, v2); |
| assertEquals(success, true); |
| assertEquals(clv.sub(k).get(ld), null); |
| |
| try { |
| clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { |
| throw new UnsupportedOperationException(); |
| }); |
| throw new AssertionError("Unexpected code path"); |
| } catch (UnsupportedOperationException e) { |
| // expected |
| } |
| assertEquals(clv.sub(k).get(ld), null); |
| } |
| } |
| |
| static void readValidateOps(ClassLoaderValue<Integer> clv, |
| ClassLoader ld, |
| Object[] keys) { |
| for (int i = 0; i < keys.length; i++) { |
| Object k = keys[i]; |
| Integer v1 = i; |
| Integer v2 = i + 333; |
| Integer rv = clv.sub(k).get(ld); |
| if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { |
| throw new AssertionError("Unexpected value: " + rv + |
| ", expected one of: null, " + v1 + ", " + v2); |
| } |
| } |
| } |
| |
| static void testConcurrentIsolation(ClassLoaderValue<Integer>[] clvs, |
| ClassLoader[] lds, |
| Object[] keys, |
| long millisRuntime) { |
| ExecutorService exe = Executors.newCachedThreadPool(); |
| List<Future<?>> futures = new ArrayList<>(); |
| AtomicBoolean stop = new AtomicBoolean(); |
| for (ClassLoaderValue<Integer> clv : clvs) { |
| for (ClassLoader ld : lds) { |
| // submit a task that exercises a mix of modifying |
| // and reading-validating operations in an isolated |
| // part of the storage. If isolation is violated, |
| // validation operations are expected to fail. |
| futures.add(exe.submit(() -> { |
| do { |
| writeValidateOps(clv, ld, keys); |
| } while (!stop.get()); |
| })); |
| // submit a task that just reads from the same part of |
| // the storage as above task. It should not disturb |
| // above task in any way and this task should never |
| // exhibit any failure although above task produces |
| // regular failures during lazy computation |
| futures.add(exe.submit(() -> { |
| do { |
| readValidateOps(clv, ld, keys); |
| } while (!stop.get()); |
| })); |
| } |
| } |
| // wait for some time |
| try { |
| Thread.sleep(millisRuntime); |
| } catch (InterruptedException e) { |
| throw new AssertionError(e); |
| } |
| // stop tasks |
| stop.set(true); |
| // collect results |
| AssertionError error = null; |
| for (Future<?> future : futures) { |
| try { |
| future.get(); |
| } catch (InterruptedException | ExecutionException e) { |
| if (error == null) error = new AssertionError("Failure"); |
| error.addSuppressed(e); |
| } |
| } |
| exe.shutdown(); |
| if (error != null) throw error; |
| } |
| |
| static void assertEquals(Object actual, Object expected) { |
| if (!Objects.equals(actual, expected)) { |
| throw new AssertionError("Expected: " + expected + ", actual: " + actual); |
| } |
| } |
| } |