From d781ca0f75fd1caf54dcff4cd8cc491e35a7c096 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Mon, 6 Jul 2020 12:37:30 +0200 Subject: [PATCH 1/2] add test for cached unificator thread safety --- .../unibo/tuprolog/unify/TestThreadSafety.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 unify/src/jvmTest/kotlin/it/unibo/tuprolog/unify/TestThreadSafety.kt diff --git a/unify/src/jvmTest/kotlin/it/unibo/tuprolog/unify/TestThreadSafety.kt b/unify/src/jvmTest/kotlin/it/unibo/tuprolog/unify/TestThreadSafety.kt new file mode 100644 index 000000000..b933cf2b9 --- /dev/null +++ b/unify/src/jvmTest/kotlin/it/unibo/tuprolog/unify/TestThreadSafety.kt @@ -0,0 +1,31 @@ +package it.unibo.tuprolog.unify + +import it.unibo.tuprolog.core.Substitution +import it.unibo.tuprolog.unify.testutils.UnificatorUtils.successfulUnifications +import java.util.* +import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit +import kotlin.test.Test +import kotlin.test.assertTrue + +class TestThreadSafety { + @Test + fun testCachedUnificatorThreadSafety() { + val executors = Executors.newFixedThreadPool(8) + val results: MutableList> = Collections.synchronizedList(LinkedList()) + val unificator = Unificator.cached(Unificator.strict()) + for (i in 1 .. 100) { + for (equation in successfulUnifications.keys) { + results.add(executors.submit { + unificator.mgu(equation.lhs, equation.rhs) + }) + } + } + executors.shutdown() + executors.awaitTermination(1, TimeUnit.MINUTES) + for (res in results) { + assertTrue { res.get() is Substitution.Unifier } + } + } +} \ No newline at end of file From 020ccb2e541dc5c12e4e7a7e1107306e33763a94 Mon Sep 17 00:00:00 2001 From: Giovanni Date: Mon, 6 Jul 2020 15:05:25 +0200 Subject: [PATCH 2/2] make SimpleLRUCache thread safe --- .../it/unibo/tuprolog/utils/impl/SimpleLRUCache.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/commonMain/kotlin/it/unibo/tuprolog/utils/impl/SimpleLRUCache.kt b/core/src/commonMain/kotlin/it/unibo/tuprolog/utils/impl/SimpleLRUCache.kt index 8df04ab43..c284beaf9 100644 --- a/core/src/commonMain/kotlin/it/unibo/tuprolog/utils/impl/SimpleLRUCache.kt +++ b/core/src/commonMain/kotlin/it/unibo/tuprolog/utils/impl/SimpleLRUCache.kt @@ -2,6 +2,8 @@ package it.unibo.tuprolog.utils.impl import it.unibo.tuprolog.utils.Cache import it.unibo.tuprolog.utils.Optional +import it.unibo.tuprolog.utils.buffered +import kotlin.jvm.Synchronized internal class SimpleLRUCache(override val capacity: Int) : Cache { @@ -11,6 +13,7 @@ internal class SimpleLRUCache(override val capacity: Int) : Cache { private val cache = LinkedHashMap() + @Synchronized override fun set(key: K, value: V): Optional> { val evicted = removeLeastRecentIfNecessary() cache[key] = value @@ -31,6 +34,7 @@ internal class SimpleLRUCache(override val capacity: Int) : Cache { } } + @Synchronized override fun get(key: K): Optional = if (cache.containsKey(key)) { Optional.of(cache[key]) @@ -38,9 +42,11 @@ internal class SimpleLRUCache(override val capacity: Int) : Cache { Optional.none() } + @Synchronized override fun toMap(): Map = cache.toMap() + @Synchronized override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || this::class != other::class) return false @@ -53,20 +59,24 @@ internal class SimpleLRUCache(override val capacity: Int) : Cache { return true } + @Synchronized override fun hashCode(): Int { var result = capacity result = 31 * result + cache.hashCode() return result } + @Synchronized override fun toSequence(): Sequence> { - return cache.entries.asSequence().map { it.toPair() } + return cache.entries.asSequence().map { it.toPair() }.buffered() } + @Synchronized override fun toString(): String { return "SimpleLRUCache(${toSequence().map { "${it.first} = ${it.second}" }.joinToString(", ")})" } override val size: Int + @Synchronized get() = cache.size } \ No newline at end of file