Skip to content

Commit 6bb7721

Browse files
authored
fix: fixing broken runtime to runtime communication (#131)
1 parent 5e4a08f commit 6bb7721

10 files changed

Lines changed: 119 additions & 148 deletions

File tree

src/main/kotlin/at/ac/uibk/dps/cirrina/Runtime.kt

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import at.ac.uibk.dps.cirrina.spec.ContextVariable
1010
import at.ac.uibk.dps.cirrina.spec.Csml as CsmlSpec
1111
import at.ac.uibk.dps.cirrina.spec.Event
1212
import at.ac.uibk.dps.cirrina.spec.Instance
13-
import at.ac.uibk.dps.cirrina.spec.graph.EventGraph
13+
import at.ac.uibk.dps.cirrina.spec.Instantiate
1414
import com.codahale.metrics.MetricRegistry
1515
import com.codahale.metrics.Timer
1616
import jakarta.inject.Inject
1717
import java.net.URI
18+
import java.util.concurrent.ConcurrentHashMap
1819
import kotlin.collections.component1
1920
import kotlin.collections.component2
2021
import kotlin.time.measureTime
@@ -56,43 +57,50 @@ constructor(
5657
var version = 0L
5758
private set
5859

59-
private val graph = EventGraph()
60+
private val map = ConcurrentHashMap<String, StateMachine>()
6061

6162
val instances: List<StateMachine>
62-
get() = graph.instances
63+
get() = map.values.toList()
64+
65+
init {
66+
eventHandler.addSubscribers(csml.instances.map { it.name })
67+
68+
eventHandler.addDynamicSubscribers(
69+
csml.collaborativeStateMachine
70+
.getAllActions()
71+
.filterIsInstance<Instantiate>()
72+
.flatMap { it.instances }
73+
.map { it.prefix }
74+
)
75+
}
6376

6477
fun instantiate(
6578
instance: Instance,
6679
instanceData: List<ContextVariable> =
6780
instance.data.map { (k, v) -> ContextVariable(k, v.evaluate()) },
6881
) {
69-
val hierarchy =
70-
stateMachineFactory.createHierarchy(
82+
stateMachineFactory
83+
.createHierarchy(
7184
name = instance.name,
7285
specification = instance.stateMachine,
73-
instanceData = instanceData,
74-
instanceSubscription = instance.subscription,
86+
subscription = instance.subscription,
7587
instanceRegistry = this,
88+
data = instanceData,
7689
parent = null,
7790
runtimeExtent = extent,
7891
eventHandler = eventHandler,
7992
serviceImplementationSelector = serviceImplementationSelector,
8093
)
94+
.forEach {
95+
map.put(it.name, it)
96+
eventHandler.addPublishers(it.outputEvents)
97+
++version
8198

82-
hierarchy.forEach { machine -> graph.addInstance(machine) }
83-
84-
++version
85-
86-
val instances = graph.instances
87-
88-
eventHandler.addPublishers(graph.getOutgoing(instances).map { it.event })
89-
eventHandler.addSubscribers(instances.flatMap { it.subscriptions })
90-
91-
hierarchy.forEach { machine -> machine.start(parentContext = runtimeJob) }
99+
it.start(runtimeJob)
100+
}
92101
}
93102

94-
fun findStateMachineInstance(name: String): StateMachine? =
95-
graph.instances.filter { it.name == name }.firstOrNull()
103+
fun findStateMachineInstance(name: String): StateMachine? = map.get(name)
96104
}
97105

98106
private val instanceRegistry = InstanceRegistry(stateMachineFactory)
@@ -104,6 +112,7 @@ constructor(
104112
.onFailure { logger.warn { "variable '${k}' already exists or failed to create" } }
105113
}
106114
}
115+
107116
csml.instances.filter { it.name in run }.forEach { instanceRegistry.instantiate(it) }
108117
}
109118

