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