From 49ea1439cbd114f822493d79091f18bce559acb9 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Wed, 13 Jun 2018 23:55:35 +0200 Subject: [PATCH] Implements reliable reconnecting --- .../quasseldroid/service/QuasselService.kt | 35 ++++++++++++++++--- .../util/helper/IntRangeHelper.kt | 6 ++-- .../quassel/syncables/BufferViewConfig.kt | 4 +-- .../libquassel/session/HeartBeatThread.kt | 34 ++++++++++++++++++ .../libquassel/session/ProtocolHandler.kt | 2 -- .../de/kuschku/libquassel/session/Session.kt | 10 ++++-- .../libquassel/session/SessionManager.kt | 26 ++++++++------ .../libquassel/util/helpers/MathHelper.kt | 7 ++-- 8 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt index 0e177366c..d2750638f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt @@ -35,7 +35,9 @@ import de.kuschku.libquassel.session.ISession import de.kuschku.libquassel.session.Session import de.kuschku.libquassel.session.SessionManager import de.kuschku.libquassel.util.compatibility.LoggingHandler +import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO +import de.kuschku.libquassel.util.helpers.clampOf import de.kuschku.libquassel.util.helpers.value import de.kuschku.malheur.CrashHandler import de.kuschku.quasseldroid.BuildConfig @@ -354,14 +356,17 @@ class QuasselService : DaggerLifecycleService(), sessionManager.state .distinctUntilChanged() - .delay(200, TimeUnit.MILLISECONDS) - .throttleFirst(1, TimeUnit.SECONDS) .toLiveData() .observe( this, Observer { handlerService.backend { - LoggingHandler.log(INFO, "QuasselService", "Autoreconnect: State Changed") - sessionManager.autoReconnect() + if (it == ConnectionState.HANDSHAKE) { + backoff = BACKOFF_MIN + } + + if (it == ConnectionState.CLOSED) { + scheduleReconnect() + } } }) @@ -380,6 +385,23 @@ class QuasselService : DaggerLifecycleService(), updateNotificationStatus(this.progress) } + // Set default backoff to 5ms + private var backoff = BACKOFF_MIN + private var scheduled = false + private fun scheduleReconnect() { + if (!scheduled) { + log(INFO, "QuasselService", "Reconnect: Scheduling backoff in ${backoff / 1_000} seconds") + scheduled = true + handlerService.backendDelayed(backoff) { + log(INFO, "QuasselService", "Reconnect: Scheduled backoff happened") + scheduled = false + + backoff = clampOf(backoff * 2, BACKOFF_MIN, BACKOFF_MAX) + sessionManager.autoReconnect(true) + } + } + } + override fun onDestroy() { sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { unregisterOnSharedPreferenceChangeListener(this@QuasselService) @@ -412,6 +434,11 @@ class QuasselService : DaggerLifecycleService(), } companion object { + // default backoff is 5 seconds + const val BACKOFF_MIN = 5_000L + // max is 30 minutes + const val BACKOFF_MAX = 1_800_000L + fun launch( context: Context, disconnect: Boolean? = null, diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt index 8ea855a18..15040669d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt @@ -19,11 +19,11 @@ package de.kuschku.quasseldroid.util.helper -import de.kuschku.libquassel.util.helpers.clamp +import de.kuschku.libquassel.util.helpers.clampOf infix fun IntRange.without(other: IntRange): Iterable<IntRange> { - val otherStart = minOf(other.start, other.last + 1).clamp(this.start, this.last + 1) - val otherLast = maxOf(other.start, other.last + 1).clamp(this.start, this.last + 1) + val otherStart = clampOf(minOf(other.start, other.last + 1), this.start, this.last + 1) + val otherLast = clampOf(maxOf(other.start, other.last + 1), this.start, this.last + 1) val startingFragment: IntRange = this.start until otherStart val endingFragment: IntRange = otherLast + 1 until this.last + 1 diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt index 85f8df480..7168fff07 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt @@ -25,7 +25,7 @@ import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferViewConfig import de.kuschku.libquassel.session.SignalProxy import de.kuschku.libquassel.util.flag.hasFlag -import de.kuschku.libquassel.util.helpers.clamp +import de.kuschku.libquassel.util.helpers.clampOf import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject @@ -126,7 +126,7 @@ class BufferViewConfig constructor( return val currentPos = _buffers.indexOf(bufferId) - val targetPos = pos.clamp(0, _buffers.size - 1) + val targetPos = clampOf(pos, 0, _buffers.size - 1) if (currentPos > targetPos) { _buffers.removeAt(currentPos) diff --git a/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt b/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt new file mode 100644 index 000000000..13436adad --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt @@ -0,0 +1,34 @@ +package de.kuschku.libquassel.session + +import de.kuschku.libquassel.protocol.message.SignalProxyMessage +import org.threeten.bp.Duration +import org.threeten.bp.Instant + +class HeartBeatThread(private val session: Session) : Thread() { + private var running = true + private var lastHeartBeatReply: Instant = Instant.now() + override fun run() { + while (running) { + Thread.sleep(30_000) + val now = Instant.now() + if (Duration.between(lastHeartBeatReply, now).toMillis() > TIMEOUT) { + session.close() + } else { + session.dispatch(SignalProxyMessage.HeartBeat(now)) + } + } + } + + fun end() { + running = false + } + + fun setLastHeartBeatReply(time: Instant) { + this.lastHeartBeatReply = time + } + + companion object { + // Timeout, set to 2 minutes + const val TIMEOUT = 120_000L + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt index 8a0a55988..7255cf2fb 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt @@ -29,7 +29,6 @@ import de.kuschku.libquassel.quassel.syncables.interfaces.ISyncableObject import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG -import org.threeten.bp.Instant import java.io.Closeable @Suppress("LeakingThis") @@ -148,7 +147,6 @@ abstract class ProtocolHandler( override fun handle(f: SignalProxyMessage.HeartBeat): Boolean { dispatch(SignalProxyMessage.HeartBeatReply(f.timestamp)) - dispatch(SignalProxyMessage.HeartBeat(Instant.now())) return true } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt index def9966bf..361c20670 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt @@ -90,6 +90,8 @@ class Session( override val lag = BehaviorSubject.createDefault(0L) + private val heartBeatThread = HeartBeatThread(this) + init { coreConnection.start() } @@ -211,10 +213,10 @@ class Session( synchronize(networkConfig, true) synchronize(backlogManager) - - dispatch(SignalProxyMessage.HeartBeat(Instant.now())) } + heartBeatThread.start() + return true } @@ -231,11 +233,11 @@ class Session( } notificationManager?.init(this) coreConnection.setState(ConnectionState.CONNECTED) - dispatch(SignalProxyMessage.HeartBeat(Instant.now())) } override fun handle(f: SignalProxyMessage.HeartBeatReply): Boolean { val now = Instant.now() + heartBeatThread.setLastHeartBeatReply(f.timestamp) val latency = now.toEpochMilli() - f.timestamp.toEpochMilli() lag.onNext(latency) return true @@ -257,6 +259,8 @@ class Session( override fun close() { super.close() + heartBeatThread.end() + coreConnection.close() certManagers.clear() diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt index a0b87ceaf..a56e736cd 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -138,19 +138,23 @@ class SessionManager( ) } - fun autoReconnect(forceReconnect: Boolean = false) { - if (!hasErrored) { - state.or(ConnectionState.DISCONNECTED).let { - if (it == ConnectionState.CLOSED) { - log(INFO, "SessionManager", "Autoreconnect triggered") - reconnect(forceReconnect) - } else { - log(INFO, "SessionManager", "Autoreconnect failed: state is $it") - } + /** + * @return if an autoreconnect has been necessary + */ + fun autoReconnect(forceReconnect: Boolean = false) = if (!hasErrored) { + state.or(ConnectionState.DISCONNECTED).let { + if (it == ConnectionState.CLOSED) { + log(INFO, "SessionManager", "Autoreconnect triggered") + reconnect(forceReconnect) + true + } else { + log(INFO, "SessionManager", "Autoreconnect failed: state is $it") + false } - } else { - log(INFO, "SessionManager", "Autoreconnect failed: hasErrored") } + } else { + log(INFO, "SessionManager", "Autoreconnect failed: hasErrored") + false } fun reconnect(forceReconnect: Boolean = false) { diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt index 6e6f98955..20e01930a 100644 --- a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt +++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt @@ -19,5 +19,8 @@ package de.kuschku.libquassel.util.helpers -inline fun Int.clamp(lowerBound: Int, upperBound: Int): Int = - maxOf(lowerBound, minOf(this, upperBound)) +inline fun clampOf(value: Int, lowerBound: Int, upperBound: Int): Int = + maxOf(lowerBound, minOf(value, upperBound)) + +inline fun clampOf(value: Long, lowerBound: Long, upperBound: Long): Long = + maxOf(lowerBound, minOf(value, upperBound)) -- GitLab