src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/ActionCommand.kt

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,32 @@ class ActionExecutor(
3838
private val eventHandler: StateMachine.StateMachineEventHandler,
3939
private val coroutineScope: CoroutineScope,
4040
) {
41-
fun execute(spec: Action, scope: Scope): List<Action> =
42-
when (spec) {
43-
is Invoke -> executeInvoke(spec, scope)
44-
is Eval -> executeEval(spec, scope)
45-
is Emit -> executeEmit(spec, scope)
46-
is Timeout -> listOf(spec.triggers)
41+
fun execute(specification: Action, scope: Scope): List<Action> =
42+
when (specification) {
43+
is Invoke -> executeInvoke(specification, scope)
44+
is Eval -> executeEval(specification, scope)
45+
is Emit -> executeEmit(specification, scope)
46+
is Timeout -> listOf(specification.triggers)
4747
is Reset -> emptyList()
48-
is Match -> executeMatch(spec, scope)
49-
is Log -> executeLog(spec, scope)
50-
is Instantiate -> executeInstantiate(spec, scope)
51-
is Ctr -> executeCtr(spec, scope)
52-
else -> error("unknown action type '${spec::class.simpleName}'")
48+
is Match -> executeMatch(specification, scope)
49+
is Log -> executeLog(specification, scope)
50+
is Instantiate -> executeInstantiate(specification, scope)
51+
is Ctr -> executeCtr(specification, scope)
52+
else -> error("unknown action type '${specification::class.simpleName}'")
5353
}
5454

55-
private fun executeInvoke(spec: Invoke, scope: Scope): List<Action> {
55+
private fun executeInvoke(specification: Invoke, scope: Scope): List<Action> {
5656
val service =
57-
selector.select(spec.type, spec.mode)
58-
?: error("no service implementation found for type '${spec.type}'")
57+
selector.select(specification.type, specification.mode)
58+
?: error("no service implementation found for type '${specification.type}'")
5959

60-
val input = spec.input.map { it.evaluate(scope.extent) }
60+
val input = specification.input.map { it.evaluate(scope.extent) }
6161

6262
coroutineScope.launch {
6363
val delta = measureTime {
6464
runCatching { service.invoke(input) }
6565
.onSuccess { output ->
66-
spec.output.forEach { reference ->
66+
specification.output.forEach { reference ->
6767
output
6868
.firstOrNull { it.name == reference }
6969
?.let {
@@ -79,7 +79,7 @@ class ActionExecutor(
7979
}
8080
}
8181

82-
spec.emits.forEach { conditionalEvent ->
82+
specification.emits.forEach { conditionalEvent ->
8383
val shouldEmit = conditionalEvent.provided?.evaluate(scope.extent) ?: true
8484

8585
if (shouldEmit != false) {
@@ -100,21 +100,22 @@ class ActionExecutor(
100100
return emptyList()
101101
}
102102

103-
private fun executeEval(spec: Eval, scope: Scope): List<Action> {
104-
spec.expression.evaluate(scope.extent)
103+
private fun executeEval(specification: Eval, scope: Scope): List<Action> {
104+
specification.expression.evaluate(scope.extent)
105105
return emptyList()
106106
}
107107

108-
private fun executeEmit(spec: Emit, scope: Scope): List<Action> {
108+
private fun executeEmit(specification: Emit, scope: Scope): List<Action> {
109109
val emittedEvent =
110-
spec.event.run {
110+
specification.event.run {
111111
val evaluatedData =
112112
data.map { item ->
113113
if (item is LazyContextVariable) item.evaluate(scope.extent) else item
114114
}
115115

116116
copy(data = evaluatedData).let { eventWithData ->
117-
val target = spec.target?.let { source -> source.evaluate(scope.extent) as? String }
117+
val target =
118+
specification.target?.let { source -> source.evaluate(scope.extent) as? String }
118119

119120
if (target != null) eventWithData.copy(target = target) else eventWithData
120121
}
@@ -128,19 +129,19 @@ class ActionExecutor(
128129
return emptyList()
129130
}
130131

131-
private fun executeMatch(spec: Match, scope: Scope): List<Action> =
132-
spec.cases.entries
132+
private fun executeMatch(specification: Match, scope: Scope): List<Action> =
133+
specification.cases.entries
133134
.filter { (expression, _) -> expression.evaluate(scope.extent) == true }
134135
.flatMap { it.value }
135-
.ifEmpty { listOfNotNull(spec.default) }
136+
.ifEmpty { listOfNotNull(specification.default) }
136137

137138
private fun executeLog(spec: Log, scope: Scope): List<Action> {
138139
spec.message.evaluate(scope.extent).toString().also { logger.info(it) }
139140
return emptyList()
140141
}
141142

142-
private fun executeInstantiate(spec: Instantiate, scope: Scope): List<Action> {
143-
val instances = spec.instances.map { it.evaluate(scope.extent).getOrThrow() }
143+
private fun executeInstantiate(specification: Instantiate, scope: Scope): List<Action> {
144+
val instances = specification.instances.map { it.evaluate(scope.extent).getOrThrow() }
144145

145146
instances.forEach {
146147
val instanceData = it.data.map { (k, v) -> ContextVariable(k, v.evaluate(scope.extent)) }
@@ -151,20 +152,20 @@ class ActionExecutor(
151152
return emptyList()
152153
}
153154

154-
private fun executeCtr(spec: Ctr, scope: Scope): List<Action> {
155+
private fun executeCtr(specification: Ctr, scope: Scope): List<Action> {
155156
val tags =
156-
spec.tags?.entries?.joinToString(".") { (key, value) ->
157+
specification.tags?.entries?.joinToString(".") { (key, value) ->
157158
"$key=${value.evaluate(scope.extent)}"
158159
} ?: ""
159160

160161
val name =
161162
if (tags.isEmpty()) {
162-
spec.counter
163+
specification.counter
163164
} else {
164-
"${spec.counter}.$tags"
165+
"${specification.counter}.$tags"
165166
}
166167

167-
metricRegistry.counter(name).inc(spec.by)
168+
metricRegistry.counter(name).inc(specification.by)
168169

169170
return emptyList()
170171
}

src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/EventHandler.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ class EventHandler(private val handler: PropagationHandler) : AutoCloseable {
6161
}
6262
}
6363

64+
fun addDynamicSubscribers(instancePrefixes: List<String>) {
65+
instancePrefixes.forEach { prefix ->
66+
val key = "events/$prefix\$*/**"
67+
subscribers.computeIfAbsent(key) { createSubscriber(key, subscriberDetection = true) }
68+
}
69+
}
70+
6471
fun emit(event: Event) {
6572
if (event.channel != Csml.EventChannel.EXTERNAL) return
6673

src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/StateMachine.kt

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ import at.ac.uibk.dps.cirrina.spec.StateMachine as StateMachineSpecification
1414
import at.ac.uibk.dps.cirrina.spec.Timeout
1515
import com.codahale.metrics.MetricRegistry
1616
import com.codahale.metrics.Timer
17-
import com.google.common.cache.CacheBuilder
18-
import com.google.common.cache.CacheLoader
19-
import com.google.common.cache.LoadingCache
2017
import dagger.assisted.Assisted
2118
import dagger.assisted.AssistedFactory
2219
import dagger.assisted.AssistedInject
@@ -56,10 +53,10 @@ class StateMachine
5653
internal constructor(
5754
@Assisted val name: String,
5855
@Assisted val specification: StateMachineSpecification,
59-
@Assisted val instanceData: List<ContextVariable>,
60-
@Assisted val instanceSubscription: Regex,
56+
@Assisted val subscription: Regex,
6157
@Assisted override val instanceRegistry: Runtime.InstanceRegistry,
6258
@Assisted private val parent: StateMachine?,
59+
@Assisted data: List<ContextVariable>,
6360
@Assisted runtimeExtent: Extent,
6461
@Assisted eventHandler: EventHandler,
6562
@Assisted serviceImplementationSelector: ServiceImplementationSelector,
@@ -69,21 +66,7 @@ internal constructor(
6966
) : Scope {
7067
override val extent: Extent
7168

72-
private val subscriptionCache: LoadingCache<Long, List<String>> =
73-
CacheBuilder.newBuilder()
74-
.maximumSize(1)
75-
.build(
76-
object : CacheLoader<Long, List<String>>() {
77-
override fun load(key: Long): List<String> {
78-
return instanceRegistry.instances
79-
.filter { instanceSubscription.matches(it.name) }
80-
.map { it.name }
81-
}
82-
}
83-
)
84-
85-
val subscriptions: List<String>
86-
get() = subscriptionCache.get(instanceRegistry.version)
69+
var outputEvents = specification.outputEvents.map { it.copy(source = name) }
8770

8871
var nested: List<String> by
8972
Delegates.vetoable(emptyList()) { _, old, _ ->
@@ -143,7 +126,7 @@ internal constructor(
143126
val transientContext =
144127
Context.empty().apply {
145128
specification.transient.forEach { (k, v) -> create(k, v.evaluate()) }
146-
instanceData.forEach { create(it.name, it.value) }
129+
data.forEach { create(it.name, it.value) }
147130
}
148131

149132
val eventContext = Context.empty()
@@ -239,7 +222,7 @@ internal constructor(
239222

240223
private fun Event.isValid(): Boolean {
241224
if (target.isNotEmpty() && target != name) return false
242-
if (channel == EventChannel.EXTERNAL && source !in subscriptions) return false
225+
if (channel == EventChannel.EXTERNAL && !subscription.matches(source)) return false
243226

244227
return true
245228
}
@@ -370,9 +353,9 @@ internal constructor(
370353
fun create(
371354
name: String,
372355
specification: StateMachineSpecification,
373-
instanceData: List<ContextVariable>,
374-
instanceSubscription: Regex,
375-
instanceRegistry: Runtime.InstanceRegistry,
356+
subscription: Regex,
357+
registry: Runtime.InstanceRegistry,
358+
data: List<ContextVariable>,
376359
parent: StateMachine?,
377360
runtimeExtent: Extent,
378361
eventHandler: EventHandler,
@@ -384,9 +367,9 @@ internal constructor(
384367
fun Factory.createHierarchy(
385368
name: String,
386369
specification: StateMachineSpecification,
387-
instanceData: List<ContextVariable>,
388-
instanceSubscription: Regex,
370+
subscription: Regex,
389371
instanceRegistry: Runtime.InstanceRegistry,
372+
data: List<ContextVariable>,
390373
parent: StateMachine?,
391374
runtimeExtent: Extent,
392375
eventHandler: EventHandler,
@@ -395,9 +378,9 @@ fun Factory.createHierarchy(
395378
create(
396379
name,
397380
specification,
398-
instanceData,
399-
instanceSubscription,
381+
subscription,
400382
instanceRegistry,
383+
data,
401384
parent,
402385
runtimeExtent,
403386
eventHandler,
@@ -409,9 +392,9 @@ fun Factory.createHierarchy(
409392
createHierarchy(
410393
"${current.name}@${nested.name}",
411394
nested,
412-
instanceData,
413-
instanceSubscription,
395+
subscription,
414396
instanceRegistry,
397+
data,
415398
current,
416399
runtimeExtent,
417400
eventHandler,

src/main/kotlin/at/ac/uibk/dps/cirrina/spec/Action.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ class Log internal constructor(description: LogDescription) : Action {
9797
class Instantiate internal constructor(csml: Csml, description: InstantiateDescription) : Action {
9898
val instances =
9999
description.instances.map {
100-
DynamicInstance.create(csml, it.value, Expression(it.key)).getOrThrow()
100+
val dynamicName = it.key
101+
DynamicInstance.create(csml, it.value, dynamicName.first, Expression(dynamicName.second))
102+
.getOrThrow()
101103
}
102104
}
103105

src/main/kotlin/at/ac/uibk/dps/cirrina/spec/CollaborativeStateMachine.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ private constructor(csml: Csml, description: CollaborativeStateMachineDescriptio
1717
fun getStateMachine(name: String) =
1818
stateMachines[name] ?: error("state machine class '${name}' not found")
1919

20+
fun getAllActions(): List<Action> {
21+
fun StateMachine.allStateMachines(): List<StateMachine> =
22+
listOf(this) + nested.flatMap { it.allStateMachines() }
23+
24+
return stateMachines.values
25+
.flatMap { it.allStateMachines() }
26+
.flatMap { sm ->
27+
val stateActions =
28+
sm.vertexSet().flatMap { state ->
29+
listOf(state.entry, state.exit, state.during, state.after).flatMap { it.vertexSet() }
30+
}
31+
32+
val transitionActions = sm.edgeSet().flatMap { transition -> transition.yields.vertexSet() }
33+
34+
stateActions + transitionActions
35+
}
36+
}
37+
2038
companion object {
2139
fun create(csml: Csml, description: CollaborativeStateMachineDescription) = runCatching {
2240
CollaborativeStateMachine(csml, description)

0 commit comments

Comments
 (0)