From b3073dbd2df066a45a04b5f892fa3344c225fad0 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Tue, 1 Sep 2015 18:46:27 +0300 Subject: [PATCH] Provide lazy implementation with an external object to synchronize on. --- js/js.libraries/src/core/kotlin.kt | 6 ++++++ .../stdlib/src/kotlin/properties/Delegation.kt | 2 +- libraries/stdlib/src/kotlin/util/Lazy.kt | 7 +++++-- libraries/stdlib/src/kotlin/util/LazyJVM.kt | 17 +++++++++++++++++ .../delegation/lazy/LazyValuesTest.kt | 17 +++++++++++++++++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/js/js.libraries/src/core/kotlin.kt b/js/js.libraries/src/core/kotlin.kt index a7d14df3900..8dd1ae00417 100644 --- a/js/js.libraries/src/core/kotlin.kt +++ b/js/js.libraries/src/core/kotlin.kt @@ -62,6 +62,12 @@ public fun lazy(initializer: () -> T): Lazy = UnsafeLazyImpl(initializer) * The [mode] parameter is ignored. */ public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy = UnsafeLazyImpl(initializer) +/** + * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]. + * + * The [lock] parameter is ignored. + */ +public fun lazy(lock: Any?, initializer: () -> T): Lazy = UnsafeLazyImpl(initializer) private fun arrayCopyResize(source: dynamic, newSize: Int, defaultValue: Any?): dynamic { val result = source.slice(0, newSize) diff --git a/libraries/stdlib/src/kotlin/properties/Delegation.kt b/libraries/stdlib/src/kotlin/properties/Delegation.kt index 2e60abc404a..77ae9720923 100644 --- a/libraries/stdlib/src/kotlin/properties/Delegation.kt +++ b/libraries/stdlib/src/kotlin/properties/Delegation.kt @@ -30,7 +30,7 @@ public object Delegates { * the property delegate object itself is used as a lock. * @param initializer the function that returns the value of the property. */ - deprecated("Use lazy {} instead in kotlin package") + deprecated("Use lazy(lock) {} instead in kotlin package", ReplaceWith("lazy(lock, initializer)")) public fun blockingLazy(lock: Any?, initializer: () -> T): ReadOnlyProperty = BlockingLazyVal(lock, initializer) /** diff --git a/libraries/stdlib/src/kotlin/util/Lazy.kt b/libraries/stdlib/src/kotlin/util/Lazy.kt index b155ed2bb54..20028341b22 100644 --- a/libraries/stdlib/src/kotlin/util/Lazy.kt +++ b/libraries/stdlib/src/kotlin/util/Lazy.kt @@ -51,9 +51,11 @@ public enum class LazyThreadSafetyMode { private object UNINITIALIZED_VALUE -private class LazyImpl(initializer: () -> T) : Lazy(), Serializable { +private open class LazyImpl(initializer: () -> T) : Lazy(), Serializable { private var initializer: (() -> T)? = initializer private volatile var _value: Any? = UNINITIALIZED_VALUE + protected open val lock: Any + get() = this override val value: T get() { @@ -62,7 +64,7 @@ private class LazyImpl(initializer: () -> T) : Lazy(), Serializable { return _v1 as T } - return synchronized(this) { + return synchronized(lock) { val _v2 = _value if (_v2 !== UNINITIALIZED_VALUE) { _v2 as T @@ -83,6 +85,7 @@ private class LazyImpl(initializer: () -> T) : Lazy(), Serializable { private fun writeReplace(): Any = InitializedLazyImpl(value) } +private class ExternallySynchronizedLazyImpl(override val lock: Any, initializer: () -> T): LazyImpl(initializer) private class UnsafeLazyImpl(initializer: () -> T) : Lazy(), Serializable { private var initializer: (() -> T)? = initializer diff --git a/libraries/stdlib/src/kotlin/util/LazyJVM.kt b/libraries/stdlib/src/kotlin/util/LazyJVM.kt index 804590a0699..3f2a5a50ad1 100644 --- a/libraries/stdlib/src/kotlin/util/LazyJVM.kt +++ b/libraries/stdlib/src/kotlin/util/LazyJVM.kt @@ -27,3 +27,20 @@ public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy = LazyThreadSafetyMode.SYNCHRONIZED -> LazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } + +/** + * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer] + * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED]. + * + * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access. + * + * The returned instance uses the specified [lock] object to synchronize on. + * When the [lock] is not specified the instance uses itself to synchronize on, + * in this case do not synchronize from external code on the returned instance as it may cause accidental deadlock. + * Also this behavior can be changed in the future. + */ +public fun lazy(lock: Any?, initializer: () -> T): Lazy = + if (lock != null) + ExternallySynchronizedLazyImpl(lock, initializer) + else + LazyImpl(initializer) \ No newline at end of file diff --git a/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt b/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt index 68ecd0f3125..f814632f153 100644 --- a/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt +++ b/libraries/stdlib/test/properties/delegation/lazy/LazyValuesTest.kt @@ -17,6 +17,23 @@ class LazyValTest { } } +class SynchronizedLazyValTest { + volatile var result = 0 + val a by lazy(this) { + ++result + } + + test fun doTest() { + synchronized(this) { + // thread { a } // not available in js // TODO: Make this test JVM-only + result = 1 + a + } + assertTrue(a == 2, "fail: initializer should be invoked only once") + assertTrue(result == 2, "fail result should be incremented after test") + } +} + class UnsafeLazyValTest { var result = 0 val a by lazy(LazyThreadSafetyMode.NONE) {