Merge changes I756cb844,I79b63c68 into main
* changes: Rename TimerFileDescriptor to RealtimeScheduler Enhance TimerFileDescriptor to schedule multiple tasks
This commit is contained in:
commit
12d6da67a0
4 changed files with 265 additions and 168 deletions
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.net.module.util;
|
||||
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.SystemClock;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.PriorityQueue;
|
||||
|
||||
/**
|
||||
* Represents a realtime scheduler object used for scheduling tasks with precise delays.
|
||||
* Compared to {@link Handler#postDelayed}, this class offers enhanced accuracy for delayed
|
||||
* callbacks by accounting for periods when the device is in deep sleep.
|
||||
*
|
||||
* <p> This class is designed for use exclusively from the handler thread.
|
||||
*
|
||||
* **Usage Examples:**
|
||||
*
|
||||
* ** Scheduling recurring tasks with the same RealtimeScheduler **
|
||||
*
|
||||
* ```java
|
||||
* // Create a RealtimeScheduler
|
||||
* final RealtimeScheduler scheduler = new RealtimeScheduler(handler);
|
||||
*
|
||||
* // Schedule a new task with a delay.
|
||||
* scheduler.postDelayed(() -> taskToExecute(), delayTime);
|
||||
*
|
||||
* // Once the delay has elapsed, and the task is running, schedule another task.
|
||||
* scheduler.postDelayed(() -> anotherTaskToExecute(), anotherDelayTime);
|
||||
*
|
||||
* // Remember to close the RealtimeScheduler after all tasks have finished running.
|
||||
* scheduler.close();
|
||||
* ```
|
||||
*/
|
||||
public class RealtimeScheduler {
|
||||
private static final String TAG = RealtimeScheduler.class.getSimpleName();
|
||||
// EVENT_ERROR may be generated even if not specified, as per its javadoc.
|
||||
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
|
||||
private final CloseGuard mGuard = new CloseGuard();
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
@NonNull
|
||||
private final MessageQueue mQueue;
|
||||
@NonNull
|
||||
private final ParcelFileDescriptor mParcelFileDescriptor;
|
||||
private final int mFdInt;
|
||||
|
||||
private final PriorityQueue<Task> mTaskQueue;
|
||||
|
||||
/**
|
||||
* An abstract class for defining tasks that can be executed using a {@link Handler}.
|
||||
*/
|
||||
private abstract static class Task implements Comparable<Task> {
|
||||
private final long mRunTimeMs;
|
||||
private final long mCreatedTimeNs = SystemClock.elapsedRealtimeNanos();
|
||||
|
||||
/**
|
||||
* create a task with a run time
|
||||
*/
|
||||
Task(long runTimeMs) {
|
||||
mRunTimeMs = runTimeMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the task using the provided {@link Handler}.
|
||||
*
|
||||
* @param handler The {@link Handler} to use for executing the task.
|
||||
*/
|
||||
abstract void post(Handler handler);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull Task o) {
|
||||
if (mRunTimeMs != o.mRunTimeMs) {
|
||||
return Long.compare(mRunTimeMs, o.mRunTimeMs);
|
||||
}
|
||||
return Long.compare(mCreatedTimeNs, o.mCreatedTimeNs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the run time of the task.
|
||||
*/
|
||||
public long getRunTimeMs() {
|
||||
return mRunTimeMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that sends a {@link Message} using a {@link Handler}.
|
||||
*/
|
||||
private static class MessageTask extends Task {
|
||||
private final Message mMessage;
|
||||
|
||||
MessageTask(Message message, long runTimeMs) {
|
||||
super(runTimeMs);
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the {@link Message} using the provided {@link Handler}.
|
||||
*
|
||||
* @param handler The {@link Handler} to use for sending the message.
|
||||
*/
|
||||
@Override
|
||||
public void post(Handler handler) {
|
||||
handler.sendMessage(mMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that posts a {@link Runnable} to a {@link Handler}.
|
||||
*/
|
||||
private static class RunnableTask extends Task {
|
||||
private final Runnable mRunnable;
|
||||
|
||||
RunnableTask(Runnable runnable, long runTimeMs) {
|
||||
super(runTimeMs);
|
||||
mRunnable = runnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts the {@link Runnable} to the provided {@link Handler}.
|
||||
*
|
||||
* @param handler The {@link Handler} to use for posting the runnable.
|
||||
*/
|
||||
@Override
|
||||
public void post(Handler handler) {
|
||||
handler.post(mRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The RealtimeScheduler constructor
|
||||
*
|
||||
* Note: The constructor is currently safe to call on another thread because it only sets final
|
||||
* members and registers the event to be called on the handler.
|
||||
*/
|
||||
public RealtimeScheduler(@NonNull Handler handler) {
|
||||
mFdInt = TimerFdUtils.createTimerFileDescriptor();
|
||||
mParcelFileDescriptor = ParcelFileDescriptor.adoptFd(mFdInt);
|
||||
mHandler = handler;
|
||||
mQueue = handler.getLooper().getQueue();
|
||||
mTaskQueue = new PriorityQueue<>();
|
||||
registerFdEventListener();
|
||||
|
||||
mGuard.open("close");
|
||||
}
|
||||
|
||||
private boolean enqueueTask(@NonNull Task task, long delayMs) {
|
||||
ensureRunningOnCorrectThread();
|
||||
if (delayMs <= 0L) {
|
||||
task.post(mHandler);
|
||||
return true;
|
||||
}
|
||||
if (mTaskQueue.isEmpty() || task.compareTo(mTaskQueue.peek()) < 0) {
|
||||
if (!TimerFdUtils.setExpirationTime(mFdInt, delayMs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
mTaskQueue.add(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a runnable to be executed after a specified delay.
|
||||
*
|
||||
* If delayMs is less than or equal to 0, the runnable will be executed immediately.
|
||||
*
|
||||
* @param runnable the runnable to be executed
|
||||
* @param delayMs the delay time in milliseconds
|
||||
* @return true if the task is scheduled successfully, false otherwise.
|
||||
*/
|
||||
public boolean postDelayed(@NonNull Runnable runnable, long delayMs) {
|
||||
return enqueueTask(new RunnableTask(runnable, SystemClock.elapsedRealtime() + delayMs),
|
||||
delayMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a scheduled runnable.
|
||||
*
|
||||
* @param runnable the runnable to be removed
|
||||
*/
|
||||
public void removeDelayedRunnable(@NonNull Runnable runnable) {
|
||||
ensureRunningOnCorrectThread();
|
||||
mTaskQueue.removeIf(task -> task instanceof RunnableTask
|
||||
&& ((RunnableTask) task).mRunnable == runnable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a message to be sent after a specified delay.
|
||||
*
|
||||
* If delayMs is less than or equal to 0, the message will be sent immediately.
|
||||
*
|
||||
* @param msg the message to be sent
|
||||
* @param delayMs the delay time in milliseconds
|
||||
* @return true if the message is scheduled successfully, false otherwise.
|
||||
*/
|
||||
public boolean sendDelayedMessage(Message msg, long delayMs) {
|
||||
|
||||
return enqueueTask(new MessageTask(msg, SystemClock.elapsedRealtime() + delayMs), delayMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a scheduled message.
|
||||
*
|
||||
* @param what the message to be removed
|
||||
*/
|
||||
public void removeDelayedMessage(int what) {
|
||||
ensureRunningOnCorrectThread();
|
||||
mTaskQueue.removeIf(task -> task instanceof MessageTask
|
||||
&& ((MessageTask) task).mMessage.what == what);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the RealtimeScheduler. This implementation closes the underlying
|
||||
* OS resources allocated to represent this stream.
|
||||
*/
|
||||
public void close() {
|
||||
ensureRunningOnCorrectThread();
|
||||
unregisterAndDestroyFd();
|
||||
}
|
||||
|
||||
private void registerFdEventListener() {
|
||||
mQueue.addOnFileDescriptorEventListener(
|
||||
mParcelFileDescriptor.getFileDescriptor(),
|
||||
FD_EVENTS,
|
||||
(fd, events) -> {
|
||||
if (!isRunning()) {
|
||||
return 0;
|
||||
}
|
||||
if ((events & EVENT_INPUT) != 0) {
|
||||
handleExpiration();
|
||||
}
|
||||
return FD_EVENTS;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isRunning() {
|
||||
return mParcelFileDescriptor.getFileDescriptor().valid();
|
||||
}
|
||||
|
||||
private void handleExpiration() {
|
||||
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||
while (!mTaskQueue.isEmpty()) {
|
||||
final Task task = mTaskQueue.peek();
|
||||
currentTimeMs = SystemClock.elapsedRealtime();
|
||||
if (currentTimeMs < task.getRunTimeMs()) {
|
||||
break;
|
||||
}
|
||||
task.post(mHandler);
|
||||
mTaskQueue.poll();
|
||||
}
|
||||
|
||||
|
||||
if (!mTaskQueue.isEmpty()) {
|
||||
// Using currentTimeMs ensures that the calculated expiration time
|
||||
// is always positive.
|
||||
if (!TimerFdUtils.setExpirationTime(mFdInt,
|
||||
mTaskQueue.peek().getRunTimeMs() - currentTimeMs)) {
|
||||
// If setting the expiration time fails, clear the task queue.
|
||||
Log.wtf(TAG, "Failed to set expiration time");
|
||||
mTaskQueue.clear();
|
||||
}
|
||||
} else {
|
||||
// We have to clean up the timer if no tasks are left. Otherwise, the timer will keep
|
||||
// being triggered.
|
||||
TimerFdUtils.setExpirationTime(mFdInt, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterAndDestroyFd() {
|
||||
if (mGuard != null) {
|
||||
mGuard.close();
|
||||
}
|
||||
|
||||
mQueue.removeOnFileDescriptorEventListener(mParcelFileDescriptor.getFileDescriptor());
|
||||
try {
|
||||
mParcelFileDescriptor.close();
|
||||
} catch (IOException exception) {
|
||||
Log.e(TAG, "close ParcelFileDescriptor failed. ", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureRunningOnCorrectThread() {
|
||||
if (mHandler.getLooper() != Looper.myLooper()) {
|
||||
throw new IllegalStateException(
|
||||
"Not running on Handler thread: " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("Finalize")
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (mGuard != null) {
|
||||
mGuard.warnIfOpen();
|
||||
}
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ android_library {
|
|||
"net-utils-device-common-struct-base",
|
||||
"net-utils-device-common-wear",
|
||||
"net-utils-service-connectivity",
|
||||
"truth",
|
||||
],
|
||||
libs: [
|
||||
"android.test.runner.stubs",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.net.module.util
|
||||
|
||||
import android.os.Build
|
||||
import android.os.ConditionVariable
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.SystemClock
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.tryTest
|
||||
import com.android.testutils.visibleOnHandlerThread
|
||||
import com.google.common.collect.Range
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.test.assertEquals
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@DevSdkIgnoreRunner.MonitorThreadLeak
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@SmallTest
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
class RealtimeSchedulerTest {
|
||||
|
||||
private val TIMEOUT_MS = 1000L
|
||||
private val TOLERANCE_MS = 50L
|
||||
private class TestHandler(looper: Looper) : Handler(looper) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
val pair = msg.obj as Pair<ConditionVariable, MutableList<Long>>
|
||||
val cv = pair.first
|
||||
cv.open()
|
||||
val executionTimes = pair.second
|
||||
executionTimes.add(SystemClock.elapsedRealtime())
|
||||
}
|
||||
}
|
||||
private val thread = HandlerThread(RealtimeSchedulerTest::class.simpleName).apply { start() }
|
||||
private val handler by lazy { TestHandler(thread.looper) }
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
thread.quitSafely()
|
||||
thread.join()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiplePostDelayedTasks() {
|
||||
val scheduler = RealtimeScheduler(handler)
|
||||
tryTest {
|
||||
val initialTimeMs = SystemClock.elapsedRealtime()
|
||||
val executionTimes = mutableListOf<Long>()
|
||||
val cv = ConditionVariable()
|
||||
handler.post {
|
||||
scheduler.postDelayed(
|
||||
{ executionTimes.add(SystemClock.elapsedRealtime() - initialTimeMs) }, 0)
|
||||
scheduler.postDelayed(
|
||||
{ executionTimes.add(SystemClock.elapsedRealtime() - initialTimeMs) }, 200)
|
||||
val toBeRemoved = Runnable {
|
||||
executionTimes.add(SystemClock.elapsedRealtime() - initialTimeMs)
|
||||
}
|
||||
scheduler.postDelayed(toBeRemoved, 250)
|
||||
scheduler.removeDelayedRunnable(toBeRemoved)
|
||||
scheduler.postDelayed(
|
||||
{ executionTimes.add(SystemClock.elapsedRealtime() - initialTimeMs) }, 100)
|
||||
scheduler.postDelayed({
|
||||
executionTimes.add(SystemClock.elapsedRealtime() - initialTimeMs)
|
||||
cv.open() }, 300)
|
||||
}
|
||||
cv.block(TIMEOUT_MS)
|
||||
assertEquals(4, executionTimes.size)
|
||||
assertThat(executionTimes[0]).isIn(Range.closed(0L, TOLERANCE_MS))
|
||||
assertThat(executionTimes[1]).isIn(Range.closed(100L, 100 + TOLERANCE_MS))
|
||||
assertThat(executionTimes[2]).isIn(Range.closed(200L, 200 + TOLERANCE_MS))
|
||||
assertThat(executionTimes[3]).isIn(Range.closed(300L, 300 + TOLERANCE_MS))
|
||||
} cleanup {
|
||||
visibleOnHandlerThread(handler) { scheduler.close() }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleSendDelayedMessages() {
|
||||
val scheduler = RealtimeScheduler(handler)
|
||||
tryTest {
|
||||
val MSG_ID_0 = 0
|
||||
val MSG_ID_1 = 1
|
||||
val MSG_ID_2 = 2
|
||||
val MSG_ID_3 = 3
|
||||
val MSG_ID_4 = 4
|
||||
val initialTimeMs = SystemClock.elapsedRealtime()
|
||||
val executionTimes = mutableListOf<Long>()
|
||||
val cv = ConditionVariable()
|
||||
handler.post {
|
||||
scheduler.sendDelayedMessage(
|
||||
Message.obtain(handler, MSG_ID_0, Pair(ConditionVariable(), executionTimes)), 0)
|
||||
scheduler.sendDelayedMessage(
|
||||
Message.obtain(handler, MSG_ID_1, Pair(ConditionVariable(), executionTimes)),
|
||||
200)
|
||||
scheduler.sendDelayedMessage(
|
||||
Message.obtain(handler, MSG_ID_4, Pair(ConditionVariable(), executionTimes)),
|
||||
250)
|
||||
scheduler.removeDelayedMessage(MSG_ID_4)
|
||||
scheduler.sendDelayedMessage(
|
||||
Message.obtain(handler, MSG_ID_2, Pair(ConditionVariable(), executionTimes)),
|
||||
100)
|
||||
scheduler.sendDelayedMessage(
|
||||
Message.obtain(handler, MSG_ID_3, Pair(cv, executionTimes)),
|
||||
300)
|
||||
}
|
||||
cv.block(TIMEOUT_MS)
|
||||
assertEquals(4, executionTimes.size)
|
||||
assertThat(executionTimes[0] - initialTimeMs).isIn(Range.closed(0L, TOLERANCE_MS))
|
||||
assertThat(executionTimes[1] - initialTimeMs)
|
||||
.isIn(Range.closed(100L, 100 + TOLERANCE_MS))
|
||||
assertThat(executionTimes[2] - initialTimeMs)
|
||||
.isIn(Range.closed(200L, 200 + TOLERANCE_MS))
|
||||
assertThat(executionTimes[3] - initialTimeMs)
|
||||
.isIn(Range.closed(300L, 300 + TOLERANCE_MS))
|
||||
} cleanup {
|
||||
visibleOnHandlerThread(handler) { scheduler.close() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.net.module.util
|
||||
|
||||
import android.os.Build
|
||||
import android.os.ConditionVariable
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.net.module.util.TimerFileDescriptor.ITask
|
||||
import com.android.net.module.util.TimerFileDescriptor.MessageTask
|
||||
import com.android.net.module.util.TimerFileDescriptor.RunnableTask
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.tryTest
|
||||
import com.android.testutils.visibleOnHandlerThread
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val MSG_TEST = 1
|
||||
|
||||
@DevSdkIgnoreRunner.MonitorThreadLeak
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@SmallTest
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
|
||||
class TimerFileDescriptorTest {
|
||||
private class TestHandler(looper: Looper) : Handler(looper) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
val cv = msg.obj as ConditionVariable
|
||||
cv.open()
|
||||
}
|
||||
}
|
||||
private val thread = HandlerThread(TimerFileDescriptorTest::class.simpleName).apply { start() }
|
||||
private val handler by lazy { TestHandler(thread.looper) }
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
thread.quitSafely()
|
||||
thread.join()
|
||||
}
|
||||
|
||||
private fun assertDelayedTaskPost(
|
||||
timerFd: TimerFileDescriptor,
|
||||
task: ITask,
|
||||
cv: ConditionVariable
|
||||
) {
|
||||
val delayTime = 10L
|
||||
val startTime1 = Instant.now()
|
||||
handler.post { timerFd.setDelayedTask(task, delayTime) }
|
||||
assertTrue(cv.block(100L /* timeoutMs*/))
|
||||
assertTrue(Duration.between(startTime1, Instant.now()).toMillis() >= delayTime)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetDelayedTask() {
|
||||
val timerFd = TimerFileDescriptor(handler)
|
||||
tryTest {
|
||||
// Verify the delayed task is executed with the self-implemented ITask
|
||||
val cv1 = ConditionVariable()
|
||||
assertDelayedTaskPost(timerFd, { cv1.open() }, cv1)
|
||||
|
||||
// Verify the delayed task is executed with the RunnableTask
|
||||
val cv2 = ConditionVariable()
|
||||
assertDelayedTaskPost(timerFd, RunnableTask{ cv2.open() }, cv2)
|
||||
|
||||
// Verify the delayed task is executed with the MessageTask
|
||||
val cv3 = ConditionVariable()
|
||||
assertDelayedTaskPost(timerFd, MessageTask(handler.obtainMessage(MSG_TEST, cv3)), cv3)
|
||||
} cleanup {
|
||||
visibleOnHandlerThread(handler) { timerFd.close() }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCancelTask() {
|
||||
// The task is posted and canceled within the same handler loop, so the short delay used
|
||||
// here won't cause flakes.
|
||||
val delayTime = 10L
|
||||
val timerFd = TimerFileDescriptor(handler)
|
||||
val cv = ConditionVariable()
|
||||
tryTest {
|
||||
handler.post {
|
||||
timerFd.setDelayedTask({ cv.open() }, delayTime)
|
||||
assertTrue(timerFd.hasDelayedTask())
|
||||
timerFd.cancelTask()
|
||||
assertFalse(timerFd.hasDelayedTask())
|
||||
}
|
||||
assertFalse(cv.block(20L /* timeoutMs*/))
|
||||
} cleanup {
|
||||
visibleOnHandlerThread(handler) { timerFd.close() }
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue