Skip to content

Commit e4efb05

Browse files
authored
Created new TimeoutTimer class which makes use of a ScheduledExecutorService (#42)
1 parent ccdb9b6 commit e4efb05

File tree

4 files changed

+209
-32
lines changed

4 files changed

+209
-32
lines changed

build.gradle

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
buildscript { repositories { jcenter() } }
22
plugins {
3-
id 'java'
4-
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
5-
id 'nebula.project' version '4.0.1'
6-
id "nebula.maven-publish" version "7.2.4"
7-
id 'nebula.nebula-bintray' version '3.5.5'
3+
id 'java'
4+
id 'org.jetbrains.kotlin.jvm' version '1.2.51'
5+
id 'nebula.project' version '4.0.1'
6+
id "nebula.maven-publish" version "7.2.4"
7+
id 'nebula.nebula-bintray' version '3.5.5'
88
}
99

1010
group 'com.github.dsrees'
@@ -13,45 +13,45 @@ version '0.1.8'
1313
sourceCompatibility = 1.8
1414

1515
repositories {
16-
mavenCentral()
16+
mavenCentral()
1717
}
1818

1919
dependencies {
20-
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
21-
compile "com.google.code.gson:gson:2.8.5"
22-
compile "com.squareup.okhttp3:okhttp:3.10.0"
20+
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
21+
compile "com.google.code.gson:gson:2.8.5"
22+
compile "com.squareup.okhttp3:okhttp:3.10.0"
2323

2424

25-
testCompile group: 'junit', name: 'junit', version: '4.12'
26-
testCompile group: 'com.google.truth', name: 'truth', version: '0.42'
27-
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.19.1'
28-
25+
testCompile group: 'junit', name: 'junit', version: '4.12'
26+
testCompile group: 'com.google.truth', name: 'truth', version: '0.42'
27+
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.19.1'
28+
testCompile group: 'com.nhaarman.mockitokotlin2', name: 'mockito-kotlin', version: '2.1.0'
2929
}
3030

3131
compileKotlin {
32-
kotlinOptions.jvmTarget = "1.8"
32+
kotlinOptions.jvmTarget = "1.8"
3333
}
3434
compileTestKotlin {
35-
kotlinOptions.jvmTarget = "1.8"
35+
kotlinOptions.jvmTarget = "1.8"
3636
}
3737

3838
bintray {
39-
user = System.getenv('bintrayUser')
40-
key = System.getenv('bintrayApiKey')
41-
dryRun = false
42-
publish = true
43-
pkg {
44-
repo = 'java-phoenix-client'
45-
name = 'JavaPhoenixClient'
46-
userOrg = user
47-
websiteUrl = 'https://github.com/dsrees/JavaPhoenixClient'
48-
issueTrackerUrl = 'https://github.com/dsrees/JavaPhoenixClient/issues'
49-
vcsUrl = 'https://github.com/dsrees/JavaPhoenixClient.git'
50-
licenses = ['MIT']
51-
version {
52-
name = project.version
53-
vcsTag = project.version
54-
}
39+
user = System.getenv('bintrayUser')
40+
key = System.getenv('bintrayApiKey')
41+
dryRun = false
42+
publish = true
43+
pkg {
44+
repo = 'java-phoenix-client'
45+
name = 'JavaPhoenixClient'
46+
userOrg = user
47+
websiteUrl = 'https://github.com/dsrees/JavaPhoenixClient'
48+
issueTrackerUrl = 'https://github.com/dsrees/JavaPhoenixClient/issues'
49+
vcsUrl = 'https://github.com/dsrees/JavaPhoenixClient.git'
50+
licenses = ['MIT']
51+
version {
52+
name = project.version
53+
vcsTag = project.version
5554
}
56-
publications = ['nebula']
55+
}
56+
publications = ['nebula']
5757
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.phoenixframework
2+
3+
import java.util.concurrent.ScheduledThreadPoolExecutor
4+
5+
// Copyright (c) 2019 Daniel Rees <[email protected]>
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
25+
class Socket {
26+
27+
/**
28+
* All timers associated with a socket will share the same pool. Used for every Channel or
29+
* Push that is sent through or created by a Socket instance. Different Socket instances will
30+
* create individual thread pools.
31+
*/
32+
private val timerPool = ScheduledThreadPoolExecutor(8)
33+
34+
35+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.phoenixframework
2+
3+
import java.util.Timer
4+
import java.util.concurrent.ScheduledExecutorService
5+
import java.util.concurrent.ScheduledFuture
6+
import java.util.concurrent.ScheduledThreadPoolExecutor
7+
import java.util.concurrent.TimeUnit
8+
import kotlin.concurrent.schedule
9+
10+
// Copyright (c) 2019 Daniel Rees <[email protected]>
11+
//
12+
// Permission is hereby granted, free of charge, to any person obtaining a copy
13+
// of this software and associated documentation files (the "Software"), to deal
14+
// in the Software without restriction, including without limitation the rights
15+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16+
// copies of the Software, and to permit persons to whom the Software is
17+
// furnished to do so, subject to the following conditions:
18+
//
19+
// The above copyright notice and this permission notice shall be included in
20+
// all copies or substantial portions of the Software.
21+
//
22+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28+
// THE SOFTWARE.
29+
30+
/**
31+
* A Timer class that schedules a callback to be called in the future. Can be configured
32+
* to use a custom retry pattern, such as exponential backoff.
33+
*/
34+
class TimeoutTimer(
35+
private val scheduledExecutorService: ScheduledExecutorService,
36+
private val callback: () -> Unit,
37+
private val timerCalculation: (tries: Int) -> Long
38+
) {
39+
40+
/** How many tries the Timer has attempted */
41+
private var tries: Int = 0
42+
43+
/** The task that has been scheduled to be executed */
44+
private var futureTask: ScheduledFuture<*>? = null
45+
46+
/**
47+
* Resets the Timer, clearing the number of current tries and stops
48+
* any scheduled timeouts.
49+
*/
50+
fun reset() {
51+
this.tries = 0
52+
this.clearTimer()
53+
}
54+
55+
/** Cancels any previous timeouts and scheduled a new one */
56+
fun scheduleTimeout() {
57+
this.clearTimer()
58+
59+
// Schedule a task to be performed after the calculated timeout in milliseconds
60+
val timeout = timerCalculation(tries + 1)
61+
this.futureTask = scheduledExecutorService.schedule({
62+
this.tries += 1
63+
this.callback.invoke()
64+
}, timeout, TimeUnit.MILLISECONDS)
65+
}
66+
67+
//------------------------------------------------------------------------------
68+
// Private
69+
//------------------------------------------------------------------------------
70+
private fun clearTimer() {
71+
// Cancel the task from completing, allowing it to fi
72+
this.futureTask?.cancel(true)
73+
this.futureTask = null
74+
}
75+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.phoenixframework
2+
3+
import com.google.common.truth.Truth.assertThat
4+
import com.nhaarman.mockitokotlin2.any
5+
import com.nhaarman.mockitokotlin2.argumentCaptor
6+
import com.nhaarman.mockitokotlin2.eq
7+
import com.nhaarman.mockitokotlin2.times
8+
import com.nhaarman.mockitokotlin2.verify
9+
import com.nhaarman.mockitokotlin2.whenever
10+
import org.junit.Test
11+
12+
import org.junit.Before
13+
import org.mockito.Mock
14+
import org.mockito.MockitoAnnotations
15+
import java.util.concurrent.ScheduledExecutorService
16+
import java.util.concurrent.ScheduledFuture
17+
import java.util.concurrent.TimeUnit
18+
19+
class TimeoutTimerTest {
20+
21+
@Mock lateinit var mockFuture: ScheduledFuture<*>
22+
@Mock lateinit var mockExecutorService: ScheduledExecutorService
23+
24+
var callbackCallCount: Int = 0
25+
lateinit var timeoutTimer: TimeoutTimer
26+
27+
@Before
28+
fun setUp() {
29+
MockitoAnnotations.initMocks(this)
30+
31+
callbackCallCount = 0
32+
whenever(mockExecutorService.schedule(any(), any(), any())).thenReturn(mockFuture)
33+
34+
timeoutTimer = TimeoutTimer(
35+
scheduledExecutorService = mockExecutorService,
36+
callback = { callbackCallCount += 1 },
37+
timerCalculation = { tries ->
38+
if(tries > 3 ) 10000 else listOf(1000L, 2000L, 5000L)[tries -1]
39+
})
40+
}
41+
42+
@Test
43+
fun `scheduleTimeout executes with backoff`() {
44+
argumentCaptor<() -> Unit> {
45+
timeoutTimer.scheduleTimeout()
46+
verify(mockExecutorService).schedule(capture(), eq(1000L), eq(TimeUnit.MILLISECONDS))
47+
(lastValue as Runnable).run()
48+
assertThat(callbackCallCount).isEqualTo(1)
49+
50+
timeoutTimer.scheduleTimeout()
51+
verify(mockExecutorService).schedule(capture(), eq(2000L), eq(TimeUnit.MILLISECONDS))
52+
(lastValue as Runnable).run()
53+
assertThat(callbackCallCount).isEqualTo(2)
54+
55+
timeoutTimer.scheduleTimeout()
56+
verify(mockExecutorService).schedule(capture(), eq(5000L), eq(TimeUnit.MILLISECONDS))
57+
(lastValue as Runnable).run()
58+
assertThat(callbackCallCount).isEqualTo(3)
59+
60+
timeoutTimer.reset()
61+
timeoutTimer.scheduleTimeout()
62+
verify(mockExecutorService, times(2)).schedule(capture(), eq(1000L), eq(TimeUnit.MILLISECONDS))
63+
(lastValue as Runnable).run()
64+
assertThat(callbackCallCount).isEqualTo(4)
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)