Merge pull request #102 from p2p-org/fix-node-status-subscription

fix node status subsciption for gRPC upstreams
This commit is contained in:
Vyacheslav Shebanov
2023-01-09 20:53:49 +02:00
committed by GitHub
2 changed files with 39 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.upstream.CurrentMultistreamHolder import io.emeraldpay.dshackle.upstream.CurrentMultistreamHolder
import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.dshackle.upstream.UpstreamAvailability
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Mono import reactor.core.publisher.Mono
@@ -17,7 +18,6 @@ import reactor.core.publisher.SignalType
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
import java.time.Duration import java.time.Duration
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.function.Consumer
@Service @Service
class SubscribeNodeStatus( class SubscribeNodeStatus(
@@ -26,11 +26,12 @@ class SubscribeNodeStatus(
companion object { companion object {
private val RETRY_TIMEOUT = Duration.ofSeconds(10) private val RETRY_TIMEOUT = Duration.ofSeconds(10)
private val log = LoggerFactory.getLogger(SubscribeNodeStatus::class.java)
} }
fun subscribe(req: Mono<SubscribeNodeStatusRequest>): Flux<NodeStatusResponse> = fun subscribe(req: Mono<SubscribeNodeStatusRequest>): Flux<NodeStatusResponse> =
req.flatMapMany { request -> req.flatMapMany { request ->
val knownUpstreams = ConcurrentHashMap<String, Boolean>() val knownUpstreams = ConcurrentHashMap<String, Sinks.Many<Boolean>>()
val duration = Duration.ofMillis(request.timespan) val duration = Duration.ofMillis(request.timespan)
// send known upstreams details immediately // send known upstreams details immediately
val descriptions = Flux.fromIterable( val descriptions = Flux.fromIterable(
@@ -51,8 +52,29 @@ class SubscribeNodeStatus(
multistreams.all() multistreams.all()
.flatMap { ms -> .flatMap { ms ->
ms.getAll().map { up -> ms.getAll().map { up ->
knownUpstreams[up.getId()] = true knownUpstreams[up.getId()] = Sinks.many().multicast().directBestEffort<Boolean>()
subscribeUpstreamUpdates(ms.chain, up, duration) { r -> knownUpstreams.remove(r) } subscribeUpstreamUpdates(ms.chain, up, duration, knownUpstreams[up.getId()]!!)
}
}
)
// stop removed upstreams update fluxes
val removals = Flux.merge(
multistreams.all()
.map { ms ->
ms.subscribeRemovedUpstreams().mapNotNull { up ->
knownUpstreams[up.getId()]?.let {
val result = it.tryEmitNext(true)
if (result.isFailure) {
log.warn("Unable to emit event about removal of an upstream - $result")
}
knownUpstreams.remove(up.getId())
NodeStatusResponse.newBuilder()
.setNodeId(up.getId())
.setDescription(buildDescription(ms.chain, up))
.setStatus(buildStatus(UpstreamAvailability.UNAVAILABLE, up.getHead().getCurrentHeight()))
.build()
}
} }
} }
) )
@@ -66,10 +88,10 @@ class SubscribeNodeStatus(
it.getId() it.getId()
} }
.filter { .filter {
!knownUpstreams.getOrDefault(it.getId(), false) !knownUpstreams.contains(it.getId())
} }
.flatMap { .flatMap {
knownUpstreams[it.getId()] = true knownUpstreams[it.getId()] = Sinks.many().multicast().directBestEffort<Boolean>()
Flux.concat( Flux.concat(
Mono.just( Mono.just(
NodeStatusResponse.newBuilder() NodeStatusResponse.newBuilder()
@@ -78,24 +100,22 @@ class SubscribeNodeStatus(
.setStatus(buildStatus(it.getStatus(), it.getHead().getCurrentHeight())) .setStatus(buildStatus(it.getStatus(), it.getHead().getCurrentHeight()))
.build() .build()
), ),
subscribeUpstreamUpdates(ms.chain, it, duration) { r -> knownUpstreams.remove(r) } subscribeUpstreamUpdates(ms.chain, it, duration, knownUpstreams[it.getId()]!!)
) )
} }
} }
) )
Flux.concat(descriptions, Flux.merge(upstreamUpdates, multiStreamUpdates)) Flux.concat(descriptions, Flux.merge(upstreamUpdates, multiStreamUpdates, removals))
} }
private fun subscribeUpstreamUpdates( private fun subscribeUpstreamUpdates(
chain: Chain, chain: Chain,
upstream: Upstream, upstream: Upstream,
timespan: Duration, timespan: Duration,
onUnavailable: Consumer<String> cancel: Sinks.Many<Boolean>
): Flux<NodeStatusResponse> { ): Flux<NodeStatusResponse> {
val retry = Sinks.many().multicast().directBestEffort<Boolean>() val retry = Sinks.many().multicast().directBestEffort<Boolean>()
val cancel = Sinks.many().multicast().directBestEffort<Boolean>()
val heads = Mono.just(upstream) val heads = Mono.just(upstream)
.repeatWhen { .repeatWhen {
retry.asFlux() retry.asFlux()
@@ -119,13 +139,8 @@ class SubscribeNodeStatus(
val statuses = upstream.observeStatus() val statuses = upstream.observeStatus()
.distinctUntilChanged() .distinctUntilChanged()
.takeUntil { it == UpstreamAvailability.UNAVAILABLE } .takeUntilOther(cancel.asFlux())
.map { .map {
if (it == UpstreamAvailability.UNAVAILABLE) {
onUnavailable.accept(upstream.getId())
// cancel head subscription & reconnections when upstream becomes unavailable
cancel.tryEmitNext(true)
}
NodeStatusResponse.newBuilder() NodeStatusResponse.newBuilder()
.setNodeId(upstream.getId()) .setNodeId(upstream.getId())
.setStatus(buildStatus(it, upstream.getHead().getCurrentHeight())) .setStatus(buildStatus(it, upstream.getHead().getCurrentHeight()))

View File

@@ -78,6 +78,9 @@ abstract class Multistream(
private val addedUpstreams = Sinks.many() private val addedUpstreams = Sinks.many()
.multicast() .multicast()
.directBestEffort<Upstream>() .directBestEffort<Upstream>()
private val removedUpstreams = Sinks.many()
.multicast()
.directBestEffort<Upstream>()
init { init {
UpstreamAvailability.values().forEach { status -> UpstreamAvailability.values().forEach { status ->
@@ -342,6 +345,7 @@ abstract class Multistream(
eventLock.withLock { eventLock.withLock {
if (event.type == UpstreamChangeEvent.ChangeType.REMOVED) { if (event.type == UpstreamChangeEvent.ChangeType.REMOVED) {
removeUpstream(event.upstream.getId()).takeIf { it }?.let { removeUpstream(event.upstream.getId()).takeIf { it }?.let {
removedUpstreams.tryEmitNext(event.upstream)
log.warn("Upstream ${event.upstream.getId()} with chain $chain has been removed") log.warn("Upstream ${event.upstream.getId()} with chain $chain has been removed")
} }
} else { } else {
@@ -370,6 +374,9 @@ abstract class Multistream(
fun subscribeAddedUpstreams(): Flux<Upstream> = fun subscribeAddedUpstreams(): Flux<Upstream> =
addedUpstreams.asFlux() addedUpstreams.asFlux()
fun subscribeRemovedUpstreams(): Flux<Upstream> =
removedUpstreams.asFlux()
// -------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------
class UpstreamStatus(val upstream: Upstream, val status: UpstreamAvailability, val ts: Instant = Instant.now()) class UpstreamStatus(val upstream: Upstream, val status: UpstreamAvailability, val ts: Instant = Instant.now())