@@ -18,10 +18,12 @@ package org.modelix.model
18
18
19
19
import gnu.trove.map.TLongObjectMap
20
20
import gnu.trove.map.hash.TLongObjectHashMap
21
+ import kotlinx.coroutines.CompletableDeferred
21
22
import kotlinx.coroutines.CoroutineScope
23
+ import kotlinx.coroutines.Deferred
22
24
import kotlinx.coroutines.Dispatchers
23
- import kotlinx.coroutines.Job
24
- import kotlinx.coroutines.launch
25
+ import kotlinx.coroutines.async
26
+ import kotlinx.coroutines.cancel
25
27
import org.modelix.model.api.ConceptReference
26
28
import org.modelix.model.api.IBranch
27
29
import org.modelix.model.api.IConcept
@@ -42,52 +44,66 @@ import org.modelix.model.lazy.NonCachingObjectStore
42
44
import org.modelix.model.persistent.CPHamtNode
43
45
import org.modelix.model.persistent.CPNode
44
46
import org.modelix.model.persistent.CPNodeRef
47
+ import java.util.Collections
45
48
import kotlin.system.measureTimeMillis
46
49
import kotlin.time.Duration.Companion.milliseconds
47
50
import kotlin.time.DurationUnit
48
51
49
52
private val LOG = mu.KotlinLogging .logger { }
50
53
51
- class InMemoryModelLoader (val model : IncrementalInMemoryModel ) {
52
- private val coroutineScope = CoroutineScope (Dispatchers .IO )
53
- private var modelLoadingJob: Job ? = null
54
-
55
- /* *
56
- * Should be called repeatedly by a readiness probe until it returns true.
57
- *
58
- * @return true if the model is done loading
59
- */
60
- @Synchronized
61
- fun loadModelAsync (tree : CLTree ): Boolean {
62
- if (model.getLoadedModel()?.loadedMapRef?.getHash() == tree.nodesMap!! .hash) return true
63
- if (modelLoadingJob?.isActive != true ) {
64
- modelLoadingJob = coroutineScope.launch {
65
- try {
66
- model.getModel(tree)
67
- } catch (ex: Throwable ) {
68
- LOG .error(ex) { " Failed loading model ${tree.hash} " }
54
+ class InMemoryModelLoader (val incrementalModel : IncrementalInMemoryModel , val coroutineScope : CoroutineScope ) {
55
+ private val treeHash2modelLoadJob = Collections .synchronizedMap(HashMap <String , Deferred <InMemoryModel >>())
56
+
57
+ fun getModel (tree : CLTree ): Deferred <InMemoryModel > {
58
+ val loadedModel = incrementalModel.getLoadedModel()
59
+ if (loadedModel != null && loadedModel.loadedMapRef.getHash() == tree.nodesMap?.hash) return CompletableDeferred (loadedModel)
60
+
61
+ return synchronized(treeHash2modelLoadJob) {
62
+ val activeJobs = treeHash2modelLoadJob.values.toList()
63
+ val loadJob = treeHash2modelLoadJob.getOrPut(tree.hash) {
64
+ coroutineScope.async {
65
+ // There should only be one active loading job, because we want to reuse as much data as possible
66
+ // from a previously loaded model, so we have to wait for its completion.
67
+ // This also limits the number of thread used from the IO dispatcher.
68
+ activeJobs.forEach { it.join() }
69
+
70
+ // This is a long-running method that should be executed only once for a new tree version.
71
+ // It's executed on the IO dispatcher, because it's not a suspendable function and blocks
72
+ // the thread.
73
+ incrementalModel.getModel(tree)
69
74
}
70
75
}
76
+
77
+ // cleanup finished jobs
78
+ treeHash2modelLoadJob - = treeHash2modelLoadJob.entries.filter { ! it.value.isActive }.map { it.key }.toSet()
79
+
80
+ loadJob
71
81
}
72
- return false
73
82
}
74
83
}
75
84
76
85
class InMemoryModels {
77
- private val models = HashMap <String , InMemoryModelLoader >()
86
+ private val coroutineScope = CoroutineScope (Dispatchers .IO )
87
+ private val branchId2modelLoader = Collections .synchronizedMap(HashMap <String , InMemoryModelLoader >())
78
88
79
- @Synchronized
80
- fun getModel (id : String ) = models.getOrPut(id) { InMemoryModelLoader (IncrementalInMemoryModel ()) }
89
+ fun dispose () {
90
+ coroutineScope.cancel(" disposed" )
91
+ }
81
92
82
- fun getModel (tree : CLTree ) = getModel(tree.getId()).model.getModel(tree)
93
+ private fun getModelLoader (branchId : String ): InMemoryModelLoader {
94
+ return synchronized(branchId2modelLoader) {
95
+ branchId2modelLoader.getOrPut(branchId) { InMemoryModelLoader (IncrementalInMemoryModel (), coroutineScope) }
96
+ }
97
+ }
83
98
84
- fun loadModelAsync (tree : CLTree ) = getModel(tree.getId()).loadModelAsync(tree)
99
+ fun getModel (tree : CLTree ): Deferred <InMemoryModel > {
100
+ return getModelLoader(tree.getId()).getModel(tree)
101
+ }
85
102
}
86
103
87
104
class IncrementalInMemoryModel {
88
105
private var lastModel: InMemoryModel ? = null
89
106
90
- @Synchronized
91
107
fun getModel (tree : CLTree ): InMemoryModel {
92
108
val reusable = lastModel?.takeIf { it.branchId == tree.getId() }
93
109
val newModel = if (reusable == null ) {
0 commit comments