From 8fdff23b5f97bbecab3e7ebfe81858de17ecbf45 Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Sun, 3 Dec 2023 13:10:19 -0500 Subject: [PATCH] Switch to thread safe regex match caching (#4) --- .../kotlin/xyz/wingio/syntakts/Syntakts.kt | 7 +-- .../wingio/syntakts/util/SynchronizedCache.kt | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/util/SynchronizedCache.kt diff --git a/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/Syntakts.kt b/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/Syntakts.kt index fd73411..495ad4a 100644 --- a/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/Syntakts.kt +++ b/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/Syntakts.kt @@ -10,6 +10,7 @@ import xyz.wingio.syntakts.parser.addTextRule import xyz.wingio.syntakts.style.StyledTextBuilder import xyz.wingio.syntakts.util.Logger import xyz.wingio.syntakts.util.LoggerImpl +import xyz.wingio.syntakts.util.SynchronizedCache import xyz.wingio.syntakts.util.Stack import xyz.wingio.syntakts.util.firstMapOrNull @@ -237,7 +238,7 @@ public class Syntakts internal constructor( if(debugOptions.enableLogging) debugOptions.logger.debug(message) } - private val cache: MutableMap = mutableMapOf() + private val cache: SynchronizedCache = SynchronizedCache() /** * Parse an input using the specified [rules] @@ -273,11 +274,11 @@ public class Syntakts internal constructor( rules.firstMapOrNull { rule -> val key = "${rule.regex}-$inspectionSource-$lastCapture" - val matchResult = if(cache.containsKey(key)) + val matchResult = if(cache.hasKey(key)) cache[key] else rule.match(inspectionSource, lastCapture).apply { - if(cache.size > 10_000) cache.remove(cache.keys.first()) + if(cache.size > 10_000) cache.removeFirst() cache[key] = this } diff --git a/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/util/SynchronizedCache.kt b/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/util/SynchronizedCache.kt new file mode 100644 index 0000000..72c0a26 --- /dev/null +++ b/syntakts-core/src/commonMain/kotlin/xyz/wingio/syntakts/util/SynchronizedCache.kt @@ -0,0 +1,53 @@ +package xyz.wingio.syntakts.util + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + * Thread safe cache store, all writes are restricted with a [Mutex] to prevent [ConcurrentModificationException]s + */ +public class SynchronizedCache { + + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate) + private val mutex: Mutex = Mutex(false) + private val cache: MutableMap = mutableMapOf() + + /** + * Number of cached entities + */ + public val size: Int get() = cache.size + + public operator fun set(key: K, value: V?) { + scope.launch { + mutex.withLock { + cache[key] = value + } + } + } + + public operator fun get(key: K): V? { + return cache[key] + } + + /** + * Returns true if this cache already contains an element with the given [key] + */ + public fun hasKey(key: K): Boolean { + return cache.containsKey(key) + } + + /** + * Removes the first element of this cache + */ + public fun removeFirst() { + scope.launch { + mutex.withLock { + cache.remove(cache.keys.firstOrNull()) + } + } + } + +} \ No newline at end of file