Skip to content

Commit 0d5dfc8

Browse files
committed
test(model-server): IStoreClient transactions
1 parent 7409913 commit 0d5dfc8

File tree

7 files changed

+176
-9
lines changed

7 files changed

+176
-9
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.gradle/
22
/build/
33
/*/build/
4+
/*/ignite/
45
.DS_Store
56
.gradletasknamecache
67
.idea/

model-server/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ dependencies {
6262
testImplementation(libs.junit)
6363
testImplementation(libs.cucumber.java)
6464
testImplementation(libs.ktor.server.test.host)
65+
testImplementation(libs.kotlin.coroutines.test)
6566
testImplementation(kotlin("test"))
6667
testImplementation(project(":modelql-untyped"))
6768
}

model-server/src/main/kotlin/org/modelix/model/server/store/IStoreClient.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import kotlinx.coroutines.withTimeoutOrNull
2121
import org.modelix.model.IKeyListener
2222
import kotlin.time.Duration.Companion.seconds
2323

24-
interface IStoreClient {
24+
interface IStoreClient : AutoCloseable {
2525
operator fun get(key: String): String?
2626
fun getAll(keys: List<String>): List<String?>
2727
fun getAll(keys: Set<String>): Map<String, String?>
28+
fun getAll(): Map<String, String?>
2829
fun put(key: String, value: String?, silent: Boolean = false)
2930
fun putAll(entries: Map<String, String?>, silent: Boolean = false)
3031
fun listen(key: String, listener: IKeyListener)

model-server/src/main/kotlin/org/modelix/model/server/store/IgniteStoreClient.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import java.util.*
2626
import java.util.concurrent.Executors
2727
import java.util.stream.Collectors
2828

29-
class IgniteStoreClient(jdbcConfFile: File?) : IStoreClient {
29+
class IgniteStoreClient(jdbcConfFile: File? = null, inmemory: Boolean = false) : IStoreClient, AutoCloseable {
3030
private val ignite: Ignite
3131
private val cache: IgniteCache<String, String?>
3232
private val timer = Executors.newScheduledThreadPool(1)
@@ -63,7 +63,7 @@ class IgniteStoreClient(jdbcConfFile: File?) : IStoreClient {
6363
)
6464
}
6565
}
66-
ignite = Ignition.start(javaClass.getResource("ignite.xml"))
66+
ignite = Ignition.start(javaClass.getResource(if (inmemory) "ignite-inmemory.xml" else "ignite.xml"))
6767
cache = ignite.getOrCreateCache("model")
6868
// timer.scheduleAtFixedRate(() -> {
6969
// System.out.println("stats: " + cache.metrics());
@@ -83,6 +83,10 @@ class IgniteStoreClient(jdbcConfFile: File?) : IStoreClient {
8383
return cache.getAll(keys)
8484
}
8585

86+
override fun getAll(): Map<String, String?> {
87+
return cache.associate { it.key to it.value }
88+
}
89+
8690
override fun put(key: String, value: String?, silent: Boolean) {
8791
putAll(Collections.singletonMap(key, value), silent)
8892
}
@@ -151,4 +155,8 @@ class IgniteStoreClient(jdbcConfFile: File?) : IStoreClient {
151155
fun dispose() {
152156
ignite.close()
153157
}
158+
159+
override fun close() {
160+
dispose()
161+
}
154162
}

model-server/src/main/kotlin/org/modelix/model/server/store/InMemoryStoreClient.kt

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import java.io.BufferedReader
2020
import java.io.FileReader
2121
import java.io.FileWriter
2222
import java.io.IOException
23-
import java.util.*
23+
import kotlin.collections.HashMap
2424

2525
fun generateId(idStr: String?): Long {
2626
return try {
@@ -41,26 +41,32 @@ class InMemoryStoreClient : IStoreClient {
4141
}
4242

4343
private val values: MutableMap<String, String?> = HashMap()
44+
private var transactionValues: MutableMap<String, String?>? = null
4445
private val listeners: MutableMap<String?, MutableSet<IKeyListener>> = HashMap()
4546

4647
@Synchronized
4748
override fun get(key: String): String? {
48-
return values[key]
49+
return if (transactionValues?.contains(key) == true) transactionValues!![key] else values[key]
4950
}
5051

5152
@Synchronized
5253
override fun getAll(keys: List<String>): List<String?> {
53-
return keys.map { values[it] }
54+
return keys.map { get(it) }
55+
}
56+
57+
@Synchronized
58+
override fun getAll(): Map<String, String?> {
59+
return values + (transactionValues ?: emptyMap())
5460
}
5561

5662
@Synchronized
5763
override fun getAll(keys: Set<String>): Map<String, String?> {
58-
return keys.associateWith { values[it] }
64+
return keys.associateWith { get(it) }
5965
}
6066

6167
@Synchronized
6268
override fun put(key: String, value: String?, silent: Boolean) {
63-
values[key] = value
69+
(transactionValues ?: values)[key] = value
6470
if (!silent) {
6571
listeners[key]?.toList()?.forEach {
6672
try {
@@ -124,6 +130,20 @@ class InMemoryStoreClient : IStoreClient {
124130

125131
@Synchronized
126132
override fun <T> runTransaction(body: () -> T): T {
127-
return body()
133+
if (transactionValues == null) {
134+
try {
135+
transactionValues = HashMap()
136+
val result = body()
137+
values.putAll(transactionValues!!)
138+
return result
139+
} finally {
140+
transactionValues = null
141+
}
142+
} else {
143+
return body()
144+
}
145+
}
146+
147+
override fun close() {
128148
}
129149
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
Licensed to the Apache Software Foundation (ASF) under one or more
5+
contributor license agreements. See the NOTICE file distributed with
6+
this work for additional information regarding copyright ownership.
7+
The ASF licenses this file to You under the Apache License, Version 2.0
8+
(the "License"); you may not use this file except in compliance with
9+
the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
-->
19+
20+
<beans xmlns="http://www.springframework.org/schema/beans"
21+
xmlns:util="http://www.springframework.org/schema/util"
22+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23+
xsi:schemaLocation="http://www.springframework.org/schema/beans
24+
http://www.springframework.org/schema/beans/spring-beans.xsd
25+
http://www.springframework.org/schema/util
26+
http://www.springframework.org/schema/util/spring-util.xsd">
27+
28+
<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
29+
<property name="consistentId" value="inmemory"/>
30+
<property name="cacheConfiguration">
31+
<list>
32+
<bean class="org.apache.ignite.configuration.CacheConfiguration">
33+
<property name="name" value="model"/>
34+
<property name="atomicityMode" value="TRANSACTIONAL"/>
35+
<property name="backups" value="0"/>
36+
<property name="statisticsEnabled" value="true"/>
37+
</bean>
38+
</list>
39+
</property>
40+
</bean>
41+
</beans>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2023.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.modelix.model.server
18+
19+
import kotlinx.coroutines.launch
20+
import kotlinx.coroutines.test.runTest
21+
import org.modelix.model.server.store.IStoreClient
22+
import org.modelix.model.server.store.IgniteStoreClient
23+
import org.modelix.model.server.store.InMemoryStoreClient
24+
import kotlin.random.Random
25+
import kotlin.test.AfterTest
26+
import kotlin.test.Test
27+
import kotlin.test.assertEquals
28+
import kotlin.test.assertFailsWith
29+
import kotlin.test.assertNull
30+
31+
class MapBasedStoreClientTest : StoreClientTest(InMemoryStoreClient())
32+
class IgniteStoreClientTest : StoreClientTest(IgniteStoreClient(inmemory = true))
33+
34+
abstract class StoreClientTest(val store: IStoreClient) {
35+
36+
@AfterTest
37+
fun cleanup() {
38+
store.close()
39+
}
40+
41+
@Test
42+
fun `transaction can be started from inside a transaction`() {
43+
store.runTransaction {
44+
store.runTransaction {
45+
store.put("abc", "def")
46+
}
47+
}
48+
}
49+
50+
@Test
51+
fun `allow put without transaction`() {
52+
val key = "ljnrdlfkesmgf"
53+
val value = "izujztdrsew"
54+
assertNull(store.get(key))
55+
store.put(key, value)
56+
assertEquals(value, store.get(key))
57+
}
58+
59+
@Test
60+
fun `transactions are isolated`() = runTest {
61+
val key = "kjhndsweret"
62+
repeat(2) {
63+
val rand = Random(it)
64+
launch {
65+
store.runTransaction {
66+
repeat(10) {
67+
val value = rand.nextInt().toString()
68+
store.put(key, value)
69+
assertEquals(value, store.get(key))
70+
Thread.sleep(rand.nextLong(5, 10))
71+
assertEquals(value, store.get(key))
72+
}
73+
}
74+
}
75+
}
76+
}
77+
78+
@Test
79+
fun `transactions are atomic`() = runTest {
80+
val key = "ioudgbnr"
81+
val value1 = "a"
82+
val value2 = "b"
83+
84+
store.put(key, value1)
85+
assertEquals(value1, store.get(key))
86+
assertFailsWith(NullPointerException::class) {
87+
store.runTransaction {
88+
store.put(key, value2)
89+
assertEquals(value2, store.get(key))
90+
throw NullPointerException()
91+
}
92+
}
93+
assertEquals(value1, store.get(key)) // failed transaction should be rolled back
94+
}
95+
}

0 commit comments

Comments
 (0)