|
27 | 27 | import com.google.common.annotations.VisibleForTesting;
|
28 | 28 | import com.google.common.util.concurrent.AbstractFuture.Listener;
|
29 | 29 | import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
|
| 30 | +import com.google.j2objc.annotations.J2ObjCIncompatible; |
30 | 31 | import com.google.j2objc.annotations.ReflectionSupport;
|
31 | 32 | import com.google.j2objc.annotations.RetainedLocalRef;
|
| 33 | +import java.lang.invoke.MethodHandles; |
| 34 | +import java.lang.invoke.VarHandle; |
32 | 35 | import java.lang.reflect.Field;
|
33 | 36 | import java.security.PrivilegedActionException;
|
34 | 37 | import java.security.PrivilegedExceptionAction;
|
@@ -345,21 +348,24 @@ void unpark() {
|
345 | 348 | Throwable thrownUnsafeFailure = null;
|
346 | 349 | Throwable thrownAtomicReferenceFieldUpdaterFailure = null;
|
347 | 350 |
|
348 |
| - try { |
349 |
| - helper = new UnsafeAtomicHelper(); |
350 |
| - } catch (Exception | Error unsafeFailure) { // sneaky checked exception |
351 |
| - thrownUnsafeFailure = unsafeFailure; |
352 |
| - // Catch absolutely everything and fall through to AtomicReferenceFieldUpdaterAtomicHelper. |
| 351 | + helper = VarHandleAtomicHelperMaker.INSTANCE.tryMakeVarHandleAtomicHelper(); |
| 352 | + if (helper == null) { |
353 | 353 | try {
|
354 |
| - helper = new AtomicReferenceFieldUpdaterAtomicHelper(); |
355 |
| - } catch (Exception // sneaky checked exception |
356 |
| - | Error atomicReferenceFieldUpdaterFailure) { |
357 |
| - // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause |
358 |
| - // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. |
359 |
| - // For these users fallback to a suboptimal implementation, based on synchronized. This |
360 |
| - // will be a definite performance hit to those users. |
361 |
| - thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure; |
362 |
| - helper = new SynchronizedHelper(); |
| 354 | + helper = new UnsafeAtomicHelper(); |
| 355 | + } catch (Exception | Error unsafeFailure) { // sneaky checked exception |
| 356 | + thrownUnsafeFailure = unsafeFailure; |
| 357 | + // Catch absolutely everything and fall through to AtomicReferenceFieldUpdaterAtomicHelper. |
| 358 | + try { |
| 359 | + helper = new AtomicReferenceFieldUpdaterAtomicHelper(); |
| 360 | + } catch (Exception // sneaky checked exception |
| 361 | + | Error atomicReferenceFieldUpdaterFailure) { |
| 362 | + // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause |
| 363 | + // getDeclaredField to throw a NoSuchFieldException when the field is definitely there. |
| 364 | + // For these users fallback to a suboptimal implementation, based on synchronized. This |
| 365 | + // will be a definite performance hit to those users. |
| 366 | + thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure; |
| 367 | + helper = new SynchronizedHelper(); |
| 368 | + } |
363 | 369 | }
|
364 | 370 | }
|
365 | 371 | ATOMIC_HELPER = helper;
|
@@ -496,6 +502,43 @@ static String atomicHelperTypeForTest() {
|
496 | 502 | return ATOMIC_HELPER.atomicHelperTypeForTest();
|
497 | 503 | }
|
498 | 504 |
|
| 505 | + private enum VarHandleAtomicHelperMaker { |
| 506 | + INSTANCE { |
| 507 | + /** |
| 508 | + * Implementation used by non-J2ObjC environments (aside, of course, from those that have |
| 509 | + * supersource for the entirety of {@link AbstractFutureState}). |
| 510 | + */ |
| 511 | + @Override |
| 512 | + @J2ObjCIncompatible |
| 513 | + @Nullable AtomicHelper tryMakeVarHandleAtomicHelper() { |
| 514 | + if (mightBeAndroid()) { |
| 515 | + return null; |
| 516 | + } |
| 517 | + try { |
| 518 | + /* |
| 519 | + * We first use reflection to check whether VarHandle exists. If we instead just tried to |
| 520 | + * load our class directly (which would trigger non-reflective loading of VarHandle) from |
| 521 | + * within a `try` block, then an error might be thrown even before we enter the `try` |
| 522 | + * block: https://github.com/google/truth/issues/333#issuecomment-765652454 |
| 523 | + * |
| 524 | + * Also, it's nice that this approach should let us catch *only* ClassNotFoundException |
| 525 | + * instead of having to catch more broadly (potentially even including, say, a |
| 526 | + * StackOverflowError). |
| 527 | + */ |
| 528 | + Class.forName("java.lang.invoke.VarHandle"); |
| 529 | + } catch (ClassNotFoundException beforeJava9) { |
| 530 | + return null; |
| 531 | + } |
| 532 | + return new VarHandleAtomicHelper(); |
| 533 | + } |
| 534 | + }; |
| 535 | + |
| 536 | + /** Implementation used by J2ObjC environments, overridden for other environments. */ |
| 537 | + @Nullable AtomicHelper tryMakeVarHandleAtomicHelper() { |
| 538 | + return null; |
| 539 | + } |
| 540 | + } |
| 541 | + |
499 | 542 | private abstract static class AtomicHelper {
|
500 | 543 | /** Non-volatile write of the thread to the {@link Waiter#thread} field. */
|
501 | 544 | abstract void putThread(Waiter waiter, Thread newValue);
|
@@ -524,6 +567,81 @@ abstract boolean casValue(
|
524 | 567 | abstract String atomicHelperTypeForTest();
|
525 | 568 | }
|
526 | 569 |
|
| 570 | + /** {@link AtomicHelper} based on {@link VarHandle}. */ |
| 571 | + @J2ObjCIncompatible |
| 572 | + // We use this class only after confirming that VarHandle is available at runtime. |
| 573 | + @SuppressWarnings({"Java8ApiChecker", "Java7ApiChecker", "AndroidJdkLibsChecker"}) |
| 574 | + @IgnoreJRERequirement |
| 575 | + private static final class VarHandleAtomicHelper extends AtomicHelper { |
| 576 | + static final VarHandle waiterThreadUpdater; |
| 577 | + static final VarHandle waiterNextUpdater; |
| 578 | + static final VarHandle waitersUpdater; |
| 579 | + static final VarHandle listenersUpdater; |
| 580 | + static final VarHandle valueUpdater; |
| 581 | + |
| 582 | + static { |
| 583 | + MethodHandles.Lookup lookup = MethodHandles.lookup(); |
| 584 | + try { |
| 585 | + waiterThreadUpdater = lookup.findVarHandle(Waiter.class, "thread", Thread.class); |
| 586 | + waiterNextUpdater = lookup.findVarHandle(Waiter.class, "next", Waiter.class); |
| 587 | + waitersUpdater = |
| 588 | + lookup.findVarHandle(AbstractFutureState.class, "waitersField", Waiter.class); |
| 589 | + listenersUpdater = |
| 590 | + lookup.findVarHandle(AbstractFutureState.class, "listenersField", Listener.class); |
| 591 | + valueUpdater = lookup.findVarHandle(AbstractFutureState.class, "valueField", Object.class); |
| 592 | + } catch (ReflectiveOperationException e) { |
| 593 | + // Those fields exist. |
| 594 | + throw newLinkageError(e); |
| 595 | + } |
| 596 | + } |
| 597 | + |
| 598 | + @Override |
| 599 | + void putThread(Waiter waiter, Thread newValue) { |
| 600 | + waiterThreadUpdater.setRelease(waiter, newValue); |
| 601 | + } |
| 602 | + |
| 603 | + @Override |
| 604 | + void putNext(Waiter waiter, @Nullable Waiter newValue) { |
| 605 | + waiterNextUpdater.setRelease(waiter, newValue); |
| 606 | + } |
| 607 | + |
| 608 | + @Override |
| 609 | + boolean casWaiters( |
| 610 | + AbstractFutureState<?> future, @Nullable Waiter expect, @Nullable Waiter update) { |
| 611 | + return waitersUpdater.compareAndSet(future, expect, update); |
| 612 | + } |
| 613 | + |
| 614 | + @Override |
| 615 | + boolean casListeners( |
| 616 | + AbstractFutureState<?> future, @Nullable Listener expect, Listener update) { |
| 617 | + return listenersUpdater.compareAndSet(future, expect, update); |
| 618 | + } |
| 619 | + |
| 620 | + @Override |
| 621 | + @Nullable Listener gasListeners(AbstractFutureState<?> future, Listener update) { |
| 622 | + return (Listener) listenersUpdater.getAndSet(future, update); |
| 623 | + } |
| 624 | + |
| 625 | + @Override |
| 626 | + @Nullable Waiter gasWaiters(AbstractFutureState<?> future, Waiter update) { |
| 627 | + return (Waiter) waitersUpdater.getAndSet(future, update); |
| 628 | + } |
| 629 | + |
| 630 | + @Override |
| 631 | + boolean casValue(AbstractFutureState<?> future, @Nullable Object expect, Object update) { |
| 632 | + return valueUpdater.compareAndSet(future, expect, update); |
| 633 | + } |
| 634 | + |
| 635 | + private static LinkageError newLinkageError(Throwable cause) { |
| 636 | + return new LinkageError(cause.toString(), cause); |
| 637 | + } |
| 638 | + |
| 639 | + @Override |
| 640 | + String atomicHelperTypeForTest() { |
| 641 | + return "VarHandleAtomicHelper"; |
| 642 | + } |
| 643 | + } |
| 644 | + |
527 | 645 | /**
|
528 | 646 | * {@link AtomicHelper} based on {@link sun.misc.Unsafe}.
|
529 | 647 | *
|
@@ -777,4 +895,10 @@ String atomicHelperTypeForTest() {
|
777 | 895 | return "SynchronizedHelper";
|
778 | 896 | }
|
779 | 897 | }
|
| 898 | + |
| 899 | + private static boolean mightBeAndroid() { |
| 900 | + String runtime = System.getProperty("java.runtime.name", ""); |
| 901 | + // I have no reason to believe that `null` is possible here, but let's make sure we don't crash: |
| 902 | + return runtime == null || runtime.contains("Android"); |
| 903 | + } |
780 | 904 | }
|
0 commit comments