Skip to content

Commit f9eff73

Browse files
cpovirkGoogle Java Core Libraries
authored and
Google Java Core Libraries
committedApr 7, 2025·
Make AggregateFutureState fields package-private.
This ensures that they are accessible to methods like `AtomicIntegerFieldUpdater.newUpdater` no matter which `util.concurrent` class those methods are called from. Previously, we were trying to arrange for those methods to be called from `AggregateFutureState` itself—specifically, through methods like `remainingCountUpdaterFromWithinAggregateFutureState`. However, we're finding that optimizers sometimes inline those methods into callers in other classes, leading to the warning "SafeAtomicHelper is broken!" Rather than try to prevent inlining with `-dontinline` directives, we instead give in and expand the fields' visibility. We already did this for `AbstractFutureState` in cl/742334547, albeit for somewhat different reasons, so we can follow the same playbook here: Rename the fields to make them harder to use by accident (as in cl/741607075), and update our Proguard config for the rename (as in cl/742724817). At that point, we might as well inline methods like `remainingCountUpdaterFromWithinAggregateFutureState` ourselves just to simplify the code, so I've done so. (Note that even `private` fields "should" be accessible to nested classes, thanks to nestmates. However, that's [not the case with `-source 8 -target 8`](https://github.com/google/guava/blob/a429676a3bb68ac9b29cea0f15ae65065bdf5a44/guava/src/com/google/common/util/concurrent/AbstractFutureState.java#L397-L402), and apparently it's not the case for Android, as well, even without `-source 8 -target 8`.) RELNOTES=`util.concurrent`: Modified our fast paths to ensure that they continue to work when run through optimizers, such as those commonly used by Android apps. This fixes problems that some users may have seen since [Guava 33.4.5](https://github.com/google/guava/releases/tag/v33.4.5). (b8dcaed) PiperOrigin-RevId: 744855670
1 parent a429676 commit f9eff73

File tree

5 files changed

+102
-112
lines changed

5 files changed

+102
-112
lines changed
 

Diff for: ‎android/guava/src/com/google/common/util/concurrent/AbstractFutureState.java

+19-9
Original file line numberDiff line numberDiff line change
@@ -392,17 +392,27 @@ void unpark() {
392392
/*
393393
* The following fields are package-private, even though we intend never to use them outside this
394394
* file. If they were instead private, then we wouldn't be able to access them reflectively from
395-
* within VarHandleAtomicHelper.
395+
* within VarHandleAtomicHelper and AtomicReferenceFieldUpdaterAtomicHelper.
396396
*
397-
* Package-private "shouldn't" be necessary: VarHandleAtomicHelper and AbstractFutureState
398-
* "should" be nestmates, so a call to MethodHandles.lookup inside VarHandleAtomicHelper "should"
399-
* have access to AbstractFutureState's private fields. However, our open-source build uses
400-
* `-source 8 -target 8`, so the class files from that build can't express nestmates. Thus, when
401-
* those class files are used from Java 9 or higher (i.e., high enough to trigger the VarHandle
402-
* code path), such a lookup would fail with an IllegalAccessException.
397+
* Package-private "shouldn't" be necessary: The *AtomicHelper classes and AbstractFutureState
398+
* "should" be nestmates, so a call to MethodHandles.lookup or
399+
* AtomicReferenceFieldUpdater.newUpdater inside *AtomicHelper "should" have access to
400+
* AbstractFutureState's private fields. However, our open-source build uses `-source 8 -target
401+
* 8`, so the class files from that build can't express nestmates. Thus, when those class files
402+
* are used from Java 9 or higher (i.e., high enough to trigger the VarHandle code path), such a
403+
* lookup would fail with an IllegalAccessException. That may then trigger use of Unsafe (possibly
404+
* with a warning under recent JVMs), or it may fall back even further to
405+
* AtomicReferenceFieldUpdaterAtomicHelper, which would fail with a similar problem to
406+
* VarHandleAtomicHelperMaker, forcing us all the way to SynchronizedAtomicHelper.
403407
*
404-
* This same problem is one of the reasons for us to likewise keep the fields in Waiter as
405-
* package-private instead of private.
408+
* Additionally, it seems that nestmates do not help with runtime reflection under *Android*, even
409+
* when we use a newer -source and -target. That doesn't normally matter for AbstractFutureState,
410+
* since Android should normally succed in using UnsafeAtomicHelper and thus never even try the
411+
* problematic AtomicReferenceFieldUpdaterAtomicHelper code path. However, the same problem *does*
412+
* matter with AggregateFutureState, which does not have an Unsafe-based helper.
413+
*
414+
* This same problem is one of the reasons for us to likewise use package-private for the fields
415+
* in Waiter.
406416
*/
407417

408418
/**

Diff for: ‎android/guava/src/com/google/common/util/concurrent/AggregateFutureState.java

+31-46
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@
4141
@ReflectionSupport(value = ReflectionSupport.Level.FULL)
4242
abstract class AggregateFutureState<OutputT extends @Nullable Object>
4343
extends AbstractFuture.TrustedFuture<OutputT> {
44+
/*
45+
* The following fields are package-private, even though we intend never to use them outside this
46+
* file. For discussion, see AbstractFutureState.
47+
*/
48+
4449
// Lazily initialized the first time we see an exception; not released until all the input futures
4550
// have completed and we have processed them all.
46-
private volatile @Nullable Set<Throwable> seenExceptions = null;
51+
volatile @Nullable Set<Throwable> seenExceptionsField = null;
4752

48-
private volatile int remaining;
53+
volatile int remainingField;
4954

5055
private static final AtomicHelper ATOMIC_HELPER;
5156

@@ -73,27 +78,29 @@ abstract class AggregateFutureState<OutputT extends @Nullable Object>
7378
}
7479

7580
AggregateFutureState(int remainingFutures) {
76-
this.remaining = remainingFutures;
81+
this.remainingField = remainingFutures;
7782
}
7883

7984
final Set<Throwable> getOrInitSeenExceptions() {
8085
/*
81-
* The initialization of seenExceptions has to be more complicated than we'd like. The simple
82-
* approach would be for each caller CAS it from null to a Set populated with its exception. But
83-
* there's another race: If the first thread fails with an exception and a second thread
84-
* immediately fails with the same exception:
86+
* The initialization of seenExceptionsField has to be more complicated than we'd like. The
87+
* simple approach would be for each caller CAS it from null to a Set populated with its
88+
* exception. But there's another race: If the first thread fails with an exception and a second
89+
* thread immediately fails with the same exception:
8590
*
8691
* Thread1: calls setException(), which returns true, context switch before it can CAS
87-
* seenExceptions to its exception
92+
* seenExceptionsField to its exception
8893
*
89-
* Thread2: calls setException(), which returns false, CASes seenExceptions to its exception,
90-
* and wrongly believes that its exception is new (leading it to logging it when it shouldn't)
94+
* Thread2: calls setException(), which returns false, CASes seenExceptionsField to its
95+
* exception, and wrongly believes that its exception is new (leading it to logging it when it
96+
* shouldn't)
9197
*
92-
* Our solution is for threads to CAS seenExceptions from null to a Set populated with _the
93-
* initial exception_, no matter which thread does the work. This ensures that seenExceptions
94-
* always contains not just the current thread's exception but also the initial thread's.
98+
* Our solution is for threads to CAS seenExceptionsField from null to a Set populated with _the
99+
* initial exception_, no matter which thread does the work. This ensures that
100+
* seenExceptionsField always contains not just the current thread's exception but also the
101+
* initial thread's.
95102
*/
96-
Set<Throwable> seenExceptionsLocal = seenExceptions;
103+
Set<Throwable> seenExceptionsLocal = seenExceptionsField;
97104
if (seenExceptionsLocal == null) {
98105
// TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure?
99106
/*
@@ -128,7 +135,7 @@ final Set<Throwable> getOrInitSeenExceptions() {
128135
* requireNonNull is safe because either our compareAndSet succeeded or it failed because
129136
* another thread did it for us.
130137
*/
131-
seenExceptionsLocal = requireNonNull(seenExceptions);
138+
seenExceptionsLocal = requireNonNull(seenExceptionsField);
132139
}
133140
return seenExceptionsLocal;
134141
}
@@ -141,7 +148,7 @@ final int decrementRemainingAndGet() {
141148
}
142149

143150
final void clearSeenExceptions() {
144-
seenExceptions = null;
151+
seenExceptionsField = null;
145152
}
146153

147154
@VisibleForTesting
@@ -150,11 +157,11 @@ static String atomicHelperTypeForTest() {
150157
}
151158

152159
private abstract static class AtomicHelper {
153-
/** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */
160+
/** Performs an atomic compare-and-set of {@link AggregateFutureState#seenExceptionsField}. */
154161
abstract void compareAndSetSeenExceptions(
155162
AggregateFutureState<?> state, @Nullable Set<Throwable> expect, Set<Throwable> update);
156163

157-
/** Atomic decrement-and-get of the {@link AggregateFutureState#remaining} field. */
164+
/** Performs an atomic decrement-and-get of {@link AggregateFutureState#remainingField}. */
158165
abstract int decrementAndGetRemainingCount(AggregateFutureState<?> state);
159166

160167
abstract String atomicHelperTypeForTest();
@@ -163,10 +170,11 @@ abstract void compareAndSetSeenExceptions(
163170
private static final class SafeAtomicHelper extends AtomicHelper {
164171
private static final AtomicReferenceFieldUpdater<
165172
? super AggregateFutureState<?>, ? super @Nullable Set<Throwable>>
166-
seenExceptionsUpdater = seenExceptionsUpdaterFromWithinAggregateFutureState();
173+
seenExceptionsUpdater =
174+
newUpdater(AggregateFutureState.class, Set.class, "seenExceptionsField");
167175

168176
private static final AtomicIntegerFieldUpdater<? super AggregateFutureState<?>>
169-
remainingCountUpdater = remainingCountUpdaterFromWithinAggregateFutureState();
177+
remainingCountUpdater = newUpdater(AggregateFutureState.class, "remainingField");
170178

171179
@Override
172180
void compareAndSetSeenExceptions(
@@ -190,16 +198,16 @@ private static final class SynchronizedAtomicHelper extends AtomicHelper {
190198
void compareAndSetSeenExceptions(
191199
AggregateFutureState<?> state, @Nullable Set<Throwable> expect, Set<Throwable> update) {
192200
synchronized (state) {
193-
if (state.seenExceptions == expect) {
194-
state.seenExceptions = update;
201+
if (state.seenExceptionsField == expect) {
202+
state.seenExceptionsField = update;
195203
}
196204
}
197205
}
198206

199207
@Override
200208
int decrementAndGetRemainingCount(AggregateFutureState<?> state) {
201209
synchronized (state) {
202-
return --state.remaining;
210+
return --state.remainingField;
203211
}
204212
}
205213

@@ -208,27 +216,4 @@ String atomicHelperTypeForTest() {
208216
return "SynchronizedAtomicHelper";
209217
}
210218
}
211-
212-
/**
213-
* Returns an {@link AtomicReferenceFieldUpdater} for {@link #seenExceptions}.
214-
*
215-
* <p>The creation of the updater has to happen directly inside {@link AggregateFutureState}, as
216-
* discussed in {@link AbstractFuture#methodHandlesLookupFromWithinAbstractFuture}.
217-
*/
218-
private static AtomicReferenceFieldUpdater<
219-
? super AggregateFutureState<?>, ? super @Nullable Set<Throwable>>
220-
seenExceptionsUpdaterFromWithinAggregateFutureState() {
221-
return newUpdater(AggregateFutureState.class, Set.class, "seenExceptions");
222-
}
223-
224-
/**
225-
* Returns an {@link AtomicIntegerFieldUpdater} for {@link #remaining}.
226-
*
227-
* <p>The creation of the updater has to happen directly inside {@link AggregateFutureState}, as
228-
* discussed in {@link AbstractFuture#methodHandlesLookupFromWithinAbstractFuture}.
229-
*/
230-
private static AtomicIntegerFieldUpdater<? super AggregateFutureState<?>>
231-
remainingCountUpdaterFromWithinAggregateFutureState() {
232-
return newUpdater(AggregateFutureState.class, "remaining");
233-
}
234219
}

Diff for: ‎guava/src/com/google/common/util/concurrent/AbstractFutureState.java

+19-9
Original file line numberDiff line numberDiff line change
@@ -392,17 +392,27 @@ void unpark() {
392392
/*
393393
* The following fields are package-private, even though we intend never to use them outside this
394394
* file. If they were instead private, then we wouldn't be able to access them reflectively from
395-
* within VarHandleAtomicHelper.
395+
* within VarHandleAtomicHelper and AtomicReferenceFieldUpdaterAtomicHelper.
396396
*
397-
* Package-private "shouldn't" be necessary: VarHandleAtomicHelper and AbstractFutureState
398-
* "should" be nestmates, so a call to MethodHandles.lookup inside VarHandleAtomicHelper "should"
399-
* have access to AbstractFutureState's private fields. However, our open-source build uses
400-
* `-source 8 -target 8`, so the class files from that build can't express nestmates. Thus, when
401-
* those class files are used from Java 9 or higher (i.e., high enough to trigger the VarHandle
402-
* code path), such a lookup would fail with an IllegalAccessException.
397+
* Package-private "shouldn't" be necessary: The *AtomicHelper classes and AbstractFutureState
398+
* "should" be nestmates, so a call to MethodHandles.lookup or
399+
* AtomicReferenceFieldUpdater.newUpdater inside *AtomicHelper "should" have access to
400+
* AbstractFutureState's private fields. However, our open-source build uses `-source 8 -target
401+
* 8`, so the class files from that build can't express nestmates. Thus, when those class files
402+
* are used from Java 9 or higher (i.e., high enough to trigger the VarHandle code path), such a
403+
* lookup would fail with an IllegalAccessException. That may then trigger use of Unsafe (possibly
404+
* with a warning under recent JVMs), or it may fall back even further to
405+
* AtomicReferenceFieldUpdaterAtomicHelper, which would fail with a similar problem to
406+
* VarHandleAtomicHelperMaker, forcing us all the way to SynchronizedAtomicHelper.
403407
*
404-
* This same problem is one of the reasons for us to likewise keep the fields in Waiter as
405-
* package-private instead of private.
408+
* Additionally, it seems that nestmates do not help with runtime reflection under *Android*, even
409+
* when we use a newer -source and -target. That doesn't normally matter for AbstractFutureState,
410+
* since Android should normally succed in using UnsafeAtomicHelper and thus never even try the
411+
* problematic AtomicReferenceFieldUpdaterAtomicHelper code path. However, the same problem *does*
412+
* matter with AggregateFutureState, which does not have an Unsafe-based helper.
413+
*
414+
* This same problem is one of the reasons for us to likewise use package-private for the fields
415+
* in Waiter.
406416
*/
407417

408418
/**

Diff for: ‎guava/src/com/google/common/util/concurrent/AggregateFutureState.java

+31-46
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@
4141
@ReflectionSupport(value = ReflectionSupport.Level.FULL)
4242
abstract class AggregateFutureState<OutputT extends @Nullable Object>
4343
extends AbstractFuture.TrustedFuture<OutputT> {
44+
/*
45+
* The following fields are package-private, even though we intend never to use them outside this
46+
* file. For discussion, see AbstractFutureState.
47+
*/
48+
4449
// Lazily initialized the first time we see an exception; not released until all the input futures
4550
// have completed and we have processed them all.
46-
private volatile @Nullable Set<Throwable> seenExceptions = null;
51+
volatile @Nullable Set<Throwable> seenExceptionsField = null;
4752

48-
private volatile int remaining;
53+
volatile int remainingField;
4954

5055
private static final AtomicHelper ATOMIC_HELPER;
5156

@@ -73,27 +78,29 @@ abstract class AggregateFutureState<OutputT extends @Nullable Object>
7378
}
7479

7580
AggregateFutureState(int remainingFutures) {
76-
this.remaining = remainingFutures;
81+
this.remainingField = remainingFutures;
7782
}
7883

7984
final Set<Throwable> getOrInitSeenExceptions() {
8085
/*
81-
* The initialization of seenExceptions has to be more complicated than we'd like. The simple
82-
* approach would be for each caller CAS it from null to a Set populated with its exception. But
83-
* there's another race: If the first thread fails with an exception and a second thread
84-
* immediately fails with the same exception:
86+
* The initialization of seenExceptionsField has to be more complicated than we'd like. The
87+
* simple approach would be for each caller CAS it from null to a Set populated with its
88+
* exception. But there's another race: If the first thread fails with an exception and a second
89+
* thread immediately fails with the same exception:
8590
*
8691
* Thread1: calls setException(), which returns true, context switch before it can CAS
87-
* seenExceptions to its exception
92+
* seenExceptionsField to its exception
8893
*
89-
* Thread2: calls setException(), which returns false, CASes seenExceptions to its exception,
90-
* and wrongly believes that its exception is new (leading it to logging it when it shouldn't)
94+
* Thread2: calls setException(), which returns false, CASes seenExceptionsField to its
95+
* exception, and wrongly believes that its exception is new (leading it to logging it when it
96+
* shouldn't)
9197
*
92-
* Our solution is for threads to CAS seenExceptions from null to a Set populated with _the
93-
* initial exception_, no matter which thread does the work. This ensures that seenExceptions
94-
* always contains not just the current thread's exception but also the initial thread's.
98+
* Our solution is for threads to CAS seenExceptionsField from null to a Set populated with _the
99+
* initial exception_, no matter which thread does the work. This ensures that
100+
* seenExceptionsField always contains not just the current thread's exception but also the
101+
* initial thread's.
95102
*/
96-
Set<Throwable> seenExceptionsLocal = seenExceptions;
103+
Set<Throwable> seenExceptionsLocal = seenExceptionsField;
97104
if (seenExceptionsLocal == null) {
98105
// TODO(cpovirk): Should we use a simpler (presumably cheaper) data structure?
99106
/*
@@ -128,7 +135,7 @@ final Set<Throwable> getOrInitSeenExceptions() {
128135
* requireNonNull is safe because either our compareAndSet succeeded or it failed because
129136
* another thread did it for us.
130137
*/
131-
seenExceptionsLocal = requireNonNull(seenExceptions);
138+
seenExceptionsLocal = requireNonNull(seenExceptionsField);
132139
}
133140
return seenExceptionsLocal;
134141
}
@@ -141,7 +148,7 @@ final int decrementRemainingAndGet() {
141148
}
142149

143150
final void clearSeenExceptions() {
144-
seenExceptions = null;
151+
seenExceptionsField = null;
145152
}
146153

147154
@VisibleForTesting
@@ -150,11 +157,11 @@ static String atomicHelperTypeForTest() {
150157
}
151158

152159
private abstract static class AtomicHelper {
153-
/** Atomic compare-and-set of the {@link AggregateFutureState#seenExceptions} field. */
160+
/** Performs an atomic compare-and-set of {@link AggregateFutureState#seenExceptionsField}. */
154161
abstract void compareAndSetSeenExceptions(
155162
AggregateFutureState<?> state, @Nullable Set<Throwable> expect, Set<Throwable> update);
156163

157-
/** Atomic decrement-and-get of the {@link AggregateFutureState#remaining} field. */
164+
/** Performs an atomic decrement-and-get of {@link AggregateFutureState#remainingField}. */
158165
abstract int decrementAndGetRemainingCount(AggregateFutureState<?> state);
159166

160167
abstract String atomicHelperTypeForTest();
@@ -163,10 +170,11 @@ abstract void compareAndSetSeenExceptions(
163170
private static final class SafeAtomicHelper extends AtomicHelper {
164171
private static final AtomicReferenceFieldUpdater<
165172
? super AggregateFutureState<?>, ? super @Nullable Set<Throwable>>
166-
seenExceptionsUpdater = seenExceptionsUpdaterFromWithinAggregateFutureState();
173+
seenExceptionsUpdater =
174+
newUpdater(AggregateFutureState.class, Set.class, "seenExceptionsField");
167175

168176
private static final AtomicIntegerFieldUpdater<? super AggregateFutureState<?>>
169-
remainingCountUpdater = remainingCountUpdaterFromWithinAggregateFutureState();
177+
remainingCountUpdater = newUpdater(AggregateFutureState.class, "remainingField");
170178

171179
@Override
172180
void compareAndSetSeenExceptions(
@@ -190,16 +198,16 @@ private static final class SynchronizedAtomicHelper extends AtomicHelper {
190198
void compareAndSetSeenExceptions(
191199
AggregateFutureState<?> state, @Nullable Set<Throwable> expect, Set<Throwable> update) {
192200
synchronized (state) {
193-
if (state.seenExceptions == expect) {
194-
state.seenExceptions = update;
201+
if (state.seenExceptionsField == expect) {
202+
state.seenExceptionsField = update;
195203
}
196204
}
197205
}
198206

199207
@Override
200208
int decrementAndGetRemainingCount(AggregateFutureState<?> state) {
201209
synchronized (state) {
202-
return --state.remaining;
210+
return --state.remainingField;
203211
}
204212
}
205213

@@ -208,27 +216,4 @@ String atomicHelperTypeForTest() {
208216
return "SynchronizedAtomicHelper";
209217
}
210218
}
211-
212-
/**
213-
* Returns an {@link AtomicReferenceFieldUpdater} for {@link #seenExceptions}.
214-
*
215-
* <p>The creation of the updater has to happen directly inside {@link AggregateFutureState}, as
216-
* discussed in {@link AbstractFuture#methodHandlesLookupFromWithinAbstractFuture}.
217-
*/
218-
private static AtomicReferenceFieldUpdater<
219-
? super AggregateFutureState<?>, ? super @Nullable Set<Throwable>>
220-
seenExceptionsUpdaterFromWithinAggregateFutureState() {
221-
return newUpdater(AggregateFutureState.class, Set.class, "seenExceptions");
222-
}
223-
224-
/**
225-
* Returns an {@link AtomicIntegerFieldUpdater} for {@link #remaining}.
226-
*
227-
* <p>The creation of the updater has to happen directly inside {@link AggregateFutureState}, as
228-
* discussed in {@link AbstractFuture#methodHandlesLookupFromWithinAbstractFuture}.
229-
*/
230-
private static AtomicIntegerFieldUpdater<? super AggregateFutureState<?>>
231-
remainingCountUpdaterFromWithinAggregateFutureState() {
232-
return newUpdater(AggregateFutureState.class, "remaining");
233-
}
234219
}

Diff for: ‎proguard/concurrent.pro

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
*** value;
2121
}
2222
-keepclassmembers class com.google.common.util.concurrent.AggregateFutureState {
23-
*** remaining;
24-
*** seenExceptions;
23+
*** remainingField;
24+
*** seenExceptionsField;
2525
}
2626

2727
# Since Unsafe is using the field offsets of these inner classes, we don't want

0 commit comments

Comments
 (0)
Please sign in to comment.