Merge "Fix ConcurrentModificationException in AcceptOnceConsumer" into tm-qpr-dev

This commit is contained in:
Kenneth Ford
2022-12-15 19:02:49 +00:00
committed by Android (Google) Code Review
2 changed files with 43 additions and 4 deletions

View File

@@ -27,9 +27,10 @@ import java.util.function.Consumer;
*/ */
public class AcceptOnceConsumer<T> implements Consumer<T> { public class AcceptOnceConsumer<T> implements Consumer<T> {
private final Consumer<T> mCallback; private final Consumer<T> mCallback;
private final DataProducer<T> mProducer; private final AcceptOnceProducerCallback<T> mProducer;
public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) { public AcceptOnceConsumer(@NonNull AcceptOnceProducerCallback<T> producer,
@NonNull Consumer<T> callback) {
mProducer = producer; mProducer = producer;
mCallback = callback; mCallback = callback;
} }
@@ -37,6 +38,20 @@ public class AcceptOnceConsumer<T> implements Consumer<T> {
@Override @Override
public void accept(@NonNull T t) { public void accept(@NonNull T t) {
mCallback.accept(t); mCallback.accept(t);
mProducer.removeDataChangedCallback(this); mProducer.onConsumerReadyToBeRemoved(this);
}
/**
* Interface to allow the {@link AcceptOnceConsumer} to notify the client that created it,
* when it is ready to be removed. This allows the client to remove the consumer object
* when it deems it is safe to do so.
* @param <T> The type of data this callback accepts through {@link #onConsumerReadyToBeRemoved}
*/
public interface AcceptOnceProducerCallback<T> {
/**
* Notifies that the given {@code callback} is ready to be removed
*/
void onConsumerReadyToBeRemoved(Consumer<T> callback);
} }
} }

View File

@@ -19,6 +19,7 @@ package androidx.window.util;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -31,11 +32,14 @@ import java.util.function.Consumer;
* *
* @param <T> The type of data this producer returns through {@link DataProducer#getData}. * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/ */
public abstract class BaseDataProducer<T> implements DataProducer<T> { public abstract class BaseDataProducer<T> implements DataProducer<T>,
AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
private final Object mLock = new Object(); private final Object mLock = new Object();
@GuardedBy("mLock") @GuardedBy("mLock")
private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>(); private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
@GuardedBy("mLock")
private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
/** /**
* Adds a callback to the set of callbacks listening for data. Data is delivered through * Adds a callback to the set of callbacks listening for data. Data is delivered through
@@ -85,6 +89,26 @@ public abstract class BaseDataProducer<T> implements DataProducer<T> {
for (Consumer<T> callback : mCallbacks) { for (Consumer<T> callback : mCallbacks) {
callback.accept(value); callback.accept(value);
} }
removeFinishedCallbacksLocked();
}
}
/**
* Removes any callbacks that notified us through {@link #onConsumerReadyToBeRemoved(Consumer)}
* that they are ready to be removed.
*/
@GuardedBy("mLock")
private void removeFinishedCallbacksLocked() {
for (Consumer<T> callback: mCallbacksToRemove) {
mCallbacks.remove(callback);
}
mCallbacksToRemove.clear();
}
@Override
public void onConsumerReadyToBeRemoved(Consumer<T> callback) {
synchronized (mLock) {
mCallbacksToRemove.add(callback);
} }
} }
} }