Skip to content

Commit 2fe739a

Browse files
committed
Clear pending events on stop()/destroy() call from notification callback
1 parent 733ec31 commit 2fe739a

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

kstatemachine/src/main/kotlin/ru/nsk/kstatemachine/PendingEventHandler.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,25 @@ fun StateMachine.throwingPendingEventHandler() = StateMachine.PendingEventHandle
1010
)
1111
}
1212

13-
fun StateMachine.queuePendingEventHandler(): StateMachine.PendingEventHandler = QueuePendingEventHandler(this)
13+
fun StateMachine.queuePendingEventHandler(): QueuePendingEventHandler = QueuePendingEventHandlerImpl(this)
1414

15-
internal class QueuePendingEventHandler(private val machine: StateMachine) : StateMachine.PendingEventHandler {
15+
interface QueuePendingEventHandler : StateMachine.PendingEventHandler {
16+
fun checkEmpty()
17+
fun nextEventAndArgument(): EventAndArgument<*>?
18+
fun clear()
19+
}
20+
21+
private class QueuePendingEventHandlerImpl(private val machine: StateMachine) : QueuePendingEventHandler {
1622
private val queue = ArrayDeque<EventAndArgument<*>>()
1723

18-
fun checkEmpty() = check(queue.isEmpty()) { "Event queue is not empty, internal error" }
24+
override fun checkEmpty() = check(queue.isEmpty()) { "Event queue is not empty, internal error" }
1925

2026
override fun onPendingEvent(pendingEvent: Event, argument: Any?) {
2127
machine.log { "$machine queued event $pendingEvent with argument $argument " }
2228
queue.add(EventAndArgument(pendingEvent, argument))
2329
}
2430

25-
fun nextEventAndArgument() = queue.removeFirstOrNull()
31+
override fun nextEventAndArgument() = queue.removeFirstOrNull()
2632

27-
fun clear() = queue.clear()
33+
override fun clear() = queue.clear()
2834
}

kstatemachine/src/main/kotlin/ru/nsk/kstatemachine/StateMachineImpl.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class StateMachineImpl(
2323
override val machineListeners: Collection<StateMachine.Listener> get() = _machineListeners
2424
override var logger: StateMachine.Logger = NullLogger
2525
override var ignoredEventHandler = StateMachine.IgnoredEventHandler { _, _ -> }
26-
override var pendingEventHandler = queuePendingEventHandler()
26+
override var pendingEventHandler: StateMachine.PendingEventHandler = queuePendingEventHandler()
2727
override var listenerExceptionHandler = StateMachine.ListenerExceptionHandler { throw it }
2828
private var _isDestroyed: Boolean = false
2929
override val isDestroyed get() = _isDestroyed
@@ -165,6 +165,10 @@ internal class StateMachineImpl(
165165
queue?.let {
166166
var eventAndArgument = it.nextEventAndArgument()
167167
while (eventAndArgument != null) {
168+
if (isDestroyed || !isRunning) { // if it happens while event processing
169+
it.clear()
170+
return result
171+
}
168172
process(eventAndArgument)
169173

170174
eventAndArgument = it.nextEventAndArgument()
@@ -251,9 +255,11 @@ internal class StateMachineImpl(
251255
}
252256

253257
override fun destroy(stop: Boolean) {
258+
if (_isDestroyed) return
254259
if (stop) stop()
255260
accept(CleanupVisitor())
256261
_isDestroyed = true
262+
log { "$this destroyed" }
257263
}
258264
}
259265

kstatemachine/src/test/kotlin/ru/nsk/kstatemachine/FinishedEventTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,25 @@ class FinishedEventTest : StringSpec({
8888
callbacks.onTriggeredTransition(ofType<FinishedDataEvent<Int>>())
8989
}
9090
}
91+
92+
"FinishedEvent in parallel child mode" {
93+
val callbacks = mockkCallbacks()
94+
95+
createStateMachine {
96+
initialState(childMode = ChildMode.PARALLEL) {
97+
state("state1") {
98+
setInitialState(finalState("state11"))
99+
}
100+
state("state2") {
101+
setInitialState(finalState("state21"))
102+
}
103+
transition<FinishedEvent> {
104+
callbacks.listen(this)
105+
}
106+
}
107+
}
108+
verifySequence {
109+
callbacks.onTriggeredTransition(ofType<FinishedEvent>())
110+
}
111+
}
91112
})

kstatemachine/src/test/kotlin/ru/nsk/kstatemachine/PendingEventHandlerTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,39 @@ class PendingEventHandlerTest : StringSpec({
9898
machine.processEvent(SwitchEvent)
9999
machine.isDestroyed shouldBe false
100100
}
101+
102+
"pending events are cleared on stop() from notification callback" {
103+
val machine = createStateMachine {
104+
val state2 = state("state2") {
105+
onEntry {
106+
machine.processEvent(SwitchEvent) shouldBe PENDING
107+
machine.processEvent(SwitchEvent) shouldBe PENDING
108+
machine.stop()
109+
}
110+
}
111+
initialState("state1") {
112+
transition<SwitchEvent>(targetState = state2)
113+
}
114+
}
115+
machine.processEvent(SwitchEvent)
116+
machine.isRunning shouldBe false
117+
machine.start()
118+
}
119+
120+
"pending events are cleared on destroy() from notification callback" {
121+
val machine = createStateMachine {
122+
val state2 = state("state2") {
123+
onEntry {
124+
machine.processEvent(SwitchEvent) shouldBe PENDING
125+
machine.processEvent(SwitchEvent) shouldBe PENDING
126+
machine.destroy(false)
127+
}
128+
}
129+
initialState("state1") {
130+
transition<SwitchEvent>(targetState = state2)
131+
}
132+
}
133+
machine.processEvent(SwitchEvent)
134+
machine.isDestroyed shouldBe true
135+
}
101136
})

0 commit comments

Comments
 (0)