1 /* 2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 package java.lang.reflect; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.Objects; 29 import java.util.concurrent.ExecutionException; 30 import java.util.concurrent.ExecutorService; 31 import java.util.concurrent.Executors; 32 import java.util.concurrent.Future; 33 import java.util.concurrent.TimeUnit; 34 import java.util.concurrent.atomic.AtomicBoolean; 35 36 /** 37 * Functional and concurrency test for ClassLoaderValue 38 * 39 * @author Peter Levart 40 */ 41 public class ClassLoaderValueTest { 42 43 @SuppressWarnings("unchecked") 44 public static void main(String[] args) throws Exception { 45 46 ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), 47 new ClassLoaderValue<>()}; 48 49 ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), 50 ClassLoader.getPlatformClassLoader(), 51 null /* bootstrap class loader */}; 52 53 Integer[] keys = new Integer[32]; 54 for (int i = 0; i < keys.length; i++) { 55 keys[i] = i + 128; 56 } 57 58 try (AutoCloseable cleanup = () -> { 59 for (ClassLoaderValue<Integer> clv : clvs) { 60 for (ClassLoader ld : lds) { 61 clv.removeAll(ld); 62 } 63 } 64 }) { 65 // 1st just one sequential pass of single-threaded validation 66 // which is easier to debug if it fails... 67 for (ClassLoaderValue<Integer> clv : clvs) { 68 for (ClassLoader ld : lds) { 69 writeValidateOps(clv, ld, keys); 70 } 71 } 72 for (ClassLoaderValue<Integer> clv : clvs) { 73 for (ClassLoader ld : lds) { 74 readValidateOps(clv, ld, keys); 75 } 76 } 77 78 // 2nd the same in concurrent setting that also validates 79 // failure-isolation between threads and data-isolation between 80 // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage 81 testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); 82 } 83 } 84 85 static void writeValidateOps(ClassLoaderValue<Integer> clv, 86 ClassLoader ld, 87 Object[] keys) { 88 for (int i = 0; i < keys.length; i++) { 89 Object k = keys[i]; 90 Integer v1 = i; 91 Integer v2 = i + 333; 92 Integer pv; 93 boolean success; 94 95 pv = clv.sub(k).putIfAbsent(ld, v1); 96 assertEquals(pv, null); 97 assertEquals(clv.sub(k).get(ld), v1); 98 99 pv = clv.sub(k).putIfAbsent(ld, v2); 100 assertEquals(pv, v1); 101 assertEquals(clv.sub(k).get(ld), v1); 102 103 success = clv.sub(k).remove(ld, v2); 104 assertEquals(success, false); 105 assertEquals(clv.sub(k).get(ld), v1); 106 107 success = clv.sub(k).remove(ld, v1); 108 assertEquals(success, true); 109 assertEquals(clv.sub(k).get(ld), null); 110 111 pv = clv.sub(k).putIfAbsent(ld, v2); 112 assertEquals(pv, null); 113 assertEquals(clv.sub(k).get(ld), v2); 114 115 pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); 116 assertEquals(pv, v2); 117 assertEquals(clv.sub(k).get(ld), v2); 118 119 success = clv.sub(k).remove(ld, v1); 120 assertEquals(success, false); 121 assertEquals(clv.sub(k).get(ld), v2); 122 123 success = clv.sub(k).remove(ld, v2); 124 assertEquals(success, true); 125 assertEquals(clv.sub(k).get(ld), null); 126 127 pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { 128 try { 129 // nested get for same key should throw 130 clv_k.get(_ld); 131 throw new AssertionError("Unexpected code path"); 132 } catch (IllegalStateException e) { 133 // expected 134 } 135 try { 136 // nested putIfAbsent for same key should throw 137 clv_k.putIfAbsent(_ld, v1); 138 throw new AssertionError("Unexpected code path"); 139 } catch (IllegalStateException e) { 140 // expected 141 } 142 // nested remove for for same key and any value (even null) 143 // should return false 144 assertEquals(clv_k.remove(_ld, null), false); 145 assertEquals(clv_k.remove(_ld, v1), false); 146 assertEquals(clv_k.remove(_ld, v2), false); 147 try { 148 // nested computeIfAbsent for same key should throw 149 clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); 150 throw new AssertionError("Unexpected code path"); 151 } catch (IllegalStateException e) { 152 // expected 153 } 154 // if everything above has been handled, we should succeed... 155 return v2; 156 }); 157 // ... and the result should be reflected in the CLV 158 assertEquals(pv, v2); 159 assertEquals(clv.sub(k).get(ld), v2); 160 161 success = clv.sub(k).remove(ld, v2); 162 assertEquals(success, true); 163 assertEquals(clv.sub(k).get(ld), null); 164 165 try { 166 clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { 167 throw new UnsupportedOperationException(); 168 }); 169 throw new AssertionError("Unexpected code path"); 170 } catch (UnsupportedOperationException e) { 171 // expected 172 } 173 assertEquals(clv.sub(k).get(ld), null); 174 } 175 } 176 177 static void readValidateOps(ClassLoaderValue<Integer> clv, 178 ClassLoader ld, 179 Object[] keys) { 180 for (int i = 0; i < keys.length; i++) { 181 Object k = keys[i]; 182 Integer v1 = i; 183 Integer v2 = i + 333; 184 Integer rv = clv.sub(k).get(ld); 185 if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { 186 throw new AssertionError("Unexpected value: " + rv + 187 ", expected one of: null, " + v1 + ", " + v2); 188 } 189 } 190 } 191 192 static void testConcurrentIsolation(ClassLoaderValue<Integer>[] clvs, 193 ClassLoader[] lds, 194 Object[] keys, 195 long millisRuntime) { 196 ExecutorService exe = Executors.newCachedThreadPool(); 197 List<Future<?>> futures = new ArrayList<>(); 198 AtomicBoolean stop = new AtomicBoolean(); 199 for (ClassLoaderValue<Integer> clv : clvs) { 200 for (ClassLoader ld : lds) { 201 // submit a task that exercises a mix of modifying 202 // and reading-validating operations in an isolated 203 // part of the storage. If isolation is violated, 204 // validation operations are expected to fail. 205 futures.add(exe.submit(() -> { 206 do { 207 writeValidateOps(clv, ld, keys); 208 } while (!stop.get()); 209 })); 210 // submit a task that just reads from the same part of 211 // the storage as above task. It should not disturb 212 // above task in any way and this task should never 213 // exhibit any failure although above task produces 214 // regular failures during lazy computation 215 futures.add(exe.submit(() -> { 216 do { 217 readValidateOps(clv, ld, keys); 218 } while (!stop.get()); 219 })); 220 } 221 } 222 // wait for some time 223 try { 224 Thread.sleep(millisRuntime); 225 } catch (InterruptedException e) { 226 throw new AssertionError(e); 227 } 228 // stop tasks 229 stop.set(true); 230 // collect results 231 AssertionError error = null; 232 for (Future<?> future : futures) { 233 try { 234 future.get(); 235 } catch (InterruptedException | ExecutionException e) { 236 if (error == null) error = new AssertionError("Failure"); 237 error.addSuppressed(e); 238 } 239 } 240 exe.shutdown(); 241 if (error != null) throw error; 242 } 243 244 static void assertEquals(Object actual, Object expected) { 245 if (!Objects.equals(actual, expected)) { 246 throw new AssertionError("Expected: " + expected + ", actual: " + actual); 247 } 248 } 249 }