diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/Bridge.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/Bridge.kt index c1f27ddc63..3a1355d383 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/Bridge.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/Bridge.kt @@ -27,6 +27,9 @@ import org.jxmpp.jid.Jid import java.time.Clock import java.time.Duration import java.time.Instant +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import kotlin.math.max import org.jitsi.jicofo.bridge.BridgeConfig.Companion.config as config /** @@ -48,36 +51,34 @@ class Bridge @JvmOverloads internal constructor( private val clock: Clock = Clock.systemUTC() ) : Comparable { - /** - * Keep track of the recently added endpoints. - */ + /** Keep track of the recently added endpoints. */ private val newEndpointsRate = RateTracker( config.participantRampupInterval, Duration.ofMillis(100), clock ) - /** - * The last report stress level - */ + private val endpointRestartRequestRate = RateTracker( + config.iceFailureDetection.interval, + Duration.ofSeconds(1), + clock + ) + + /** Number of endpoints currently allocated on this bridge by this jicofo instance. */ + val endpoints = AtomicInteger(0) + + /** The last report stress level */ var lastReportedStressLevel = 0.0 private set - /** - * Holds bridge version (if known - not all bridge version are capable of - * reporting it). - */ + /** Holds bridge version (if known - not all bridge version are capable of reporting it). */ private var version: String? = null - /** - * Whether the last received presence indicated the bridge is healthy. - */ + /** Whether the last received presence indicated the bridge is healthy. */ var isHealthy = true private set - /** - * Holds bridge release ID, or null if not known. - */ + /** Holds bridge release ID, or null if not known. */ private var releaseId: String? = null /** @@ -108,25 +109,16 @@ class Bridge @JvmOverloads internal constructor( } } - /** - * Start out with the configured value, update if the bridge reports a value. - */ + /** Start out with the configured value, update if the bridge reports a value. */ private var averageParticipantStress = config.averageParticipantStress - /** - * Stores a boolean that indicates whether the bridge is in graceful shutdown mode. - */ + /** Stores a boolean that indicates whether the bridge is in graceful shutdown mode. */ var isInGracefulShutdown = false // we assume it is not shutting down - /** - * Whether the bridge is in SHUTTING_DOWN mode. - */ + /** Whether the bridge is in SHUTTING_DOWN mode. */ var isShuttingDown = false private set - /** - * @return true if the bridge is currently in drain mode - */ /** * Stores a boolean that indicates whether the bridge is in drain mode. */ @@ -140,19 +132,27 @@ class Bridge @JvmOverloads internal constructor( */ private var failureInstant: Instant? = null - /** - * @return the region of this [Bridge]. - */ + /** @return the region of this [Bridge]. */ var region: String? = null private set - /** - * @return the relay ID advertised by the bridge, or `null` if - * none was advertised. - */ + /** @return the relay ID advertised by the bridge, or `null` if none was advertised. */ var relayId: String? = null private set + /** + * If this [Bridge] has been removed from the list of bridges. Once removed, the metrics specific to this instance + * are cleared and no longer emitted. If the bridge re-connects, a new [Bridge] instance will be created. + */ + val removed = AtomicBoolean(false) + + /** + * The last instant at which we detected, based on restart requests from endpoints, that this bridge is failing ICE + */ + private var lastIceFailed = Instant.MIN + private val failingIce: Boolean + get() = Duration.between(lastIceFailed, clock.instant()) < config.iceFailureDetection.timeout + private val logger: Logger = LoggerImpl(Bridge::class.java.name) init { @@ -237,37 +237,75 @@ class Bridge @JvmOverloads internal constructor( return compare(this, other) } - /** - * Notifies this [Bridge] that it was used for a new endpoint. - */ + /** Notifies this [Bridge] that it was used for a new endpoint. */ fun endpointAdded() { newEndpointsRate.update(1) + endpoints.incrementAndGet() + if (!removed.get()) { + BridgeMetrics.endpoints.set(endpoints.get().toLong(), listOf(jid.resourceOrEmpty.toString())) + } } - /** - * Returns the net number of video channels recently allocated or removed - * from this bridge. - */ + fun endpointRemoved() = endpointsRemoved(1) + fun endpointsRemoved(count: Int) { + endpoints.addAndGet(-count) + if (!removed.get()) { + BridgeMetrics.endpoints.set(endpoints.get().toLong(), listOf(jid.resourceOrEmpty.toString())) + } + if (endpoints.get() < 0) { + logger.error("Removed more endpoints than were allocated. Resetting to 0.", Throwable()) + endpoints.set(0) + } + } + internal fun markRemoved() { + if (removed.compareAndSet(false, true)) { + BridgeMetrics.restartRequestsMetric.remove(listOf(jid.resourceOrEmpty.toString())) + BridgeMetrics.endpoints.remove(listOf(jid.resourceOrEmpty.toString())) + } + } + internal fun updateMetrics() { + if (!removed.get()) { + BridgeMetrics.failingIce.set(failingIce, listOf(jid.resourceOrEmpty.toString())) + } + } + + fun endpointRequestedRestart() { + endpointRestartRequestRate.update(1) + if (!removed.get()) { + BridgeMetrics.restartRequestsMetric.inc(listOf(jid.resourceOrEmpty.toString())) + } + + if (config.iceFailureDetection.enabled) { + val restartCount = endpointRestartRequestRate.getAccumulatedCount() + val endpoints = endpoints.get() + if (endpoints >= config.iceFailureDetection.minEndpoints && + restartCount > endpoints * config.iceFailureDetection.threshold + ) { + // Reset the timeout regardless of the previous state, but only log if the state changed. + if (!failingIce) { + logger.info("Detected an ICE failing state.") + } + lastIceFailed = clock.instant() + } + } + } + + /** Returns the net number of video channels recently allocated or removed from this bridge. */ private val recentlyAddedEndpointCount: Long get() = newEndpointsRate.getAccumulatedCount() - /** - * The version of this bridge (with embedded release ID, if available). - */ + /** The version of this bridge (with embedded release ID, if available). */ val fullVersion: String? get() = if (version != null && releaseId != null) "$version-$releaseId" else version - /** - * {@inheritDoc} - */ override fun toString(): String { return String.format( - "Bridge[jid=%s, version=%s, relayId=%s, region=%s, stress=%.2f]", + "Bridge[jid=%s, version=%s, relayId=%s, region=%s, correctedStress=%.2f]", jid.toString(), fullVersion, relayId, region, - stress + correctedStress ) } @@ -276,34 +314,37 @@ class Bridge @JvmOverloads internal constructor( * can exceed 1). * @return this bridge's stress level */ - val stress: Double - get() = - // While a stress of 1 indicates a bridge is fully loaded, we allow - // larger values to keep sorting correctly. - lastReportedStressLevel + - recentlyAddedEndpointCount.coerceAtLeast(0) * averageParticipantStress + val correctedStress: Double + get() { + // Correct for recently added endpoints. + // While a stress of 1 indicates a bridge is fully loaded, we allow larger values to keep sorting correctly. + val s = lastReportedStressLevel + recentlyAddedEndpointCount.coerceAtLeast(0) * averageParticipantStress - /** - * @return true if the stress of the bridge is greater-than-or-equal to the threshold. - */ + // Correct for failing ICE. + return if (failingIce) max(s, config.stressThreshold + 0.01) else s + } + + /** @return true if the stress of the bridge is greater-than-or-equal to the threshold. */ val isOverloaded: Boolean - get() = stress >= config.stressThreshold + get() = correctedStress >= config.stressThreshold val debugState: OrderedJsonObject - get() { - val o = OrderedJsonObject() - o["version"] = version.toString() - o["release"] = releaseId.toString() - o["stress"] = stress - o["operational"] = isOperational - o["region"] = region.toString() - o["drain"] = isDraining - o["graceful-shutdown"] = isInGracefulShutdown - o["shutting-down"] = isShuttingDown - o["overloaded"] = isOverloaded - o["relay-id"] = relayId.toString() - o["healthy"] = isHealthy - return o + get() = OrderedJsonObject().apply { + this["corrected-stress"] = correctedStress + this["drain"] = isDraining + this["endpoints"] = endpoints.get() + this["endpoint-restart-requests"] = endpointRestartRequestRate.getAccumulatedCount() + this["failing-ice"] = failingIce + this["graceful-shutdown"] = isInGracefulShutdown + this["healthy"] = isHealthy + this["operational"] = isOperational + this["overloaded"] = isOverloaded + this["region"] = region.toString() + this["relay-id"] = relayId.toString() + this["release"] = releaseId.toString() + this["shutting-down"] = isShuttingDown + this["stress"] = lastReportedStressLevel + this["version"] = version.toString() } companion object { @@ -327,7 +368,7 @@ class Bridge @JvmOverloads internal constructor( return if (myPriority != otherPriority) { myPriority - otherPriority } else { - b1.stress.compareTo(b2.stress) + b1.correctedStress.compareTo(b2.correctedStress) } } diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeConfig.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeConfig.kt index b9292cfec6..6bf7c3cb93 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeConfig.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeConfig.kt @@ -156,6 +156,8 @@ class BridgeConfig private constructor() { fun getRegionGroup(region: String?): Set = if (region == null) emptySet() else regionGroups[region] ?: setOf(region) + val iceFailureDetection = IceFailureDetectionConfig() + companion object { const val BASE = "jicofo.bridge" @@ -163,3 +165,25 @@ class BridgeConfig private constructor() { val config = BridgeConfig() } } + +class IceFailureDetectionConfig { + val enabled: Boolean by config { + "$BASE.enabled".from(JitsiConfig.newConfig) + } + val interval: Duration by config { + "$BASE.interval".from(JitsiConfig.newConfig) + } + val minEndpoints: Int by config { + "$BASE.min-endpoints".from(JitsiConfig.newConfig) + } + val threshold: Double by config { + "$BASE.threshold".from(JitsiConfig.newConfig) + } + val timeout: Duration by config { + "$BASE.timeout".from(JitsiConfig.newConfig) + } + + companion object { + const val BASE = "jicofo.bridge.ice-failure-detection" + } +} diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeMetrics.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeMetrics.kt new file mode 100644 index 0000000000..a60c1ebd15 --- /dev/null +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeMetrics.kt @@ -0,0 +1,23 @@ +package org.jitsi.jicofo.bridge + +import org.jitsi.jicofo.metrics.JicofoMetricsContainer.Companion.instance as metricsContainer +class BridgeMetrics { + companion object { + /** Total number of participants that requested a restart for a specific bridge. */ + val restartRequestsMetric = metricsContainer.registerCounter( + "bridge_restart_requests_total", + "Total number of requests to restart a bridge", + labelNames = listOf("jvb") + ) + val endpoints = metricsContainer.registerLongGauge( + "bridge_endpoints", + "The number of endpoints on a bridge.", + labelNames = listOf("jvb") + ) + val failingIce = metricsContainer.registerBooleanMetric( + "bridge_failing_ice", + "Whether a bridge is currently in the failing ICE state.", + labelNames = listOf("jvb") + ) + } +} diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeSelector.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeSelector.kt index 163fd8533a..7e7df1dc19 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeSelector.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/BridgeSelector.kt @@ -112,6 +112,7 @@ class BridgeSelector @JvmOverloads constructor( logger.warn("Lost a bridge: $bridgeJid") lostBridges.inc() } + it.markRemoved() bridgeCount.dec() eventEmitter.fireEvent { bridgeRemoved(it) } } @@ -214,10 +215,7 @@ class BridgeSelector @JvmOverloads constructor( conferenceBridges, participantProperties, OctoConfig.config.enabled - ).also { - // The bridge was selected for an endpoint, increment its counter. - it?.endpointAdded() - } + ) } val stats: JSONObject @@ -245,6 +243,7 @@ class BridgeSelector @JvmOverloads constructor( inShutdownBridgeCountMetric.set(bridges.values.count { it.isInGracefulShutdown }.toLong()) operationalBridgeCountMetric.set(bridges.values.count { it.isOperational }.toLong()) bridgeVersionCount.set(bridges.values.map { it.fullVersion }.toSet().size.toLong()) + bridges.values.forEach { it.updateMetrics() } } companion object { diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/VisitorTopologyStrategy.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/VisitorTopologyStrategy.kt index 785cf2623a..85f34ad8c6 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/VisitorTopologyStrategy.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/VisitorTopologyStrategy.kt @@ -35,8 +35,9 @@ class VisitorTopologyStrategy : TopologySelectionStrategy() { cascade.getDistanceFrom(it) { node -> !node.visitor } } - val sortedNodes = nodesWithDistance.entries.sortedWith(compareBy({ it.value }, { it.key.bridge.stress })) - .map { it.key } + val sortedNodes = nodesWithDistance.entries.sortedWith( + compareBy({ it.value }, { it.key.bridge.correctedStress }) + ).map { it.key } /* TODO: this logic looks a lot like bridge selection. Do we want to try to share logic with that code? */ val nonOverloadedInRegion = sortedNodes.filter { diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriSessionManager.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriSessionManager.kt index b30588c2cb..5caef3f209 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriSessionManager.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriSessionManager.kt @@ -57,7 +57,7 @@ interface ColibriSessionManager { suppressLocalBridgeUpdate: Boolean = false ) - fun getBridgeSessionId(participantId: String): String? + fun getBridgeSessionId(participantId: String): Pair /** * Stop using [bridge], expiring all endpoints on it (e.g. because it was detected to have failed). diff --git a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriV2SessionManager.kt b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriV2SessionManager.kt index f151f5b0c8..9d518107e9 100644 --- a/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriV2SessionManager.kt +++ b/jicofo-selector/src/main/kotlin/org/jitsi/jicofo/bridge/colibri/ColibriV2SessionManager.kt @@ -112,6 +112,7 @@ class ColibriV2SessionManager( logger.info("Expiring.") sessions.values.forEach { session -> logger.debug { "Expiring $session" } + session.bridge.endpointsRemoved(getSessionParticipants(session).size) session.expire() } sessions.clear() @@ -134,6 +135,7 @@ class ColibriV2SessionManager( private fun removeSession(session: Colibri2Session): Set { val participants = getSessionParticipants(session) + session.bridge.endpointsRemoved(participants.size) session.expire() removeNode(session, ::repairMesh) sessions.remove(session.relayId) @@ -162,6 +164,7 @@ class ColibriV2SessionManager( sessionRemoved = true } else { session.expire(sessionParticipantsToRemove) + session.bridge.endpointRemoved() sessionParticipantsToRemove.forEach { remove(it) } participantsRemoved.addAll(sessionParticipantsToRemove) @@ -334,6 +337,7 @@ class ColibriV2SessionManager( ) } participantInfo = ParticipantInfo(participant, session) + session.bridge.endpointAdded() stanzaCollector = session.sendAllocationRequest(participantInfo) add(participantInfo) if (created) { @@ -581,8 +585,9 @@ class ColibriV2SessionManager( } } - override fun getBridgeSessionId(participantId: String): String? = synchronized(syncRoot) { - return participants[participantId]?.session?.id + override fun getBridgeSessionId(participantId: String): Pair = synchronized(syncRoot) { + val session = participants[participantId]?.session + return Pair(session?.bridge, session?.id) } override fun removeBridge(bridge: Bridge): List = synchronized(syncRoot) { diff --git a/jicofo-selector/src/main/resources/reference.conf b/jicofo-selector/src/main/resources/reference.conf index a72f40a83c..6e8abdf648 100644 --- a/jicofo-selector/src/main/resources/reference.conf +++ b/jicofo-selector/src/main/resources/reference.conf @@ -81,6 +81,21 @@ jicofo { // Note that if no separate Service connection has been configured, all services will automatically use the // Client connection. xmpp-connection-name = Service + + // Detect bridge failure based on endpoints that request a restart due to an ICE failure. + ice-failure-detection { + enabled = false + // Count the number of restart requests in the last `interval`. + interval = 2 minutes + // The minimum number of endpoints on the bridge before this check is enabled. + min-endpoints = 30 + // The threshold, in terms of fraction of endpoints that requested a restart in the last `interval`, above which + // the bridge will be considered failing ICE. + threshold = 0.1 + // Once the "failing ICE" state is entered, the bridge will stay in it at least this long (refreshed as long as + // the condition is met). + timeout = 10 minutes + } } // Configure the codecs and RTP extensions to be used in the offer sent to clients. codec { diff --git a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeSelectorTest.kt b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeSelectorTest.kt index d4ddfe82d4..340abb66be 100644 --- a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeSelectorTest.kt +++ b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeSelectorTest.kt @@ -42,10 +42,11 @@ class BridgeSelectorTest : ShouldSpec() { context("Stress from new endpoints") { val bridgeSelector = BridgeSelector(clock) val bridge = bridgeSelector.addJvbAddress(jid1).apply { setStats() } - bridge.stress shouldBe 0 + bridge.correctedStress shouldBe 0 bridgeSelector.selectBridge() + bridge.endpointAdded() // The stress should increase because it was recently selected. - bridge.stress shouldNotBe 0 + bridge.correctedStress shouldNotBe 0 } context("Selection based on operational status") { diff --git a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeTest.kt b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeTest.kt index ca733e9888..533125125b 100644 --- a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeTest.kt +++ b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/BridgeTest.kt @@ -31,13 +31,13 @@ class BridgeTest : ShouldSpec({ val bridge1: Bridge = mockk { every { isOperational } returns true every { isInGracefulShutdown } returns false - every { stress } returns 10.0 + every { correctedStress } returns 10.0 } val bridge2: Bridge = mockk { every { isOperational } returns false every { isInGracefulShutdown } returns true - every { stress } returns .1 + every { correctedStress } returns .1 } Bridge.compare(bridge1, bridge2) shouldBeLessThan 0 } @@ -46,13 +46,13 @@ class BridgeTest : ShouldSpec({ val bridge1: Bridge = mockk { every { isOperational } returns true every { isInGracefulShutdown } returns false - every { stress } returns 10.0 + every { correctedStress } returns 10.0 } val bridge2: Bridge = mockk { every { isOperational } returns true every { isInGracefulShutdown } returns true - every { stress } returns .1 + every { correctedStress } returns .1 } Bridge.compare(bridge1, bridge2) shouldBeLessThan 0 } @@ -61,13 +61,13 @@ class BridgeTest : ShouldSpec({ val bridge1: Bridge = mockk { every { isOperational } returns true every { isInGracefulShutdown } returns false - every { stress } returns 10.0 + every { correctedStress } returns 10.0 } val bridge2: Bridge = mockk { every { isOperational } returns true every { isInGracefulShutdown } returns false - every { stress } returns .1 + every { correctedStress } returns .1 } Bridge.compare(bridge2, bridge1) shouldBeLessThan 0 } @@ -103,21 +103,21 @@ class BridgeTest : ShouldSpec({ // This mostly makes sure the test framework works as expected. val bridge = Bridge(JidCreate.from("bridge")) - bridge.stress shouldBe 0 + bridge.correctedStress shouldBe 0 bridge.region shouldBe null bridge.setStats(stress = 0.1) - bridge.stress shouldBe 0.1 + bridge.correctedStress shouldBe 0.1 bridge.region shouldBe null // The different stats should be updated independently. bridge.setStats(region = "region") - bridge.stress shouldBe 0.1 + bridge.correctedStress shouldBe 0.1 bridge.region shouldBe "region" // The different stats should be updated independently. bridge.setStats(stress = 0.2) - bridge.stress shouldBe 0.2 + bridge.correctedStress shouldBe 0.2 bridge.region shouldBe "region" } }) diff --git a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/RegionBasedSelectionTest.kt b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/RegionBasedSelectionTest.kt index 728ca6d7e3..fda557cdc5 100644 --- a/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/RegionBasedSelectionTest.kt +++ b/jicofo-selector/src/test/kotlin/org/jitsi/jicofo/bridge/RegionBasedSelectionTest.kt @@ -48,7 +48,7 @@ class RegionBasedSelectionTest : ShouldSpec() { with(RegionBasedBridgeSelectionStrategy()) { context("In a single region") { - select()!!.stress shouldBe Low.stress + select()!!.correctedStress shouldBe Low.stress select(participantRegion = ApSouth) shouldBe bridges[ApSouth][Low] select( participantRegion = ApSouth, @@ -175,11 +175,11 @@ val regionGroupsConfig = """ private fun mockBridge(r: Regions, s: StressLevels) = mockk { every { region } returns r.region - every { stress } returns s.stress + every { correctedStress } returns s.stress every { isOverloaded } returns (s == High) every { lastReportedStressLevel } returns s.stress every { relayId } returns "dummy" - every { this@mockk.toString() } returns "MockBridge[region=$region, stress=$stress]" + every { this@mockk.toString() } returns "MockBridge[region=$region, stress=$correctedStress]" } // Create a Low, Medium and High stress bridge in each region. diff --git a/jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConferenceImpl.java b/jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConferenceImpl.java index dc5ebb5ef1..f068334a3e 100644 --- a/jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConferenceImpl.java +++ b/jicofo/src/main/java/org/jitsi/jicofo/conference/JitsiMeetConferenceImpl.java @@ -17,6 +17,7 @@ */ package org.jitsi.jicofo.conference; +import kotlin.*; import org.jetbrains.annotations.*; import org.jitsi.jicofo.*; import org.jitsi.jicofo.auth.*; @@ -1169,13 +1170,18 @@ public MemberRole getRoleForMucJid(Jid mucJid) */ public void iceFailed(@NotNull Participant participant, String bridgeSessionId) { - String existingBridgeSessionId = getColibriSessionManager().getBridgeSessionId(participant.getEndpointId()); - if (Objects.equals(bridgeSessionId, existingBridgeSessionId)) + Pair existingBridgeSession + = getColibriSessionManager().getBridgeSessionId(participant.getEndpointId()); + if (Objects.equals(bridgeSessionId, existingBridgeSession.getSecond())) { logger.info(String.format( "Received ICE failed notification from %s, bridge-session ID: %s", participant.getEndpointId(), bridgeSessionId)); + if (existingBridgeSession.getFirst() != null) + { + existingBridgeSession.getFirst().endpointRequestedRestart(); + } reInviteParticipant(participant); } else @@ -1203,12 +1209,18 @@ void terminateSession( throws InvalidBridgeSessionIdException { // TODO: maybe move the bridgeSessionId logic to Participant - String existingBridgeSessionId = getColibriSessionManager().getBridgeSessionId(participant.getEndpointId()); - if (!Objects.equals(bridgeSessionId, existingBridgeSessionId)) + Pair existingBridgeSession + = getColibriSessionManager().getBridgeSessionId(participant.getEndpointId()); + if (!Objects.equals(bridgeSessionId, existingBridgeSession.getSecond())) { throw new InvalidBridgeSessionIdException(bridgeSessionId + " is not a currently active session"); } + if (reinvite && existingBridgeSession.getFirst() != null) + { + existingBridgeSession.getFirst().endpointRequestedRestart(); + } + synchronized (participantLock) { terminateParticipant( diff --git a/pom.xml b/pom.xml index 3d0adcb028..02241668b4 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ 5.7.2 1.7.32 1.0-127-g6c65524 - 1.1-150-g57913c0 + 1.1-152-g43a2034 3.0.0 4.8.6 4.8.6.6