Skip to content

Commit ce4f2f2

Browse files
committed
uts: mock object.wait(timeout) calls as well
1 parent cdf3a7c commit ce4f2f2

7 files changed

Lines changed: 75 additions & 6 deletions

File tree

lib/src/main/java/io/ably/lib/http/HttpScheduler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException, Execution
291291
long remaining = unit.toMillis(timeout), deadline = clock.currentTimeMillis() + remaining;
292292
synchronized(this) {
293293
while(remaining > 0) {
294-
wait(remaining);
294+
clock.waitOn(this, remaining);
295295
if(isDone) { break; }
296296
remaining = deadline - clock.currentTimeMillis();
297297
}

lib/src/main/java/io/ably/lib/transport/ConnectionManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ public void run() {
995995
boolean pending;
996996
synchronized(heartbeatWaiters) {
997997
try {
998-
heartbeatWaiters.wait(HEARTBEAT_TIMEOUT);
998+
clock.waitOn(heartbeatWaiters, HEARTBEAT_TIMEOUT);
999999
} catch (InterruptedException ie) {
10001000
}
10011001
pending = clear();
@@ -1506,7 +1506,7 @@ private void tryWait(long timeout) {
15061506
if(timeout == 0) {
15071507
wait();
15081508
} else {
1509-
wait(timeout);
1509+
clock.waitOn(this, timeout);
15101510
}
15111511
} catch (InterruptedException e) {}
15121512
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
package io.ably.lib.util;
22

3+
/**
4+
* Abstraction over time-related operations used throughout the SDK.
5+
*
6+
* <p>The default implementation, {@link SystemClock}, delegates to the real system clock and
7+
* standard Java concurrency primitives. Tests and debug builds can supply an alternative
8+
* implementation (e.g. a fake/controllable clock) via {@link io.ably.lib.debug.DebugOptions#clock}
9+
* to drive time-dependent behaviour deterministically without sleeping.
10+
*/
311
public interface Clock {
12+
13+
/**
14+
* Returns the current wall-clock time in milliseconds since the Unix epoch
15+
* (1 January 1970 00:00:00 UTC), analogous to {@link System#currentTimeMillis()}.
16+
*
17+
* @return current time in milliseconds
18+
*/
419
long currentTimeMillis();
20+
21+
/**
22+
* Creates a new {@link NamedTimer} backed by this clock.
23+
*
24+
* <p>The name is used for diagnostic and logging purposes (e.g. as the underlying
25+
* {@link java.util.Timer} thread name).
26+
*
27+
* @param name a human-readable label for the timer; must not be {@code null}
28+
* @return a new {@link NamedTimer} instance ready to schedule tasks
29+
*/
530
NamedTimer newTimer(String name);
31+
32+
/**
33+
* Causes the current thread to wait until either another thread calls
34+
* {@link Object#notify()} / {@link Object#notifyAll()} on {@code target}, or the
35+
* specified timeout elapses — analogous to {@link Object#wait(long)}.
36+
*
37+
* <p>The caller must hold the monitor of {@code target} before invoking this method,
38+
* exactly as required by {@link Object#wait(long)}.
39+
*
40+
* @param target the object whose monitor the current thread holds and will wait on;
41+
* must not be {@code null}
42+
* @param timeout maximum time to wait in milliseconds; {@code 0} means wait indefinitely
43+
* @throws InterruptedException if the current thread is interrupted while waiting
44+
*/
45+
void waitOn(Object target, long timeout) throws InterruptedException;
646
}

lib/src/main/java/io/ably/lib/util/SystemClock.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ public void cancel() {
3131
};
3232
}
3333

34+
@Override
35+
public void waitOn(Object target, long timeout) throws InterruptedException {
36+
target.wait(timeout);
37+
}
38+
3439
public static Clock clockFrom(ClientOptions opts) {
3540
if (opts instanceof DebugOptions) {
3641
Clock c = ((DebugOptions) opts).clock;

uts/src/test/kotlin/io/ably/lib/test/mock/DefaultPendingRequest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import io.ably.lib.network.FailedConnectionException
44
import io.ably.lib.network.HttpBody
55
import io.ably.lib.network.HttpRequest
66
import io.ably.lib.network.HttpResponse
7+
import kotlin.time.Duration
78
import kotlinx.coroutines.CompletableDeferred
89
import java.net.SocketTimeoutException
9-
import java.time.Duration
1010

1111
internal class DefaultPendingRequest(
1212
private val request: HttpRequest,
@@ -34,7 +34,7 @@ internal class DefaultPendingRequest(
3434

3535
override fun respondWithDelay(delay: Duration, status: Int, body: Any) {
3636
Thread {
37-
Thread.sleep(delay.toMillis())
37+
Thread.sleep(delay.inWholeMilliseconds)
3838
respondWith(status, body)
3939
}.apply { isDaemon = true }.start()
4040
}

uts/src/test/kotlin/io/ably/lib/test/mock/FakeClock.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlin.time.Duration
1515
class FakeClock(initialTimeMs: Long = 0L) : Clock {
1616
@Volatile private var time = initialTimeMs
1717
private val timers = mutableMapOf<String, FakeNamedTimer>()
18+
private val waiters = mutableListOf<Waiter>()
1819

1920
override fun currentTimeMillis() = time
2021

@@ -24,10 +25,29 @@ class FakeClock(initialTimeMs: Long = 0L) : Clock {
2425
return t
2526
}
2627

28+
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
29+
override fun waitOn(target: Any, timeout: Long) {
30+
synchronized(waiters) {
31+
waiters.add(Waiter(target as Object, time + timeout))
32+
}
33+
(target as Object).wait()
34+
}
35+
2736
/** Advance virtual time by [ms] milliseconds, firing any timers that become due. */
2837
fun advance(ms: Long) {
2938
time += ms
3039
timers.values.forEach { it.fireDue(time) }
40+
val due = synchronized(waiters) {
41+
waiters.filter { it.fireAt <= time }.also {
42+
waiters.removeIf { it.fireAt <= time }
43+
}
44+
}
45+
// notifyAll() requires holding the target's monitor.
46+
due.forEach { waiter ->
47+
synchronized(waiter.target) {
48+
waiter.target.notifyAll()
49+
}
50+
}
3151
}
3252

3353
/** Advance virtual time by [time], firing any timers that become due. */
@@ -60,4 +80,7 @@ class FakeClock(initialTimeMs: Long = 0L) : Clock {
6080
}
6181

6282
class Scheduled(val task: TimerTask, val fireAt: Long)
83+
84+
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
85+
class Waiter(val target: Object, val fireAt: Long)
6386
}

uts/src/test/kotlin/io/ably/lib/test/mock/PendingRequest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.ably.lib.test.mock
22

3-
import java.time.Duration
3+
import kotlin.time.Duration
4+
45

56
/**
67
* An in-flight HTTP request that the test must resolve.

0 commit comments

Comments
 (0)