From 0f4d1ab4d91bb07403ba0b0b7b5bf77ddf0f6e2e Mon Sep 17 00:00:00 2001 From: Paul Hu Date: Wed, 26 Feb 2025 09:11:53 +0000 Subject: [PATCH] Implement RealtimeScheduler in DiscoveryExecutor This provides more accurate delay callbacks, thereby enhancing the executor's overall functionality. Bug: 366373064 Test: atest FrameworksNetTests NsdManagerTest Change-Id: I903851464a92427b6e256573d693bb89a96d013f --- .../connectivity/mdns/DiscoveryExecutor.java | 36 +++++++++++- .../mdns/MdnsDiscoveryManager.java | 2 +- .../mdns/DiscoveryExecutorTest.kt | 58 ++++++++++++++++--- 3 files changed, 83 insertions(+), 13 deletions(-) diff --git a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java index 21af1a14c9..5b6ec9dfbb 100644 --- a/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java +++ b/service-t/src/com/android/server/connectivity/mdns/DiscoveryExecutor.java @@ -27,6 +27,7 @@ import android.util.Pair; import androidx.annotation.GuardedBy; import com.android.net.module.util.HandlerUtils; +import com.android.net.module.util.RealtimeScheduler; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -49,7 +50,13 @@ public class DiscoveryExecutor implements Executor { @NonNull private final ArrayList> mPendingTasks = new ArrayList<>(); - DiscoveryExecutor(@Nullable Looper defaultLooper) { + @GuardedBy("mPendingTasks") + @Nullable + RealtimeScheduler mRealtimeScheduler; + @NonNull private final MdnsFeatureFlags mMdnsFeatureFlags; + + DiscoveryExecutor(@Nullable Looper defaultLooper, @NonNull MdnsFeatureFlags mdnsFeatureFlags) { + mMdnsFeatureFlags = mdnsFeatureFlags; if (defaultLooper != null) { this.mHandlerThread = null; synchronized (mPendingTasks) { @@ -62,7 +69,7 @@ public class DiscoveryExecutor implements Executor { synchronized (mPendingTasks) { mHandler = new Handler(getLooper()); for (Pair pendingTask : mPendingTasks) { - mHandler.postDelayed(pendingTask.first, pendingTask.second); + executeDelayed(pendingTask.first, pendingTask.second); } mPendingTasks.clear(); } @@ -95,15 +102,33 @@ public class DiscoveryExecutor implements Executor { /** Execute the given function after the specified amount of time elapses. */ public void executeDelayed(Runnable function, long delayMillis) { final Handler handler; + final RealtimeScheduler realtimeScheduler; synchronized (mPendingTasks) { if (this.mHandler == null) { mPendingTasks.add(Pair.create(function, delayMillis)); return; } else { handler = this.mHandler; + if (mMdnsFeatureFlags.mIsAccurateDelayCallbackEnabled + && this.mRealtimeScheduler == null) { + this.mRealtimeScheduler = new RealtimeScheduler(mHandler); + } + realtimeScheduler = this.mRealtimeScheduler; } } - handler.postDelayed(function, delayMillis); + if (realtimeScheduler != null) { + if (delayMillis == 0L) { + handler.post(function); + return; + } + if (HandlerUtils.isRunningOnHandlerThread(handler)) { + realtimeScheduler.postDelayed(function, delayMillis); + } else { + handler.post(() -> realtimeScheduler.postDelayed(function, delayMillis)); + } + } else { + handler.postDelayed(function, delayMillis); + } } /** Shutdown the thread if necessary. */ @@ -111,6 +136,11 @@ public class DiscoveryExecutor implements Executor { if (this.mHandlerThread != null) { this.mHandlerThread.quitSafely(); } + synchronized (mPendingTasks) { + if (mRealtimeScheduler != null) { + mRealtimeScheduler.close(); + } + } } /** diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java index 33bcb70d7e..8cd366206e 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java @@ -125,7 +125,7 @@ public class MdnsDiscoveryManager implements MdnsSocketClientBase.Callback { this.sharedLog = sharedLog; this.perSocketServiceTypeClients = new PerSocketServiceTypeClients(); this.mdnsFeatureFlags = mdnsFeatureFlags; - this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper()); + this.discoveryExecutor = new DiscoveryExecutor(socketClient.getLooper(), mdnsFeatureFlags); } /** diff --git a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt index 51539a0e18..67fb4280b1 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/DiscoveryExecutorTest.kt @@ -17,6 +17,7 @@ package com.android.server.connectivity.mdns import android.os.Build +import android.os.Handler import android.os.HandlerThread import android.testing.TestableLooper import com.android.testutils.DevSdkIgnoreRule @@ -36,6 +37,8 @@ private const val DEFAULT_TIMEOUT = 2000L @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2) class DiscoveryExecutorTest { private val thread = HandlerThread(DiscoveryExecutorTest::class.simpleName).apply { start() } + private val handler by lazy { Handler(thread.looper) } + private val testableLooper by lazy { TestableLooper(thread.looper) } @After fun tearDown() { @@ -45,8 +48,10 @@ class DiscoveryExecutorTest { @Test fun testCheckAndRunOnHandlerThread() { - val testableLooper = TestableLooper(thread.looper) - val executor = DiscoveryExecutor(testableLooper.looper) + val executor = DiscoveryExecutor( + testableLooper.looper, + MdnsFeatureFlags.newBuilder().build() + ) try { val future = CompletableFuture() executor.checkAndRunOnHandlerThread { future.complete(true) } @@ -58,17 +63,17 @@ class DiscoveryExecutorTest { // Create a DiscoveryExecutor with the null defaultLooper and verify the task can execute // normally. - val executor2 = DiscoveryExecutor(null /* defaultLooper */) + val executor2 = DiscoveryExecutor( + null /* defaultLooper */, + MdnsFeatureFlags.newBuilder().build() + ) val future2 = CompletableFuture() executor2.checkAndRunOnHandlerThread { future2.complete(true) } assertTrue(future2.get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)) executor2.shutDown() } - @Test - fun testExecute() { - val testableLooper = TestableLooper(thread.looper) - val executor = DiscoveryExecutor(testableLooper.looper) + private fun verifyExecute(executor: DiscoveryExecutor) { try { val future = CompletableFuture() executor.execute { future.complete(true) } @@ -80,10 +85,28 @@ class DiscoveryExecutorTest { } } + @Test + fun testExecute() { + verifyExecute(DiscoveryExecutor( + testableLooper.looper, + MdnsFeatureFlags.newBuilder().build() + )) + } + + @Test + fun testExecute_RealtimeScheduler() { + verifyExecute(DiscoveryExecutor( + testableLooper.looper, + MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build() + )) + } + @Test fun testExecuteDelayed() { - val testableLooper = TestableLooper(thread.looper) - val executor = DiscoveryExecutor(testableLooper.looper) + val executor = DiscoveryExecutor( + testableLooper.looper, + MdnsFeatureFlags.newBuilder().build() + ) try { // Verify the executeDelayed method val future = CompletableFuture() @@ -107,4 +130,21 @@ class DiscoveryExecutorTest { testableLooper.destroy() } } + + @Test + fun testExecuteDelayed_RealtimeScheduler() { + val executor = DiscoveryExecutor( + thread.looper, + MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build() + ) + try { + // Verify the executeDelayed method + val future = CompletableFuture() + // Schedule a task with 50ms delay + executor.executeDelayed({ future.complete(true) }, 50L) + assertTrue(future.get(500L, TimeUnit.MILLISECONDS)) + } finally { + testableLooper.destroy() + } + } }