@@ -12,6 +12,10 @@ import com.github.gotify.client.model.Message
1212import java.util.Calendar
1313import java.util.concurrent.TimeUnit
1414import java.util.concurrent.atomic.AtomicLong
15+ import kotlin.math.pow
16+ import kotlin.time.Duration
17+ import kotlin.time.Duration.Companion.minutes
18+ import kotlin.time.Duration.Companion.seconds
1519import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
1620import okhttp3.OkHttpClient
1721import okhttp3.Request
@@ -24,7 +28,9 @@ internal class WebSocketConnection(
2428 private val baseUrl : String ,
2529 settings : SSLSettings ,
2630 private val token : String? ,
27- private val alarmManager : AlarmManager
31+ private val alarmManager : AlarmManager ,
32+ private val reconnectDelay : Duration ,
33+ private val exponentialBackoff : Boolean
2834) {
2935 companion object {
3036 private val ID = AtomicLong (0 )
@@ -128,19 +134,19 @@ internal class WebSocketConnection(
128134 state = State .Disconnected
129135 }
130136
131- fun scheduleReconnectNow (seconds : Long ) = scheduleReconnect(ID .get(), seconds )
137+ fun scheduleReconnectNow (scheduleIn : Duration ) = scheduleReconnect(ID .get(), scheduleIn )
132138
133139 @Synchronized
134- fun scheduleReconnect (id : Long , seconds : Long ) {
140+ fun scheduleReconnect (id : Long , scheduleIn : Duration ) {
135141 if (state == State .Connecting || state == State .Connected ) {
136142 return
137143 }
138144 state = State .Scheduled
139145
140146 if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
141- Logger .info(" WebSocket: scheduling a restart in $seconds second(s) (via alarm manager)" )
147+ Logger .info(" WebSocket: scheduling a restart in $scheduleIn (via alarm manager)" )
142148 val future = Calendar .getInstance()
143- future.add(Calendar .SECOND , seconds .toInt())
149+ future.add(Calendar .SECOND , scheduleIn.inWholeSeconds .toInt())
144150
145151 alarmManagerCallback?.run (alarmManager::cancel)
146152 val cb = OnAlarmListener { syncExec(id) { start() } }
@@ -153,11 +159,11 @@ internal class WebSocketConnection(
153159 null
154160 )
155161 } else {
156- Logger .info(" WebSocket: scheduling a restart in $seconds second(s) " )
162+ Logger .info(" WebSocket: scheduling a restart in $scheduleIn " )
157163 handlerCallback?.run (reconnectHandler::removeCallbacks)
158164 val cb = Runnable { syncExec(id) { start() } }
159165 handlerCallback = cb
160- reconnectHandler.postDelayed(cb, TimeUnit . SECONDS .toMillis(seconds) )
166+ reconnectHandler.postDelayed(cb, scheduleIn.inWholeMilliseconds )
161167 }
162168 }
163169
@@ -204,10 +210,15 @@ internal class WebSocketConnection(
204210 closed()
205211
206212 errorCount++
207- val minutes = (errorCount * 2 - 1 ).coerceAtMost(20 )
208213
209- onFailure.execute(response?.message ? : " unreachable" , minutes)
210- scheduleReconnect(id, TimeUnit .MINUTES .toSeconds(minutes.toLong()))
214+ var scheduleIn = reconnectDelay
215+ if (exponentialBackoff) {
216+ scheduleIn * = 2.0 .pow(errorCount - 1 )
217+ }
218+ scheduleIn = scheduleIn.coerceIn(5 .seconds.. 20 .minutes)
219+
220+ onFailure.execute(response?.message ? : " unreachable" , scheduleIn)
221+ scheduleReconnect(id, scheduleIn)
211222 }
212223 super .onFailure(webSocket, t, response)
213224 }
@@ -221,7 +232,7 @@ internal class WebSocketConnection(
221232 }
222233
223234 internal fun interface OnNetworkFailureRunnable {
224- fun execute (status : String , minutes : Int )
235+ fun execute (status : String , reconnectIn : Duration )
225236 }
226237
227238 internal enum class State {
0 commit comments