Line 0
Link Here
|
|
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 2015 Oracle and/or its affiliates. All rights reserved. |
5 |
* |
6 |
* Oracle and Java are registered trademarks of Oracle and/or its affiliates. |
7 |
* Other names may be trademarks of their respective owners. |
8 |
* |
9 |
* The contents of this file are subject to the terms of either the GNU |
10 |
* General Public License Version 2 only ("GPL") or the Common |
11 |
* Development and Distribution License("CDDL") (collectively, the |
12 |
* "License"). You may not use this file except in compliance with the |
13 |
* License. You can obtain a copy of the License at |
14 |
* http://www.netbeans.org/cddl-gplv2.html |
15 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
16 |
* specific language governing permissions and limitations under the |
17 |
* License. When distributing the software, include this License Header |
18 |
* Notice in each file and include the License file at |
19 |
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this |
20 |
* particular file as subject to the "Classpath" exception as provided |
21 |
* by Oracle in the GPL Version 2 section of the License file that |
22 |
* accompanied this code. If applicable, add the following below the |
23 |
* License Header, with the fields enclosed by brackets [] replaced by |
24 |
* your own identifying information: |
25 |
* "Portions Copyrighted [year] [name of copyright owner]" |
26 |
* |
27 |
* If you wish your version of this file to be governed by only the CDDL |
28 |
* or only the GPL Version 2, indicate your decision by adding |
29 |
* "[Contributor] elects to include this software in this distribution |
30 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
31 |
* single choice of license, a recipient has the option to distribute |
32 |
* your version of this file under either the CDDL, the GPL Version 2 or |
33 |
* to extend the choice of license to its licensees as provided above. |
34 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
35 |
* Version 2 license, then the option applies only if the new code is |
36 |
* made subject to such option by the copyright holder. |
37 |
* |
38 |
* Contributor(s): |
39 |
* |
40 |
* Portions Copyrighted 2015 Sun Microsystems, Inc. |
41 |
*/ |
42 |
package org.openide.util.lookup.implspi; |
43 |
|
44 |
import java.lang.ref.Reference; |
45 |
import java.lang.ref.ReferenceQueue; |
46 |
import java.lang.ref.WeakReference; |
47 |
import java.util.LinkedHashMap; |
48 |
import java.util.Map; |
49 |
import java.util.concurrent.atomic.AtomicBoolean; |
50 |
import java.util.logging.Handler; |
51 |
import java.util.logging.Level; |
52 |
import java.util.logging.LogRecord; |
53 |
import java.util.logging.Logger; |
54 |
import org.netbeans.junit.Log; |
55 |
import org.netbeans.junit.NbTestCase; |
56 |
import org.openide.util.lookup.implspi.CallbackReferencesSupport.CallbackHandler; |
57 |
|
58 |
/** |
59 |
* |
60 |
* @author martin |
61 |
*/ |
62 |
public class CallbackReferencesSupportTest extends NbTestCase { |
63 |
|
64 |
private LoggerWarningsHandler logHandler; |
65 |
|
66 |
public CallbackReferencesSupportTest(String testName) { |
67 |
super(testName); |
68 |
} |
69 |
|
70 |
@Override |
71 |
protected void setUp() throws Exception { |
72 |
Logger log = Logger.getLogger(CallbackReferencesSupport.class.getName()); |
73 |
Log.enable(log.getName(), Level.FINEST); |
74 |
logHandler = new LoggerWarningsHandler(); |
75 |
log.addHandler(logHandler); |
76 |
} |
77 |
|
78 |
@Override |
79 |
protected void tearDown() throws Exception { |
80 |
assertFalse(logHandler.toString(), logHandler.hasMessages()); |
81 |
Logger log = Logger.getLogger(CallbackReferencesSupport.class.getName()); |
82 |
log.removeHandler(logHandler); |
83 |
logHandler = null; |
84 |
} |
85 |
|
86 |
public void testCleanupCallback() throws Exception { |
87 |
final boolean[] cleared = new boolean[] { false }; |
88 |
final boolean[] finalized = new boolean[] { false }; |
89 |
SupportCallback callback = new SupportCallback() { |
90 |
@Override |
91 |
public void referenceCleared(Reference ref) { |
92 |
synchronized (cleared) { |
93 |
cleared[0] = true; |
94 |
cleared.notifyAll(); |
95 |
} |
96 |
} |
97 |
}; |
98 |
|
99 |
// Hold the reference and free the referent: |
100 |
System.err.println("Hold the reference and free the referent"); |
101 |
Object obj = new Object(); |
102 |
TestWeakWithFinalize twf = new TestWeakWithFinalize(obj, callback, finalized); |
103 |
CallbackReferencesSupportFinishable crsf = (CallbackReferencesSupportFinishable) CallbackReferencesSupport.getDefault(); |
104 |
boolean active = crsf.isDaemonRunning(); |
105 |
assertTrue("The daemon started", active); |
106 |
WeakReference objRef = new WeakReference(obj); |
107 |
obj = null; |
108 |
assertGC("Object should be GC'ed", objRef); |
109 |
synchronized (cleared) { |
110 |
while (!cleared[0]) { |
111 |
cleared.wait(100); |
112 |
System.gc(); |
113 |
} |
114 |
} |
115 |
assertTrue(cleared[0]); |
116 |
assertFalse(finalized[0]); |
117 |
active = crsf.isDaemonRunning(); |
118 |
assertFalse("The daemon should finish", active); |
119 |
WeakReference twRef = new WeakReference(twf); |
120 |
twf = null; |
121 |
assertGC("TestWeak reference should be GC'ed", twRef); |
122 |
synchronized (finalized) { |
123 |
while (!finalized[0]) { |
124 |
finalized.wait(100); |
125 |
System.gc(); |
126 |
} |
127 |
} |
128 |
assertTrue(finalized[0]); |
129 |
// Reset: |
130 |
cleared[0] = false; |
131 |
finalized[0] = false; |
132 |
|
133 |
// Free the reference and hold the referent |
134 |
System.err.println("Free the reference and hold the referent"); |
135 |
obj = new Object(); |
136 |
twf = new TestWeakWithFinalize(obj, callback, finalized); |
137 |
active = crsf.isDaemonRunning(); |
138 |
assertTrue("The daemon started", active); |
139 |
twRef = new WeakReference(twf); |
140 |
twf = null; |
141 |
assertGC("TestWeak reference should be GC'ed", twRef); |
142 |
synchronized (finalized) { |
143 |
while (!finalized[0]) { |
144 |
finalized.wait(100); |
145 |
System.gc(); |
146 |
} |
147 |
} |
148 |
assertTrue(finalized[0]); |
149 |
assertFalse(cleared[0]); // Should never clear |
150 |
objRef = new WeakReference(obj); |
151 |
obj = null; |
152 |
assertGC("Object should be GC'ed", objRef); |
153 |
assertFalse(cleared[0]); // Should never clear |
154 |
while (crsf.isDaemonRunning()) { |
155 |
System.gc(); |
156 |
Thread.sleep(100); |
157 |
} |
158 |
active = crsf.isDaemonRunning(); |
159 |
assertFalse("The daemon should finish", active); |
160 |
// Reset: |
161 |
cleared[0] = false; |
162 |
finalized[0] = false; |
163 |
|
164 |
// Free both the reference and the referent |
165 |
System.err.println("Free both the reference and the referent"); |
166 |
obj = new Object(); |
167 |
TestWeak tw = new TestWeak(obj, callback); |
168 |
active = crsf.isDaemonRunning(); |
169 |
assertTrue("The daemon started", active); |
170 |
objRef = new WeakReference(obj); |
171 |
obj = null; |
172 |
twRef = new WeakReference(tw); |
173 |
tw = null; |
174 |
assertGC("Object should be GC'ed", objRef); |
175 |
assertGC("TestWeak reference should be GC'ed", twRef); |
176 |
|
177 |
while (crsf.isDaemonRunning()) { |
178 |
System.gc(); |
179 |
Thread.sleep(100); |
180 |
} |
181 |
|
182 |
// Free several referents and hold the references |
183 |
System.err.println("Free several referents and hold the references"); |
184 |
int n = 500000; |
185 |
Object[] objs = new Object[n]; |
186 |
TestWeak[] tws = new TestWeak[n]; |
187 |
final boolean[] calledBack = new boolean[n]; |
188 |
SupportCallback cb = new SupportCallback() { |
189 |
@Override public void referenceCleared(Reference ref) { |
190 |
calledBack[((TestWeak) ref).id] = true; |
191 |
} |
192 |
}; |
193 |
for (int i = 0; i < n; i++) { |
194 |
objs[i] = new Object(); |
195 |
tws[i] = new TestWeak(objs[i], cb); |
196 |
tws[i].id = i; |
197 |
} |
198 |
active = crsf.isDaemonRunning(); |
199 |
assertTrue("The daemon started", active); |
200 |
WeakReference[] objRefs = new WeakReference[n]; |
201 |
WeakReference[] twRefs = new WeakReference[n]; |
202 |
for (int i = 0; i < n; i++) { |
203 |
objRefs[i] = new WeakReference(objs[i]); |
204 |
twRefs[i] = new WeakReference(tws[i]); |
205 |
} |
206 |
objs = null; |
207 |
for (int i = 0; i < n; i++) { |
208 |
assertGC("Object "+i+" should be GC'ed", objRefs[i]); |
209 |
assertEquals(tws[i], twRefs[i].get()); |
210 |
} |
211 |
|
212 |
while (crsf.isDaemonRunning()) { |
213 |
System.gc(); |
214 |
Thread.sleep(100); |
215 |
} |
216 |
String ncb = null; |
217 |
for (int i = 0; i < n; i++) { |
218 |
if (!calledBack[i]) { |
219 |
if (ncb == null) { |
220 |
ncb = Integer.toString(i); |
221 |
} else { |
222 |
ncb += ", "+i; |
223 |
} |
224 |
} |
225 |
} |
226 |
assertNull("Not called back: "+ncb, ncb); |
227 |
tws = null; |
228 |
for (int i = 0; i < n; i++) { |
229 |
assertGC("TestWeak reference "+i+" should be GC'ed", twRefs[i]); |
230 |
} |
231 |
|
232 |
// Free several references and the referents |
233 |
System.err.println("Free several references and the referents"); |
234 |
objs = new Object[n]; |
235 |
tws = new TestWeak[n]; |
236 |
for (int i = 0; i < n; i++) { |
237 |
objs[i] = new Object(); |
238 |
tws[i] = new TestWeak(objs[i], cb); |
239 |
tws[i].id = i; |
240 |
} |
241 |
active = crsf.isDaemonRunning(); |
242 |
assertTrue("The daemon started", active); |
243 |
objRefs = new WeakReference[n]; |
244 |
twRefs = new WeakReference[n]; |
245 |
for (int i = 0; i < n; i++) { |
246 |
objRefs[i] = new WeakReference(objs[i]); |
247 |
twRefs[i] = new WeakReference(tws[i]); |
248 |
} |
249 |
objs = null; |
250 |
tws = null; |
251 |
for (int i = 0; i < n; i++) { |
252 |
assertGC("Object "+i+" should be GC'ed", objRefs[i]); |
253 |
assertGC("TestWeak reference "+i+" should be GC'ed", twRefs[i]); |
254 |
} |
255 |
|
256 |
while (crsf.isDaemonRunning()) { |
257 |
System.gc(); |
258 |
Thread.sleep(100); |
259 |
} |
260 |
} |
261 |
|
262 |
public void testNullRefs() throws Exception { |
263 |
final boolean[] cleared = new boolean[] { false }; |
264 |
final boolean[] finalized = new boolean[] { false }; |
265 |
/*CallbackReferencesSupport support = CallbackReferencesSupport.create(new SupportCallback() { |
266 |
@Override |
267 |
public void referenceCleared(Reference ref) { |
268 |
cleared[0] = true; |
269 |
} |
270 |
});*/ |
271 |
CallbackReferencesSupport support = CallbackReferencesSupport.getDefault(); |
272 |
new WeakReferenceOnNull(support, finalized); |
273 |
CallbackReferencesSupportFinishable crsf = (CallbackReferencesSupportFinishable) CallbackReferencesSupport.getDefault(); |
274 |
boolean active = crsf.isDaemonRunning(); |
275 |
// is probably active, unless the WeakReferenceOnNull was already finalized. |
276 |
synchronized (finalized) { |
277 |
while (!finalized[0]) { |
278 |
finalized.wait(100); |
279 |
System.gc(); |
280 |
} |
281 |
} |
282 |
assertTrue("The weak ref was not finalized?!?", finalized[0]); |
283 |
//WeakReference supportRef = new WeakReference(support); |
284 |
//support = null; |
285 |
//assertGC("The support should be GC'ed", supportRef); |
286 |
// Since the ref was null, the clearing callback was not called: |
287 |
assertFalse("Unexpectedly cleared!", cleared[0]); |
288 |
while (crsf.isDaemonRunning()) { |
289 |
System.gc(); |
290 |
Thread.sleep(100); |
291 |
} |
292 |
active = crsf.isDaemonRunning(); |
293 |
assertFalse("The daemon shoud die!", active); |
294 |
/* |
295 |
boolean isNPE = false; |
296 |
try { |
297 |
new TestWeak(new Object(), (SupportCallback) null, null); |
298 |
} catch (NullPointerException npex) { |
299 |
isNPE = true; |
300 |
} |
301 |
assertTrue(isNPE); |
302 |
*/ |
303 |
active = crsf.isDaemonRunning(); |
304 |
assertFalse("NPE should not start the daemon", active); |
305 |
} |
306 |
|
307 |
private static class WeakReferenceOnNull extends WeakReference { |
308 |
|
309 |
private final CallbackReferencesSupport support; |
310 |
private final boolean[] finalized; |
311 |
|
312 |
public WeakReferenceOnNull(CallbackReferencesSupport support, boolean[] finalized) { |
313 |
super(null, support.getReferenceQueue()); |
314 |
this.support = support; |
315 |
support.registerReferenceFinalizer(this); |
316 |
this.finalized = finalized; |
317 |
} |
318 |
|
319 |
@Override |
320 |
protected void finalize() throws Throwable { |
321 |
synchronized (finalized) { |
322 |
finalized[0] = true; |
323 |
finalized.notifyAll(); |
324 |
} |
325 |
super.finalize(); |
326 |
//support.notifyFinalized(); |
327 |
} |
328 |
} |
329 |
|
330 |
static class TestWeak<T> extends WeakReference<T> { |
331 |
|
332 |
private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); |
333 |
|
334 |
final SupportCallback cleanup; |
335 |
int id; |
336 |
|
337 |
public TestWeak(T referent, SupportCallback cleanup) { |
338 |
this(referent, cleanup, CallbackReferencesSupport.getDefault()); |
339 |
} |
340 |
|
341 |
private TestWeak(T referent, SupportCallback cleanup, CallbackReferencesSupport support) { |
342 |
super(referent, support.getReferenceQueue()); |
343 |
this.cleanup = cleanup; |
344 |
support.registerReferenceFinalizer(this); |
345 |
if (!REGISTERED.getAndSet(true)) { |
346 |
CallbackReferencesSupport.registerCallbackHandler(TestWeak.class, new TestCallbackHandler()); |
347 |
} |
348 |
} |
349 |
|
350 |
private static final class TestCallbackHandler implements CallbackHandler { |
351 |
|
352 |
@Override |
353 |
public void handleCallback(Reference ref) { |
354 |
((TestWeak) ref).cleanup.referenceCleared(ref); |
355 |
} |
356 |
|
357 |
} |
358 |
|
359 |
} |
360 |
|
361 |
static class TestWeakWithFinalize<T> extends WeakReference<T> { |
362 |
|
363 |
private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); |
364 |
|
365 |
final SupportCallback cleanup; |
366 |
private final boolean[] finalized; |
367 |
int id; |
368 |
|
369 |
public TestWeakWithFinalize(T referent, SupportCallback cleanup, boolean[] finalized) { |
370 |
this(referent, cleanup, CallbackReferencesSupport.getDefault(), finalized); |
371 |
} |
372 |
|
373 |
private TestWeakWithFinalize(T referent, SupportCallback cleanup, CallbackReferencesSupport support, boolean[] finalized) { |
374 |
super(referent, support.getReferenceQueue()); |
375 |
this.cleanup = cleanup; |
376 |
support.registerReferenceFinalizer(this); |
377 |
this.finalized = finalized; |
378 |
if (!REGISTERED.getAndSet(true)) { |
379 |
CallbackReferencesSupport.registerCallbackHandler(TestWeakWithFinalize.class, new TestCallbackHandler()); |
380 |
} |
381 |
} |
382 |
|
383 |
@Override |
384 |
protected final void finalize() throws Throwable { |
385 |
//System.err.println("TestWeak finzalizing..."); |
386 |
//support.notifyFinalized(); |
387 |
super.finalize(); |
388 |
if (finalized != null) { |
389 |
synchronized (finalized) { |
390 |
finalized[0] = true; |
391 |
finalized.notifyAll(); |
392 |
} |
393 |
} |
394 |
} |
395 |
|
396 |
private static final class TestCallbackHandler implements CallbackHandler { |
397 |
|
398 |
@Override |
399 |
public void handleCallback(Reference ref) { |
400 |
((TestWeakWithFinalize) ref).cleanup.referenceCleared(ref); |
401 |
} |
402 |
|
403 |
} |
404 |
|
405 |
} |
406 |
|
407 |
static interface SupportCallback { |
408 |
void referenceCleared(Reference ref); |
409 |
} |
410 |
|
411 |
private static class LoggerWarningsHandler extends Handler { |
412 |
|
413 |
private final Map<String, Level> messages = new LinkedHashMap<String, Level>(); |
414 |
|
415 |
public LoggerWarningsHandler() { |
416 |
} |
417 |
|
418 |
@Override |
419 |
public void publish(LogRecord record) { |
420 |
Level f = record.getLevel(); |
421 |
if (f.intValue() >= Level.CONFIG.intValue()) { |
422 |
messages.put(record.getMessage(), f); |
423 |
} |
424 |
} |
425 |
|
426 |
@Override |
427 |
public void flush() { |
428 |
} |
429 |
|
430 |
@Override |
431 |
public void close() throws SecurityException { |
432 |
} |
433 |
|
434 |
public boolean hasMessages() { |
435 |
return !messages.isEmpty(); |
436 |
} |
437 |
|
438 |
@Override |
439 |
public String toString() { |
440 |
return "LoggerWarningsHandler: "+messages.toString(); |
441 |
} |
442 |
|
443 |
} |
444 |
|
445 |
} |