29
29
import com .google .common .util .concurrent .internal .InternalFutures ;
30
30
import com .google .errorprone .annotations .CanIgnoreReturnValue ;
31
31
import com .google .errorprone .annotations .ForOverride ;
32
+ import com .google .j2objc .annotations .J2ObjCIncompatible ;
32
33
import com .google .j2objc .annotations .ReflectionSupport ;
33
34
import com .google .j2objc .annotations .RetainedLocalRef ;
35
+ import java .lang .invoke .MethodHandles ;
36
+ import java .lang .invoke .VarHandle ;
34
37
import java .lang .reflect .Field ;
35
38
import java .security .AccessController ;
36
39
import java .security .PrivilegedActionException ;
@@ -150,35 +153,60 @@ public final boolean cancel(boolean mayInterruptIfRunning) {
150
153
151
154
private static final AtomicHelper ATOMIC_HELPER ;
152
155
156
+ /**
157
+ * Returns the result of calling {@link MethodHandles#lookup} from inside {@link AbstractFuture}.
158
+ * By virtue of being created there, it has access to the private fields of {@link
159
+ * AbstractFuture}, so that access is available to anyone who calls this method—specifically, to
160
+ * {@link VarHandleAtomicHelper}.
161
+ *
162
+ * <p>This "shouldn't" be necessary: {@link VarHandleAtomicHelper} and {@link AbstractFuture}
163
+ * "should" be nestmates, so a call to {@link MethodHandles#lookup} inside {@link
164
+ * VarHandleAtomicHelper} "should" have access to each other's private fields. However, our
165
+ * open-source build uses {@code -source 8 -target 8}, so the class files from that build can't
166
+ * express nestmates. Thus, when those class files are used from Java 9 or higher (i.e., high
167
+ * enough to trigger the {@link VarHandle} code path), such a lookup would fail with an {@link
168
+ * IllegalAccessException}.
169
+ *
170
+ * <p>Note that we do not have a similar problem with the fields in {@link Waiter} because those
171
+ * fields are not private. (We could solve the problem with {@link AbstractFuture} fields in the
172
+ * same way if we wanted.)
173
+ */
174
+ private static MethodHandles .Lookup methodHandlesLookupFromWithinAbstractFuture () {
175
+ return MethodHandles .lookup ();
176
+ }
177
+
153
178
static {
154
179
AtomicHelper helper ;
155
180
Throwable thrownUnsafeFailure = null ;
156
181
Throwable thrownAtomicReferenceFieldUpdaterFailure = null ;
157
182
158
- try {
159
- helper = new UnsafeAtomicHelper ();
160
- } catch (Exception | Error unsafeFailure ) { // sneaky checked exception
161
- thrownUnsafeFailure = unsafeFailure ;
162
- // catch absolutely everything and fall through to our
163
- // 'AtomicReferenceFieldUpdaterAtomicHelper' The access control checks that ARFU does means
164
- // the caller class has to be AbstractFuture instead of
165
- // AtomicReferenceFieldUpdaterAtomicHelper, so we annoyingly define these here
183
+ helper = VarHandleAtomicHelperMaker .INSTANCE .tryMakeVarHandleAtomicHelper ();
184
+ if (helper == null ) {
166
185
try {
167
- helper =
168
- new AtomicReferenceFieldUpdaterAtomicHelper (
169
- newUpdater (Waiter .class , Thread .class , "thread" ),
170
- newUpdater (Waiter .class , Waiter .class , "next" ),
171
- newUpdater (AbstractFuture .class , Waiter .class , "waiters" ),
172
- newUpdater (AbstractFuture .class , Listener .class , "listeners" ),
173
- newUpdater (AbstractFuture .class , Object .class , "value" ));
174
- } catch (Exception // sneaky checked exception
175
- | Error atomicReferenceFieldUpdaterFailure ) {
176
- // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause
177
- // getDeclaredField to throw a NoSuchFieldException when the field is definitely there.
178
- // For these users fallback to a suboptimal implementation, based on synchronized. This will
179
- // be a definite performance hit to those users.
180
- thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure ;
181
- helper = new SynchronizedHelper ();
186
+ helper = new UnsafeAtomicHelper ();
187
+ } catch (Exception | Error unsafeFailure ) { // sneaky checked exception
188
+ thrownUnsafeFailure = unsafeFailure ;
189
+ // catch absolutely everything and fall through to our
190
+ // 'AtomicReferenceFieldUpdaterAtomicHelper' The access control checks that ARFU does means
191
+ // the caller class has to be AbstractFuture instead of
192
+ // AtomicReferenceFieldUpdaterAtomicHelper, so we annoyingly define these here
193
+ try {
194
+ helper =
195
+ new AtomicReferenceFieldUpdaterAtomicHelper (
196
+ newUpdater (Waiter .class , Thread .class , "thread" ),
197
+ newUpdater (Waiter .class , Waiter .class , "next" ),
198
+ newUpdater (AbstractFuture .class , Waiter .class , "waiters" ),
199
+ newUpdater (AbstractFuture .class , Listener .class , "listeners" ),
200
+ newUpdater (AbstractFuture .class , Object .class , "value" ));
201
+ } catch (Exception // sneaky checked exception
202
+ | Error atomicReferenceFieldUpdaterFailure ) {
203
+ // Some Android 5.0.x Samsung devices have bugs in JDK reflection APIs that cause
204
+ // getDeclaredField to throw a NoSuchFieldException when the field is definitely there.
205
+ // For these users fallback to a suboptimal implementation, based on synchronized. This
206
+ // will be a definite performance hit to those users.
207
+ thrownAtomicReferenceFieldUpdaterFailure = atomicReferenceFieldUpdaterFailure ;
208
+ helper = new SynchronizedHelper ();
209
+ }
182
210
}
183
211
}
184
212
ATOMIC_HELPER = helper ;
@@ -200,6 +228,40 @@ public final boolean cancel(boolean mayInterruptIfRunning) {
200
228
}
201
229
}
202
230
231
+ private enum VarHandleAtomicHelperMaker {
232
+ INSTANCE {
233
+ /**
234
+ * Implementation used by non-J2ObjC environments (aside, of course, from those that have
235
+ * supersource for the entirety of {@link AbstractFuture}).
236
+ */
237
+ @ Override
238
+ @ J2ObjCIncompatible
239
+ @ Nullable AtomicHelper tryMakeVarHandleAtomicHelper () {
240
+ try {
241
+ /*
242
+ * We first use reflection to check whether VarHandle exists. If we instead just tried to
243
+ * load our class directly (which would trigger non-reflective loading of VarHandle) from
244
+ * within a `try` block, then an error might be thrown even before we enter the `try`
245
+ * block: https://github.com/google/truth/issues/333#issuecomment-765652454
246
+ *
247
+ * Also, it's nice that this approach should let us catch *only* ClassNotFoundException
248
+ * instead of having to catch more broadly (potentially even including, say, a
249
+ * StackOverflowError).
250
+ */
251
+ Class .forName ("java.lang.invoke.VarHandle" );
252
+ } catch (ClassNotFoundException beforeJava9 ) {
253
+ return null ;
254
+ }
255
+ return new VarHandleAtomicHelper ();
256
+ }
257
+ };
258
+
259
+ /** Implementation used by J2ObjC environments, overridden for other environments. */
260
+ @ Nullable AtomicHelper tryMakeVarHandleAtomicHelper () {
261
+ return null ;
262
+ }
263
+ }
264
+
203
265
/** Waiter links form a Treiber stack, in the {@link #waiters} field. */
204
266
private static final class Waiter {
205
267
static final Waiter TOMBSTONE = new Waiter (false /* ignored param */ );
@@ -1339,6 +1401,72 @@ abstract boolean casListeners(
1339
1401
abstract boolean casValue (AbstractFuture <?> future , @ Nullable Object expect , Object update );
1340
1402
}
1341
1403
1404
+ /** {@link AtomicHelper} based on {@link VarHandle}. */
1405
+ @ J2ObjCIncompatible
1406
+ // We use this class only after confirming that VarHandle is available at runtime.
1407
+ @ SuppressWarnings ("Java8ApiChecker" )
1408
+ @ IgnoreJRERequirement
1409
+ private static final class VarHandleAtomicHelper extends AtomicHelper {
1410
+ static final VarHandle waiterThreadUpdater ;
1411
+ static final VarHandle waiterNextUpdater ;
1412
+ static final VarHandle waitersUpdater ;
1413
+ static final VarHandle listenersUpdater ;
1414
+ static final VarHandle valueUpdater ;
1415
+
1416
+ static {
1417
+ MethodHandles .Lookup lookup = methodHandlesLookupFromWithinAbstractFuture ();
1418
+ try {
1419
+ waiterThreadUpdater = lookup .findVarHandle (Waiter .class , "thread" , Thread .class );
1420
+ waiterNextUpdater = lookup .findVarHandle (Waiter .class , "next" , Waiter .class );
1421
+ waitersUpdater = lookup .findVarHandle (AbstractFuture .class , "waiters" , Waiter .class );
1422
+ listenersUpdater = lookup .findVarHandle (AbstractFuture .class , "listeners" , Listener .class );
1423
+ valueUpdater = lookup .findVarHandle (AbstractFuture .class , "value" , Object .class );
1424
+ } catch (ReflectiveOperationException e ) {
1425
+ // Those fields exist.
1426
+ throw newLinkageError (e );
1427
+ }
1428
+ }
1429
+
1430
+ @ Override
1431
+ void putThread (Waiter waiter , Thread newValue ) {
1432
+ waiterThreadUpdater .setRelease (waiter , newValue );
1433
+ }
1434
+
1435
+ @ Override
1436
+ void putNext (Waiter waiter , @ Nullable Waiter newValue ) {
1437
+ waiterNextUpdater .setRelease (waiter , newValue );
1438
+ }
1439
+
1440
+ @ Override
1441
+ boolean casWaiters (AbstractFuture <?> future , @ Nullable Waiter expect , @ Nullable Waiter update ) {
1442
+ return waitersUpdater .compareAndSet (future , expect , update );
1443
+ }
1444
+
1445
+ @ Override
1446
+ boolean casListeners (AbstractFuture <?> future , @ Nullable Listener expect , Listener update ) {
1447
+ return listenersUpdater .compareAndSet (future , expect , update );
1448
+ }
1449
+
1450
+ @ Override
1451
+ Listener gasListeners (AbstractFuture <?> future , Listener update ) {
1452
+ return (Listener ) listenersUpdater .getAndSet (future , update );
1453
+ }
1454
+
1455
+ @ Override
1456
+ Waiter gasWaiters (AbstractFuture <?> future , Waiter update ) {
1457
+ return (Waiter ) waitersUpdater .getAndSet (future , update );
1458
+ }
1459
+
1460
+ @ Override
1461
+ boolean casValue (AbstractFuture <?> future , @ Nullable Object expect , Object update ) {
1462
+ return valueUpdater .compareAndSet (future , expect , update );
1463
+ }
1464
+
1465
+ private static LinkageError newLinkageError (Throwable cause ) {
1466
+ return new LinkageError (cause .toString (), cause );
1467
+ }
1468
+ }
1469
+
1342
1470
/**
1343
1471
* {@link AtomicHelper} based on {@link sun.misc.Unsafe}.
1344
1472
*
0 commit comments