|
@@ -4,10 +4,11 @@ import inn.ocsf.bee.freigeld.core.calc.CoinStrategy
|
|
|
import inn.ocsf.bee.freigeld.core.calc.CoinUtils
|
|
|
import inn.ocsf.bee.freigeld.core.model.*
|
|
|
import inn.ocsf.bee.freigeld.core.service.TicketService
|
|
|
-import kotlinx.coroutines.*
|
|
|
+import kotlinx.coroutines.CompletableDeferred
|
|
|
import org.slf4j.LoggerFactory
|
|
|
import org.springframework.beans.factory.annotation.Autowired
|
|
|
import org.springframework.stereotype.Service
|
|
|
+import org.springframework.transaction.annotation.Transactional
|
|
|
import java.util.*
|
|
|
import java.util.concurrent.CompletableFuture
|
|
|
import javax.annotation.PostConstruct
|
|
@@ -26,23 +27,24 @@ class CentralBankQueueLevel : CentralBankAccountLevel() {
|
|
|
@Inject
|
|
|
private lateinit var ticketService: TicketService
|
|
|
|
|
|
- val globalFutureMap = mutableMapOf<UUID, CompletableDeferred<Any>>()
|
|
|
+ private val globalFutureMap = mutableMapOf<UUID, CompletableDeferred<Ticket>>()
|
|
|
|
|
|
private val log = LoggerFactory.getLogger(javaClass)
|
|
|
|
|
|
- override fun exchange(to: BankAccount, from: BankAccount, amount: Long): CompletableFuture<Any> {
|
|
|
+ override fun exchange(to: BankAccount, from: BankAccount, amount: Long): CompletableFuture<Ticket> {
|
|
|
val e = ExchangeStartEvent(UUID.randomUUID(), to, from, amount)
|
|
|
- val ret = CompletableFuture<Any>()
|
|
|
+ val ret = CompletableFuture<Ticket>()
|
|
|
globalFutureMap.computeIfAbsent(e.id) { id ->
|
|
|
- val def = CompletableDeferred<Any>()
|
|
|
- def.invokeOnCompletion { t: Throwable? -> if (t == null) ret.complete(null) else ret.completeExceptionally(t) }
|
|
|
+ ticketService.offerLast(bankTicketChannelName, e)
|
|
|
+ val def = CompletableDeferred<Ticket>()
|
|
|
+ def.invokeOnCompletion { t: Throwable? -> if (t == null) ret.complete(e) else ret.completeExceptionally(t) }
|
|
|
def
|
|
|
}
|
|
|
- ticketService.offerLast(bankTicketChannelName, e)
|
|
|
return ret
|
|
|
}
|
|
|
|
|
|
- suspend fun pollLater(e: BankEvent): Boolean {
|
|
|
+ @Transactional
|
|
|
+ fun pollLater(e: BankEvent): Boolean {
|
|
|
return when (e) {
|
|
|
is ExchangeStartEvent -> {
|
|
|
val eFrom = e.from?.let { getAccount(it) } ?: emitter
|
|
@@ -163,83 +165,62 @@ class CentralBankQueueLevel : CentralBankAccountLevel() {
|
|
|
} ?: true
|
|
|
}
|
|
|
is ExchangeSuccessEvent -> {
|
|
|
- globalFutureMap.get(e.parentEvent?.id)!!.let {
|
|
|
- globalFutureMap.remove(e.parentEvent?.id)
|
|
|
- it.complete(true)
|
|
|
+ e.parentEvent?.let { p ->
|
|
|
+ globalFutureMap.get(p.id)?.let {
|
|
|
+ globalFutureMap.remove(p.id)
|
|
|
+ it.complete(p)
|
|
|
+ }
|
|
|
}
|
|
|
true
|
|
|
}
|
|
|
is ExchangeFailedEvent -> {
|
|
|
- globalFutureMap.get(e.parentEvent?.id)?.let {
|
|
|
- globalFutureMap.remove(e.parentEvent?.id)
|
|
|
- it.completeExceptionally(ExchangeFailedException(e, e.reason))
|
|
|
+ e.parentEvent?.let { p ->
|
|
|
+ globalFutureMap.get(p.id)?.let {
|
|
|
+ globalFutureMap.remove(e.parentEvent?.id)
|
|
|
+ it.completeExceptionally(ExchangeFailedException(e, e.reason))
|
|
|
+ }
|
|
|
}
|
|
|
true
|
|
|
}
|
|
|
- is BankPauseOnRecalcEvent -> !e.paused!!
|
|
|
- else -> throw IllegalArgumentException("wrong event type ${e.javaClass.name}")
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fun poll(e: BankEvent): Boolean {
|
|
|
- return runBlocking {
|
|
|
- try {
|
|
|
- pollLater(e)
|
|
|
- } catch (t: Throwable) {
|
|
|
- log.error("error in event", t)
|
|
|
+ is BankPauseOnRecalcEvent -> {
|
|
|
+ getCurrentState { it.queueState = BankQueueState.recalc }
|
|
|
true
|
|
|
}
|
|
|
+ is BankResumeAfterRecalcEvent -> {
|
|
|
+ accounts.map { it to it.coins?.map { it.id }?.intersect(e.emitterEvent?.nullCoins ?: setOf()) }.filter { it.second?.isNotEmpty() ?: false }.forEach { ap ->
|
|
|
+ val nullCoins = ap.second?.map { ap.first.extractOne(it) }?.toSet() ?: setOf()
|
|
|
+ emitter.accept(nullCoins)
|
|
|
+ }
|
|
|
+ getCurrentState { it.queueState = BankQueueState.open }
|
|
|
+ true
|
|
|
+ }
|
|
|
+ else -> throw IllegalArgumentException("wrong event type ${e.javaClass.name}")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- val maxPollCount = 1024
|
|
|
-
|
|
|
@PostConstruct
|
|
|
fun start() {
|
|
|
- GlobalScope.launch {
|
|
|
- do {
|
|
|
- tick()
|
|
|
- delay(200)
|
|
|
- } while (true)
|
|
|
- }
|
|
|
emitter.listen { e ->
|
|
|
- log.info("emitter event ${e.javaClass.name}")
|
|
|
+ log.debug("emitter event ${e.javaClass.name}")
|
|
|
when (e) {
|
|
|
- is EmitterStartRecalculationEvent -> {
|
|
|
- ticketService.offerLast(bankTicketChannelName, BankPauseOnRecalcEvent(UUID.randomUUID(), e))
|
|
|
- runBlocking {
|
|
|
- do {
|
|
|
- delay(500)
|
|
|
- } while (!(ticketService.peekFirst(bankTicketChannelName) is BankPauseOnRecalcEvent))
|
|
|
- }
|
|
|
- }
|
|
|
- is EmitterStopRecalculationEvent -> {
|
|
|
- accounts.map { it to it.coins?.map { it.id }?.intersect(e.nullCoins) }.filter { it.second?.isNotEmpty() ?: false }.forEach { ap ->
|
|
|
- val nullCoins = ap.second?.map {
|
|
|
- //(ap.first as DemoAccount).internalCoins.get(it)?.incEra() //TODO убрать!
|
|
|
- ap.first.extractOne(it)
|
|
|
- }?.toSet() ?: setOf()
|
|
|
- emitter.accept(nullCoins)
|
|
|
- }
|
|
|
- ticketService.peekFirst(bankTicketChannelName) { (it as BankPauseOnRecalcEvent).paused = false; it }
|
|
|
- }
|
|
|
+ is EmitterStartRecalculationEvent -> ticketService.offerLast(bankTicketChannelName, BankPauseOnRecalcEvent(UUID.randomUUID(), e))
|
|
|
+ is EmitterStopRecalculationEvent -> ticketService.offerLast(bankTicketChannelName, BankResumeAfterRecalcEvent(UUID.randomUUID(), e))
|
|
|
+ else -> throw RuntimeException("unknown emitter event ${e.javaClass.name}")
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- fun tick() {
|
|
|
- var pollCount = 0
|
|
|
- do {
|
|
|
- val e = ticketService.peekFirst(bankTicketChannelName)
|
|
|
- if (e != null && e is AbstractBankEvent) {
|
|
|
- if (poll(e)) {
|
|
|
- ticketService.remove(e)
|
|
|
+ ticketService.listen(bankTicketChannelName) { e ->
|
|
|
+ val qst = getCurrentState { it.queueState ?: BankQueueState.open }
|
|
|
+ if (e is AbstractBankEvent && qst != BankQueueState.closed) {
|
|
|
+ if ((qst == BankQueueState.open && !(e is BankResumeAfterRecalcEvent)) || (qst == BankQueueState.recalc && e is BankResumeAfterRecalcEvent)) {
|
|
|
+ pollLater(e)
|
|
|
+ } else {
|
|
|
+ false
|
|
|
}
|
|
|
+ } else {
|
|
|
+ null
|
|
|
}
|
|
|
- pollCount++
|
|
|
- } while (e != null && pollCount < maxPollCount)
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
abstract class AbstractBankEvent : BankEvent()
|
|
@@ -287,11 +268,18 @@ class BankPauseOnRecalcEvent() : AbstractBankEvent() {
|
|
|
|
|
|
var emitterEvent: EmitterStartRecalculationEvent? = null
|
|
|
|
|
|
- var paused: Boolean? = true
|
|
|
+ constructor(id: UUID, emitterEvent: EmitterStartRecalculationEvent) : this() {
|
|
|
+ this.id = id
|
|
|
+ this.emitterEvent = emitterEvent
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class BankResumeAfterRecalcEvent() : AbstractBankEvent() {
|
|
|
+ var emitterEvent: EmitterStopRecalculationEvent? = null
|
|
|
|
|
|
- constructor(id: UUID, emitterEvent: EmitterStartRecalculationEvent, paused: Boolean = true) : this() {
|
|
|
+ constructor(id: UUID, emitterEvent: EmitterStopRecalculationEvent) : this() {
|
|
|
this.id = id
|
|
|
this.emitterEvent = emitterEvent
|
|
|
- this.paused = paused
|
|
|
}
|
|
|
}
|
|
|
+
|