From a3a34058fe86ef8bfe50c8cfb31d0d980bc4d5b5 Mon Sep 17 00:00:00 2001 From: Paul Hu Date: Wed, 1 Jan 2025 18:56:45 -0800 Subject: [PATCH] Revert^2 "Use timerfd to improve the accuracy of query timing" This reverts commit a481dc184fd75f5118578624b5c0ed8ec24cdf7b. Reason for revert: Update this change to ensure proper closure of the timerfd object. Change-Id: I14530ba77a39c77b4c7af6855ec77ba4907ab9f9 --- .../connectivity/mdns/MdnsFeatureFlags.java | 31 +++++ .../mdns/MdnsServiceTypeClient.java | 90 +++++++++---- staticlibs/Android.bp | 4 + .../net/module/util/RealtimeScheduler.java | 27 +++- .../mdns/MdnsServiceTypeClientTests.java | 124 ++++++++++++++++-- 5 files changed, 236 insertions(+), 40 deletions(-) diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java index 2f3bdc5530..1cf5e4d854 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java @@ -95,6 +95,11 @@ public class MdnsFeatureFlags { "nsd_cached_services_retention_time"; public static final int DEFAULT_CACHED_SERVICES_RETENTION_TIME_MILLISECONDS = 10000; + /** + * A feature flag to control whether the accurate delay callback should be enabled. + */ + public static final String NSD_ACCURATE_DELAY_CALLBACK = "nsd_accurate_delay_callback"; + // Flag for offload feature public final boolean mIsMdnsOffloadFeatureEnabled; @@ -128,6 +133,9 @@ public class MdnsFeatureFlags { // Retention Time for cached services public final long mCachedServicesRetentionTime; + // Flag for accurate delay callback + public final boolean mIsAccurateDelayCallbackEnabled; + // Flag to use shorter (16 characters + .local) hostnames public final boolean mIsShortHostnamesEnabled; @@ -230,6 +238,14 @@ public class MdnsFeatureFlags { return mIsShortHostnamesEnabled || isForceEnabledForTest(NSD_USE_SHORT_HOSTNAMES); } + /** + * Indicates whether {@link #NSD_ACCURATE_DELAY_CALLBACK} is enabled, including for testing. + */ + public boolean isAccurateDelayCallbackEnabled() { + return mIsAccurateDelayCallbackEnabled + || isForceEnabledForTest(NSD_ACCURATE_DELAY_CALLBACK); + } + /** * The constructor for {@link MdnsFeatureFlags}. */ @@ -244,6 +260,7 @@ public class MdnsFeatureFlags { boolean avoidAdvertisingEmptyTxtRecords, boolean isCachedServicesRemovalEnabled, long cachedServicesRetentionTime, + boolean isAccurateDelayCallbackEnabled, boolean isShortHostnamesEnabled, @Nullable FlagOverrideProvider overrideProvider) { mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled; @@ -257,6 +274,7 @@ public class MdnsFeatureFlags { mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords; mIsCachedServicesRemovalEnabled = isCachedServicesRemovalEnabled; mCachedServicesRetentionTime = cachedServicesRetentionTime; + mIsAccurateDelayCallbackEnabled = isAccurateDelayCallbackEnabled; mIsShortHostnamesEnabled = isShortHostnamesEnabled; mOverrideProvider = overrideProvider; } @@ -281,6 +299,7 @@ public class MdnsFeatureFlags { private boolean mAvoidAdvertisingEmptyTxtRecords; private boolean mIsCachedServicesRemovalEnabled; private long mCachedServicesRetentionTime; + private boolean mIsAccurateDelayCallbackEnabled; private boolean mIsShortHostnamesEnabled; private FlagOverrideProvider mOverrideProvider; @@ -299,6 +318,7 @@ public class MdnsFeatureFlags { mAvoidAdvertisingEmptyTxtRecords = true; // Default enabled. mIsCachedServicesRemovalEnabled = false; mCachedServicesRetentionTime = DEFAULT_CACHED_SERVICES_RETENTION_TIME_MILLISECONDS; + mIsAccurateDelayCallbackEnabled = false; mIsShortHostnamesEnabled = true; // Default enabled. mOverrideProvider = null; } @@ -425,6 +445,16 @@ public class MdnsFeatureFlags { return this; } + /** + * Set whether the accurate delay callback is enabled. + * + * @see #NSD_ACCURATE_DELAY_CALLBACK + */ + public Builder setIsAccurateDelayCallbackEnabled(boolean isAccurateDelayCallbackEnabled) { + mIsAccurateDelayCallbackEnabled = isAccurateDelayCallbackEnabled; + return this; + } + /** * Set whether the short hostnames feature is enabled. * @@ -450,6 +480,7 @@ public class MdnsFeatureFlags { mAvoidAdvertisingEmptyTxtRecords, mIsCachedServicesRemovalEnabled, mCachedServicesRetentionTime, + mIsAccurateDelayCallbackEnabled, mIsShortHostnamesEnabled, mOverrideProvider); } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java index 8c86fb8324..56d4b9aff2 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java @@ -20,6 +20,7 @@ import static com.android.net.module.util.HandlerUtils.ensureRunningOnHandlerThr import static com.android.server.connectivity.mdns.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE; import static com.android.server.connectivity.mdns.MdnsServiceCache.ServiceExpiredCallback; import static com.android.server.connectivity.mdns.MdnsServiceCache.findMatchedResponse; +import static com.android.server.connectivity.mdns.MdnsQueryScheduler.ScheduledQueryTaskArgs; import static com.android.server.connectivity.mdns.util.MdnsUtils.Clock; import static com.android.server.connectivity.mdns.util.MdnsUtils.buildMdnsServiceInfoFromResponse; @@ -36,6 +37,7 @@ import androidx.annotation.VisibleForTesting; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.DnsUtils; +import com.android.net.module.util.RealtimeScheduler; import com.android.net.module.util.SharedLog; import com.android.server.connectivity.mdns.util.MdnsUtils; @@ -94,6 +96,9 @@ public class MdnsServiceTypeClient { private final boolean removeServiceAfterTtlExpires = MdnsConfigs.removeServiceAfterTtlExpires(); private final Clock clock; + // Use RealtimeScheduler for query scheduling, which allows for more accurate sending of + // queries. + @Nullable private final RealtimeScheduler realtimeScheduler; @Nullable private MdnsSearchOptions searchOptions; @@ -139,8 +144,7 @@ public class MdnsServiceTypeClient { public void handleMessage(Message msg) { switch (msg.what) { case EVENT_START_QUERYTASK: { - final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs = - (MdnsQueryScheduler.ScheduledQueryTaskArgs) msg.obj; + final ScheduledQueryTaskArgs taskArgs = (ScheduledQueryTaskArgs) msg.obj; // QueryTask should be run immediately after being created (not be scheduled in // advance). Because the result of "makeResponsesForResolve" depends on answers // that were received before it is called, so to take into account all answers @@ -174,7 +178,7 @@ public class MdnsServiceTypeClient { final long now = clock.elapsedRealtime(); lastSentTime = now; final long minRemainingTtl = getMinRemainingTtl(now); - MdnsQueryScheduler.ScheduledQueryTaskArgs args = + final ScheduledQueryTaskArgs args = mdnsQueryScheduler.scheduleNextRun( sentResult.taskArgs.config, minRemainingTtl, @@ -189,10 +193,14 @@ public class MdnsServiceTypeClient { sharedLog.log(String.format("Query sent with transactionId: %d. " + "Next run: sessionId: %d, in %d ms", sentResult.transactionId, args.sessionId, timeToNextTaskMs)); - dependencies.sendMessageDelayed( - handler, - handler.obtainMessage(EVENT_START_QUERYTASK, args), - timeToNextTaskMs); + if (realtimeScheduler != null) { + setDelayedTask(args, timeToNextTaskMs); + } else { + dependencies.sendMessageDelayed( + handler, + handler.obtainMessage(EVENT_START_QUERYTASK, args), + timeToNextTaskMs); + } break; } default: @@ -254,6 +262,14 @@ public class MdnsServiceTypeClient { return List.of(new DatagramPacket(queryBuffer, 0, queryBuffer.length, address)); } } + + /** + * @see RealtimeScheduler + */ + @Nullable + public RealtimeScheduler createRealtimeScheduler(@NonNull Handler handler) { + return new RealtimeScheduler(handler); + } } /** @@ -301,6 +317,8 @@ public class MdnsServiceTypeClient { this.mdnsQueryScheduler = new MdnsQueryScheduler(); this.cacheKey = new MdnsServiceCache.CacheKey(serviceType, socketKey); this.featureFlags = featureFlags; + this.realtimeScheduler = featureFlags.isAccurateDelayCallbackEnabled() + ? dependencies.createRealtimeScheduler(handler) : null; } /** @@ -310,6 +328,9 @@ public class MdnsServiceTypeClient { removeScheduledTask(); mdnsQueryScheduler.cancelScheduledRun(); serviceCache.unregisterServiceExpiredCallback(cacheKey); + if (realtimeScheduler != null) { + realtimeScheduler.close(); + } } private List getExistingServices() { @@ -317,6 +338,12 @@ public class MdnsServiceTypeClient { ? serviceCache.getCachedServices(cacheKey) : Collections.emptyList(); } + private void setDelayedTask(ScheduledQueryTaskArgs args, long timeToNextTaskMs) { + realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK); + realtimeScheduler.sendDelayedMessage( + handler.obtainMessage(EVENT_START_QUERYTASK, args), timeToNextTaskMs); + } + /** * Registers {@code listener} for receiving discovery event of mDNS service instances, and * starts @@ -363,7 +390,7 @@ public class MdnsServiceTypeClient { } final long minRemainingTtl = getMinRemainingTtl(now); if (hadReply) { - MdnsQueryScheduler.ScheduledQueryTaskArgs args = + final ScheduledQueryTaskArgs args = mdnsQueryScheduler.scheduleNextRun( taskConfig, minRemainingTtl, @@ -377,10 +404,14 @@ public class MdnsServiceTypeClient { final long timeToNextTaskMs = calculateTimeToNextTask(args, now); sharedLog.log(String.format("Schedule a query. Next run: sessionId: %d, in %d ms", args.sessionId, timeToNextTaskMs)); - dependencies.sendMessageDelayed( - handler, - handler.obtainMessage(EVENT_START_QUERYTASK, args), - timeToNextTaskMs); + if (realtimeScheduler != null) { + setDelayedTask(args, timeToNextTaskMs); + } else { + dependencies.sendMessageDelayed( + handler, + handler.obtainMessage(EVENT_START_QUERYTASK, args), + timeToNextTaskMs); + } } else { final List servicesToResolve = makeResponsesForResolve(socketKey); final QueryTask queryTask = new QueryTask( @@ -420,7 +451,11 @@ public class MdnsServiceTypeClient { } private void removeScheduledTask() { - dependencies.removeMessages(handler, EVENT_START_QUERYTASK); + if (realtimeScheduler != null) { + realtimeScheduler.removeDelayedMessage(EVENT_START_QUERYTASK); + } else { + dependencies.removeMessages(handler, EVENT_START_QUERYTASK); + } sharedLog.log("Remove EVENT_START_QUERYTASK" + ", current session: " + currentSessionId); ++currentSessionId; @@ -506,10 +541,13 @@ public class MdnsServiceTypeClient { } } } - if (dependencies.hasMessages(handler, EVENT_START_QUERYTASK)) { + final boolean hasScheduledTask = realtimeScheduler != null + ? realtimeScheduler.hasDelayedMessage(EVENT_START_QUERYTASK) + : dependencies.hasMessages(handler, EVENT_START_QUERYTASK); + if (hasScheduledTask) { final long now = clock.elapsedRealtime(); final long minRemainingTtl = getMinRemainingTtl(now); - MdnsQueryScheduler.ScheduledQueryTaskArgs args = + final ScheduledQueryTaskArgs args = mdnsQueryScheduler.maybeRescheduleCurrentRun(now, minRemainingTtl, lastSentTime, currentSessionId + 1, searchOptions.numOfQueriesBeforeBackoff()); @@ -518,10 +556,14 @@ public class MdnsServiceTypeClient { final long timeToNextTaskMs = calculateTimeToNextTask(args, now); sharedLog.log(String.format("Reschedule a query. Next run: sessionId: %d, in %d ms", args.sessionId, timeToNextTaskMs)); - dependencies.sendMessageDelayed( - handler, - handler.obtainMessage(EVENT_START_QUERYTASK, args), - timeToNextTaskMs); + if (realtimeScheduler != null) { + setDelayedTask(args, timeToNextTaskMs); + } else { + dependencies.sendMessageDelayed( + handler, + handler.obtainMessage(EVENT_START_QUERYTASK, args), + timeToNextTaskMs); + } } } } @@ -686,10 +728,10 @@ public class MdnsServiceTypeClient { private static class QuerySentArguments { private final int transactionId; private final List subTypes = new ArrayList<>(); - private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs; + private final ScheduledQueryTaskArgs taskArgs; QuerySentArguments(int transactionId, @NonNull List subTypes, - @NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs) { + @NonNull ScheduledQueryTaskArgs taskArgs) { this.transactionId = transactionId; this.subTypes.addAll(subTypes); this.taskArgs = taskArgs; @@ -698,14 +740,14 @@ public class MdnsServiceTypeClient { // A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task. private class QueryTask implements Runnable { - private final MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs; + private final ScheduledQueryTaskArgs taskArgs; private final List servicesToResolve = new ArrayList<>(); private final List subtypes = new ArrayList<>(); private final boolean sendDiscoveryQueries; private final List existingServices = new ArrayList<>(); private final boolean onlyUseIpv6OnIpv6OnlyNetworks; private final SocketKey socketKey; - QueryTask(@NonNull MdnsQueryScheduler.ScheduledQueryTaskArgs taskArgs, + QueryTask(@NonNull ScheduledQueryTaskArgs taskArgs, @NonNull Collection servicesToResolve, @NonNull Collection subtypes, boolean sendDiscoveryQueries, @NonNull Collection existingServices, @@ -771,7 +813,7 @@ public class MdnsServiceTypeClient { return minRemainingTtl == Long.MAX_VALUE ? 0 : minRemainingTtl; } - private static long calculateTimeToNextTask(MdnsQueryScheduler.ScheduledQueryTaskArgs args, + private static long calculateTimeToNextTask(ScheduledQueryTaskArgs args, long now) { return Math.max(args.timeToRun - now, 0); } diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp index 0eab6e74d6..8034e57e2a 100644 --- a/staticlibs/Android.bp +++ b/staticlibs/Android.bp @@ -438,7 +438,11 @@ java_library { srcs: [ "device/com/android/net/module/util/FdEventsReader.java", "device/com/android/net/module/util/HandlerUtils.java", + "device/com/android/net/module/util/JniUtil.java", + "device/com/android/net/module/util/RealtimeScheduler.java", "device/com/android/net/module/util/SharedLog.java", + "device/com/android/net/module/util/ServiceConnectivityJni.java", + "device/com/android/net/module/util/TimerFdUtils.java", "framework/com/android/net/module/util/ByteUtils.java", "framework/com/android/net/module/util/CollectionUtils.java", "framework/com/android/net/module/util/DnsUtils.java", diff --git a/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java b/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java index c8fdf72f7b..2d952237fa 100644 --- a/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java +++ b/staticlibs/device/com/android/net/module/util/RealtimeScheduler.java @@ -227,6 +227,13 @@ public class RealtimeScheduler { return enqueueTask(new MessageTask(msg, SystemClock.elapsedRealtime() + delayMs), delayMs); } + private static boolean isMessageTask(Task task, int what) { + if (task instanceof MessageTask && ((MessageTask) task).mMessage.what == what) { + return true; + } + return false; + } + /** * Remove a scheduled message. * @@ -234,8 +241,24 @@ public class RealtimeScheduler { */ public void removeDelayedMessage(int what) { ensureRunningOnCorrectThread(); - mTaskQueue.removeIf(task -> task instanceof MessageTask - && ((MessageTask) task).mMessage.what == what); + mTaskQueue.removeIf(task -> isMessageTask(task, what)); + } + + /** + * Check if there is a scheduled message. + * + * @param what the message to be checked + * @return true if there is a target message, false otherwise. + */ + public boolean hasDelayedMessage(int what) { + ensureRunningOnCorrectThread(); + + for (Task task : mTaskQueue) { + if (isMessageTask(task, what)) { + return true; + } + } + return false; } /** diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java index 67f9d9cc66..dad03e0631 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java @@ -59,6 +59,7 @@ import android.os.Message; import android.text.TextUtils; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.RealtimeScheduler; import com.android.net.module.util.SharedLog; import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry; import com.android.server.connectivity.mdns.util.MdnsUtils; @@ -127,6 +128,8 @@ public class MdnsServiceTypeClientTests { private SharedLog mockSharedLog; @Mock private MdnsServiceTypeClient.Dependencies mockDeps; + @Mock + private RealtimeScheduler mockRealtimeScheduler; @Captor private ArgumentCaptor serviceInfoCaptor; @@ -145,6 +148,7 @@ public class MdnsServiceTypeClientTests { private Message delayMessage = null; private Handler realHandler = null; private MdnsFeatureFlags featureFlags = MdnsFeatureFlags.newBuilder().build(); + private Message message = null; @Before @SuppressWarnings("DoNotMock") @@ -244,10 +248,21 @@ public class MdnsServiceTypeClientTests { return true; }).when(mockDeps).sendMessage(any(Handler.class), any(Message.class)); - client = makeMdnsServiceTypeClient(); + doAnswer(inv -> { + realHandler = (Handler) inv.getArguments()[0]; + return mockRealtimeScheduler; + }).when(mockDeps).createRealtimeScheduler(any(Handler.class)); + + doAnswer(inv -> { + message = (Message) inv.getArguments()[0]; + latestDelayMs = (long) inv.getArguments()[1]; + return null; + }).when(mockRealtimeScheduler).sendDelayedMessage(any(), anyLong()); + + client = makeMdnsServiceTypeClient(featureFlags); } - private MdnsServiceTypeClient makeMdnsServiceTypeClient() { + private MdnsServiceTypeClient makeMdnsServiceTypeClient(MdnsFeatureFlags featureFlags) { return new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps, serviceCache, featureFlags); @@ -1926,9 +1941,7 @@ public class MdnsServiceTypeClientTests { @Test public void testSendQueryWithKnownAnswers() throws Exception { - client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor, - mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps, - serviceCache, + client = makeMdnsServiceTypeClient( MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build()); doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket( @@ -1990,9 +2003,7 @@ public class MdnsServiceTypeClientTests { @Test public void testSendQueryWithSubTypeWithKnownAnswers() throws Exception { - client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor, - mockDecoderClock, socketKey, mockSharedLog, thread.getLooper(), mockDeps, - serviceCache, + client = makeMdnsServiceTypeClient( MdnsFeatureFlags.newBuilder().setIsQueryWithKnownAnswerEnabled(true).build()); doCallRealMethod().when(mockDeps).getDatagramPacketsFromMdnsPacket( @@ -2114,6 +2125,73 @@ public class MdnsServiceTypeClientTests { assertEquals(9680L, latestDelayMs); } + @Test + public void sendQueries_AccurateDelayCallback() { + client = makeMdnsServiceTypeClient( + MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()); + + final int numOfQueriesBeforeBackoff = 2; + final MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder() + .addSubtype(SUBTYPE) + .setQueryMode(AGGRESSIVE_QUERY_MODE) + .setNumOfQueriesBeforeBackoff(numOfQueriesBeforeBackoff) + .build(); + startSendAndReceive(mockListenerOne, searchOptions); + verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK); + + // Verify that the first query has been sent. + verifyAndSendQuery(0 /* index */, 0 /* timeInMs */, true /* expectsUnicastResponse */, + true /* multipleSocketDiscovery */, 1 /* scheduledCount */, + 1 /* sendMessageCount */, true /* useAccurateDelayCallback */); + + // Verify that the second query has been sent + verifyAndSendQuery(1 /* index */, 0 /* timeInMs */, false /* expectsUnicastResponse */, + true /* multipleSocketDiscovery */, 2 /* scheduledCount */, + 2 /* sendMessageCount */, true /* useAccurateDelayCallback */); + + // Verify that the third query has been sent + verifyAndSendQuery(2 /* index */, TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS, + false /* expectsUnicastResponse */, true /* multipleSocketDiscovery */, + 3 /* scheduledCount */, 3 /* sendMessageCount */, + true /* useAccurateDelayCallback */); + + // In backoff mode, the current scheduled task will be canceled and reschedule if the + // 0.8 * smallestRemainingTtl is larger than time to next run. + long currentTime = TEST_TTL / 2 + TEST_ELAPSED_REALTIME; + doReturn(currentTime).when(mockDecoderClock).elapsedRealtime(); + doReturn(true).when(mockRealtimeScheduler).hasDelayedMessage(EVENT_START_QUERYTASK); + processResponse(createResponse( + "service-instance-1", "192.0.2.123", 5353, + SERVICE_TYPE_LABELS, + Collections.emptyMap(), TEST_TTL), socketKey); + // Verify that the message removal occurred. + verify(mockRealtimeScheduler, times(6)).removeDelayedMessage(EVENT_START_QUERYTASK); + assertNotNull(message); + verifyAndSendQuery(3 /* index */, (long) (TEST_TTL / 2 * 0.8) /* timeInMs */, + true /* expectsUnicastResponse */, true /* multipleSocketDiscovery */, + 5 /* scheduledCount */, 4 /* sendMessageCount */, + true /* useAccurateDelayCallback */); + + // Stop sending packets. + stopSendAndReceive(mockListenerOne); + verify(mockRealtimeScheduler, times(8)).removeDelayedMessage(EVENT_START_QUERYTASK); + } + + @Test + public void testTimerFdCloseProperly() { + client = makeMdnsServiceTypeClient( + MdnsFeatureFlags.newBuilder().setIsAccurateDelayCallbackEnabled(true).build()); + + // Start query + startSendAndReceive(mockListenerOne, MdnsSearchOptions.newBuilder().build()); + verify(mockRealtimeScheduler, times(1)).removeDelayedMessage(EVENT_START_QUERYTASK); + + // Stop query and verify the close() method has been called. + stopSendAndReceive(mockListenerOne); + verify(mockRealtimeScheduler, times(2)).removeDelayedMessage(EVENT_START_QUERYTASK); + verify(mockRealtimeScheduler).close(); + } + private static MdnsServiceInfo matchServiceName(String name) { return argThat(info -> info.getServiceInstanceName().equals(name)); } @@ -2127,9 +2205,22 @@ public class MdnsServiceTypeClientTests { private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse, boolean multipleSocketDiscovery, int scheduledCount) { - // Dispatch the message - if (delayMessage != null && realHandler != null) { - dispatchMessage(); + verifyAndSendQuery(index, timeInMs, expectsUnicastResponse, + multipleSocketDiscovery, scheduledCount, index + 1 /* sendMessageCount */, + false /* useAccurateDelayCallback */); + } + + private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse, + boolean multipleSocketDiscovery, int scheduledCount, int sendMessageCount, + boolean useAccurateDelayCallback) { + if (useAccurateDelayCallback && message != null && realHandler != null) { + runOnHandler(() -> realHandler.dispatchMessage(message)); + message = null; + } else { + // Dispatch the message + if (delayMessage != null && realHandler != null) { + dispatchMessage(); + } } assertEquals(timeInMs, latestDelayMs); currentThreadExecutor.getAndClearLastScheduledRunnable().run(); @@ -2152,11 +2243,16 @@ public class MdnsServiceTypeClientTests { eq(socketKey), eq(false)); } } - verify(mockDeps, times(index + 1)) + verify(mockDeps, times(sendMessageCount)) .sendMessage(any(Handler.class), any(Message.class)); // Verify the task has been scheduled. - verify(mockDeps, times(scheduledCount)) - .sendMessageDelayed(any(Handler.class), any(Message.class), anyLong()); + if (useAccurateDelayCallback) { + verify(mockRealtimeScheduler, times(scheduledCount)) + .sendDelayedMessage(any(), anyLong()); + } else { + verify(mockDeps, times(scheduledCount)) + .sendMessageDelayed(any(Handler.class), any(Message.class), anyLong()); + } } private static String[] getTestServiceName(String instanceName) {