Don't publish computed value to other threads till post compute is finished (KT-28940)
This fixes a race condition with counting supertypes (the only client of createLazyValueWithPostCompute left at the moment). #KT-28940 Fixed
This commit is contained in:
committed by
Nikolay Krasko
parent
4f488ddd16
commit
728ccdee63
@@ -174,7 +174,7 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
final Function1<? super Boolean, ? extends T> onRecursiveCall,
|
final Function1<? super Boolean, ? extends T> onRecursiveCall,
|
||||||
@NotNull final Function1<? super T, Unit> postCompute
|
@NotNull final Function1<? super T, Unit> postCompute
|
||||||
) {
|
) {
|
||||||
return new LockBasedNotNullLazyValue<T>(this, computable) {
|
return new LockBasedNotNullLazyValueWithPostCompute<T>(this, computable) {
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
protected RecursionDetectedResult<T> recursionDetected(boolean firstTime) {
|
protected RecursionDetectedResult<T> recursionDetected(boolean firstTime) {
|
||||||
@@ -185,13 +185,13 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void postCompute(@NotNull T value) {
|
protected void doPostCompute(@NotNull T value) {
|
||||||
postCompute.invoke(value);
|
postCompute.invoke(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String presentableName() {
|
protected String presentableName() {
|
||||||
return "LazyValueWithPostCompute";
|
return "LockBasedNotNullLazyValueWithPostCompute";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -224,9 +224,9 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
public <T> NullableLazyValue<T> createNullableLazyValueWithPostCompute(
|
public <T> NullableLazyValue<T> createNullableLazyValueWithPostCompute(
|
||||||
@NotNull Function0<? extends T> computable, @NotNull final Function1<? super T, Unit> postCompute
|
@NotNull Function0<? extends T> computable, @NotNull final Function1<? super T, Unit> postCompute
|
||||||
) {
|
) {
|
||||||
return new LockBasedLazyValue<T>(this, computable) {
|
return new LockBasedLazyValueWithPostCompute<T>(this, computable) {
|
||||||
@Override
|
@Override
|
||||||
protected void postCompute(@Nullable T value) {
|
protected void doPostCompute(T value) {
|
||||||
postCompute.invoke(value);
|
postCompute.invoke(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,18 +303,6 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
RECURSION_WAS_DETECTED
|
RECURSION_WAS_DETECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Important thread-safety note!
|
|
||||||
*
|
|
||||||
* This implementation publishes value **BEFORE** calling postCompute on it.
|
|
||||||
*
|
|
||||||
* It means that thread-safety of actions in postCompute() and recursion prevention
|
|
||||||
* rely *solely* on the `storageManager.lock()`.
|
|
||||||
*
|
|
||||||
* And yes, there are a LockBasedStorageManager.NO_LOCKS, which doesn't have lock at all,
|
|
||||||
* so if you have some `StorageManager` (or even if it is an instanceof `LockBasedStorageManager`),
|
|
||||||
* thread-safety of produced lazy values still not guaranteed.
|
|
||||||
*/
|
|
||||||
private static class LockBasedLazyValue<T> implements NullableLazyValue<T> {
|
private static class LockBasedLazyValue<T> implements NullableLazyValue<T> {
|
||||||
private final LockBasedStorageManager storageManager;
|
private final LockBasedStorageManager storageManager;
|
||||||
private final Function0<? extends T> computable;
|
private final Function0<? extends T> computable;
|
||||||
@@ -365,8 +353,12 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
value = NotValue.COMPUTING;
|
value = NotValue.COMPUTING;
|
||||||
try {
|
try {
|
||||||
T typedValue = computable.invoke();
|
T typedValue = computable.invoke();
|
||||||
value = typedValue;
|
|
||||||
|
// Don't publish computed value till post compute is finished as it may cause a race condition
|
||||||
|
// if post compute modifies value internals.
|
||||||
postCompute(typedValue);
|
postCompute(typedValue);
|
||||||
|
|
||||||
|
value = typedValue;
|
||||||
return typedValue;
|
return typedValue;
|
||||||
}
|
}
|
||||||
catch (Throwable throwable) {
|
catch (Throwable throwable) {
|
||||||
@@ -398,7 +390,7 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void postCompute(T value) {
|
protected void postCompute(T value) {
|
||||||
// Doing something in post-compute helps prevent infinite recursion
|
// Default post compute implementation doesn't publish the value till it is finished
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -411,8 +403,67 @@ public class LockBasedStorageManager implements StorageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LockBasedNotNullLazyValue<T> extends LockBasedLazyValue<T> implements NotNullLazyValue<T> {
|
private static abstract class LockBasedLazyValueWithPostCompute<T> extends LockBasedLazyValue<T> {
|
||||||
|
@NotNull
|
||||||
|
private final ThreadLocal<Object> valuePostCompute = new ThreadLocal<Object>() {
|
||||||
|
@Override
|
||||||
|
protected Object initialValue() {
|
||||||
|
return NotValue.NOT_COMPUTED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public LockBasedLazyValueWithPostCompute(
|
||||||
|
@NotNull LockBasedStorageManager storageManager,
|
||||||
|
@NotNull Function0<? extends T> computable
|
||||||
|
) {
|
||||||
|
super(storageManager, computable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T invoke() {
|
||||||
|
Object _value = valuePostCompute.get();
|
||||||
|
if (!(_value instanceof NotValue)) {
|
||||||
|
// This thread is counting the value so allow an early publication
|
||||||
|
return WrappedValues.unescapeThrowable(_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doing something in post-compute helps prevent infinite recursion
|
||||||
|
@Override
|
||||||
|
protected final void postCompute(T value) {
|
||||||
|
valuePostCompute.set(value);
|
||||||
|
try {
|
||||||
|
doPostCompute(value);
|
||||||
|
} finally {
|
||||||
|
valuePostCompute.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doPostCompute(T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class LockBasedNotNullLazyValueWithPostCompute<T> extends LockBasedLazyValueWithPostCompute<T>
|
||||||
|
implements NotNullLazyValue<T> {
|
||||||
|
public LockBasedNotNullLazyValueWithPostCompute(
|
||||||
|
@NotNull LockBasedStorageManager storageManager,
|
||||||
|
@NotNull Function0<? extends T> computable
|
||||||
|
) {
|
||||||
|
super(storageManager, computable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public T invoke() {
|
||||||
|
T result = super.invoke();
|
||||||
|
assert result != null : "compute() returned null";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class LockBasedNotNullLazyValue<T> extends LockBasedLazyValue<T> implements NotNullLazyValue<T> {
|
||||||
public LockBasedNotNullLazyValue(@NotNull LockBasedStorageManager storageManager, @NotNull Function0<? extends T> computable) {
|
public LockBasedNotNullLazyValue(@NotNull LockBasedStorageManager storageManager, @NotNull Function0<? extends T> computable) {
|
||||||
super(storageManager, computable);
|
super(storageManager, computable);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user