Adjust query frequency based on remaining TTL
After numOfQueriesBeforeBackoff query, the mDNS discovery logic will enter backoff mode. In backoff mode, the query frequency will be updated to max(20, 0.8 * shortest remaining TTL) seconds. It will help to reduce mDNS query frequency in certain use cases. Bug: 284480315 Test: atest CtsNetTest FrameworksNetTests Change-Id: Iac8baaaf58cf9b3b8e67e1cd80402fdecde1d3d4
This commit is contained in:
@@ -50,7 +50,8 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
source.readBoolean(),
|
||||
source.readParcelable(null),
|
||||
source.readString(),
|
||||
(source.dataAvail() > 0) ? source.readBoolean() : false);
|
||||
source.readBoolean(),
|
||||
source.readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,9 +63,9 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
private final List<String> subtypes;
|
||||
@Nullable
|
||||
private final String resolveInstanceName;
|
||||
|
||||
private final boolean isPassiveMode;
|
||||
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
private final int numOfQueriesBeforeBackoff;
|
||||
private final boolean removeExpiredService;
|
||||
// The target network for searching. Null network means search on all possible interfaces.
|
||||
@Nullable private final Network mNetwork;
|
||||
@@ -76,13 +77,15 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
boolean removeExpiredService,
|
||||
@Nullable Network network,
|
||||
@Nullable String resolveInstanceName,
|
||||
boolean onlyUseIpv6OnIpv6OnlyNetworks) {
|
||||
boolean onlyUseIpv6OnIpv6OnlyNetworks,
|
||||
int numOfQueriesBeforeBackoff) {
|
||||
this.subtypes = new ArrayList<>();
|
||||
if (subtypes != null) {
|
||||
this.subtypes.addAll(subtypes);
|
||||
}
|
||||
this.isPassiveMode = isPassiveMode;
|
||||
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
|
||||
this.removeExpiredService = removeExpiredService;
|
||||
mNetwork = network;
|
||||
this.resolveInstanceName = resolveInstanceName;
|
||||
@@ -122,6 +125,14 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
return onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of queries should be executed before backoff mode is enabled.
|
||||
* The default number is 3 if it is not set.
|
||||
*/
|
||||
public int numOfQueriesBeforeBackoff() {
|
||||
return numOfQueriesBeforeBackoff;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if service will be removed after its TTL expires. */
|
||||
public boolean removeExpiredService() {
|
||||
return removeExpiredService;
|
||||
@@ -159,6 +170,7 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
out.writeParcelable(mNetwork, 0);
|
||||
out.writeString(resolveInstanceName);
|
||||
out.writeBoolean(onlyUseIpv6OnIpv6OnlyNetworks);
|
||||
out.writeInt(numOfQueriesBeforeBackoff);
|
||||
}
|
||||
|
||||
/** A builder to create {@link MdnsSearchOptions}. */
|
||||
@@ -166,6 +178,7 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
private final Set<String> subtypes;
|
||||
private boolean isPassiveMode = true;
|
||||
private boolean onlyUseIpv6OnIpv6OnlyNetworks = false;
|
||||
private int numOfQueriesBeforeBackoff = 3;
|
||||
private boolean removeExpiredService;
|
||||
private Network mNetwork;
|
||||
private String resolveInstanceName;
|
||||
@@ -218,6 +231,14 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the query backoff mode should be turned on.
|
||||
*/
|
||||
public Builder setNumOfQueriesBeforeBackoff(int numOfQueriesBeforeBackoff) {
|
||||
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the service should be removed after TTL.
|
||||
*
|
||||
@@ -258,7 +279,8 @@ public class MdnsSearchOptions implements Parcelable {
|
||||
removeExpiredService,
|
||||
mNetwork,
|
||||
resolveInstanceName,
|
||||
onlyUseIpv6OnIpv6OnlyNetworks);
|
||||
onlyUseIpv6OnIpv6OnlyNetworks,
|
||||
numOfQueriesBeforeBackoff);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
*/
|
||||
public class MdnsServiceTypeClient {
|
||||
|
||||
private static final String TAG = MdnsServiceTypeClient.class.getSimpleName();
|
||||
private static final int DEFAULT_MTU = 1500;
|
||||
|
||||
private final String serviceType;
|
||||
@@ -63,6 +64,7 @@ public class MdnsServiceTypeClient {
|
||||
private final ArrayMap<MdnsServiceBrowserListener, MdnsSearchOptions> listeners =
|
||||
new ArrayMap<>();
|
||||
// TODO: change instanceNameToResponse to TreeMap with case insensitive comparator.
|
||||
@GuardedBy("lock")
|
||||
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
|
||||
private final boolean removeServiceAfterTtlExpires =
|
||||
MdnsConfigs.removeServiceAfterTtlExpires();
|
||||
@@ -77,7 +79,14 @@ public class MdnsServiceTypeClient {
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Future<?> requestTaskFuture;
|
||||
private Future<?> nextQueryTaskFuture;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private QueryTask lastScheduledTask;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private long lastSentTime;
|
||||
|
||||
/**
|
||||
* Constructor of {@link MdnsServiceTypeClient}.
|
||||
@@ -189,7 +198,7 @@ public class MdnsServiceTypeClient {
|
||||
}
|
||||
}
|
||||
// Cancel the next scheduled periodical task.
|
||||
if (requestTaskFuture != null) {
|
||||
if (nextQueryTaskFuture != null) {
|
||||
cancelRequestTaskLocked();
|
||||
}
|
||||
// Keep tracking the ScheduledFuture for the task so we can cancel it if caller is not
|
||||
@@ -198,21 +207,40 @@ public class MdnsServiceTypeClient {
|
||||
searchOptions.getSubtypes(),
|
||||
searchOptions.isPassiveMode(),
|
||||
searchOptions.onlyUseIpv6OnIpv6OnlyNetworks(),
|
||||
currentSessionId,
|
||||
searchOptions.numOfQueriesBeforeBackoff(),
|
||||
socketKey);
|
||||
final long now = clock.elapsedRealtime();
|
||||
if (lastSentTime == 0) {
|
||||
lastSentTime = now;
|
||||
}
|
||||
if (hadReply) {
|
||||
requestTaskFuture = scheduleNextRunLocked(taskConfig);
|
||||
final QueryTaskConfig queryTaskConfig = taskConfig.getConfigForNextRun();
|
||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
||||
final long timeToRun = now + queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
||||
nextQueryTaskFuture = scheduleNextRunLocked(queryTaskConfig,
|
||||
minRemainingTtl, now, timeToRun, currentSessionId);
|
||||
} else {
|
||||
requestTaskFuture = executor.submit(new QueryTask(taskConfig));
|
||||
lastScheduledTask = new QueryTask(taskConfig,
|
||||
now /* timeToRun */,
|
||||
now + getMinRemainingTtlLocked(now)/* minTtlExpirationTimeWhenScheduled */,
|
||||
currentSessionId);
|
||||
nextQueryTaskFuture = executor.submit(lastScheduledTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void cancelRequestTaskLocked() {
|
||||
requestTaskFuture.cancel(true);
|
||||
final boolean canceled = nextQueryTaskFuture.cancel(true);
|
||||
sharedLog.log("task canceled:" + canceled + ", current session: " + currentSessionId
|
||||
+ " task hashcode: " + getHexString(nextQueryTaskFuture));
|
||||
++currentSessionId;
|
||||
requestTaskFuture = null;
|
||||
nextQueryTaskFuture = null;
|
||||
lastScheduledTask = null;
|
||||
}
|
||||
|
||||
private static String getHexString(Object o) {
|
||||
return Integer.toHexString(System.identityHashCode(o));
|
||||
}
|
||||
|
||||
private boolean responseMatchesOptions(@NonNull MdnsResponse response,
|
||||
@@ -247,7 +275,7 @@ public class MdnsServiceTypeClient {
|
||||
if (listeners.remove(listener) == null) {
|
||||
return listeners.isEmpty();
|
||||
}
|
||||
if (listeners.isEmpty() && requestTaskFuture != null) {
|
||||
if (listeners.isEmpty() && nextQueryTaskFuture != null) {
|
||||
cancelRequestTaskLocked();
|
||||
}
|
||||
return listeners.isEmpty();
|
||||
@@ -284,9 +312,9 @@ public class MdnsServiceTypeClient {
|
||||
for (MdnsResponse response : allResponses) {
|
||||
if (modifiedResponse.contains(response)) {
|
||||
if (response.isGoodbye()) {
|
||||
onGoodbyeReceived(response.getServiceInstanceName());
|
||||
onGoodbyeReceivedLocked(response.getServiceInstanceName());
|
||||
} else {
|
||||
onResponseModified(response);
|
||||
onResponseModifiedLocked(response);
|
||||
}
|
||||
} else if (instanceNameToResponse.containsKey(response.getServiceInstanceName())) {
|
||||
// If the response is not modified and already in the cache. The cache will
|
||||
@@ -294,6 +322,20 @@ public class MdnsServiceTypeClient {
|
||||
instanceNameToResponse.put(response.getServiceInstanceName(), response);
|
||||
}
|
||||
}
|
||||
if (nextQueryTaskFuture != null && lastScheduledTask != null
|
||||
&& lastScheduledTask.config.shouldUseQueryBackoff()) {
|
||||
final long now = clock.elapsedRealtime();
|
||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
||||
final long timeToRun = calculateTimeToRun(lastScheduledTask,
|
||||
lastScheduledTask.config, now,
|
||||
minRemainingTtl, lastSentTime);
|
||||
if (timeToRun > lastScheduledTask.timeToRun) {
|
||||
QueryTaskConfig lastTaskConfig = lastScheduledTask.config;
|
||||
cancelRequestTaskLocked();
|
||||
nextQueryTaskFuture = scheduleNextRunLocked(lastTaskConfig, minRemainingTtl,
|
||||
now, timeToRun, currentSessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,13 +365,14 @@ public class MdnsServiceTypeClient {
|
||||
}
|
||||
}
|
||||
|
||||
if (requestTaskFuture != null) {
|
||||
if (nextQueryTaskFuture != null) {
|
||||
cancelRequestTaskLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onResponseModified(@NonNull MdnsResponse response) {
|
||||
@GuardedBy("lock")
|
||||
private void onResponseModifiedLocked(@NonNull MdnsResponse response) {
|
||||
final String serviceInstanceName = response.getServiceInstanceName();
|
||||
final MdnsResponse currentResponse =
|
||||
instanceNameToResponse.get(serviceInstanceName);
|
||||
@@ -375,7 +418,8 @@ public class MdnsServiceTypeClient {
|
||||
}
|
||||
}
|
||||
|
||||
private void onGoodbyeReceived(@Nullable String serviceInstanceName) {
|
||||
@GuardedBy("lock")
|
||||
private void onGoodbyeReceivedLocked(@Nullable String serviceInstanceName) {
|
||||
final MdnsResponse response = instanceNameToResponse.remove(serviceInstanceName);
|
||||
if (response == null) {
|
||||
return;
|
||||
@@ -427,32 +471,52 @@ public class MdnsServiceTypeClient {
|
||||
MdnsConfigs.alwaysAskForUnicastResponseInEachBurst();
|
||||
private final boolean usePassiveMode;
|
||||
private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
private final long sessionId;
|
||||
private final int numOfQueriesBeforeBackoff;
|
||||
@VisibleForTesting
|
||||
int transactionId;
|
||||
final int transactionId;
|
||||
@VisibleForTesting
|
||||
boolean expectUnicastResponse;
|
||||
private int queriesPerBurst;
|
||||
private int timeBetweenBurstsInMs;
|
||||
private int burstCounter;
|
||||
private int timeToRunNextTaskInMs;
|
||||
private boolean isFirstBurst;
|
||||
final boolean expectUnicastResponse;
|
||||
private final int queriesPerBurst;
|
||||
private final int timeBetweenBurstsInMs;
|
||||
private final int burstCounter;
|
||||
private final long delayUntilNextTaskWithoutBackoffMs;
|
||||
private final boolean isFirstBurst;
|
||||
private final long queryCount;
|
||||
@NonNull private final SocketKey socketKey;
|
||||
|
||||
|
||||
QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId,
|
||||
boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter,
|
||||
int queriesPerBurst, int timeBetweenBurstsInMs,
|
||||
long delayUntilNextTaskWithoutBackoffMs) {
|
||||
this.subtypes = new ArrayList<>(other.subtypes);
|
||||
this.usePassiveMode = other.usePassiveMode;
|
||||
this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff;
|
||||
this.transactionId = transactionId;
|
||||
this.expectUnicastResponse = expectUnicastResponse;
|
||||
this.queriesPerBurst = queriesPerBurst;
|
||||
this.timeBetweenBurstsInMs = timeBetweenBurstsInMs;
|
||||
this.burstCounter = burstCounter;
|
||||
this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
|
||||
this.isFirstBurst = isFirstBurst;
|
||||
this.queryCount = queryCount;
|
||||
this.socketKey = other.socketKey;
|
||||
}
|
||||
QueryTaskConfig(@NonNull Collection<String> subtypes,
|
||||
boolean usePassiveMode,
|
||||
boolean onlyUseIpv6OnIpv6OnlyNetworks,
|
||||
long sessionId,
|
||||
int numOfQueriesBeforeBackoff,
|
||||
@Nullable SocketKey socketKey) {
|
||||
this.usePassiveMode = usePassiveMode;
|
||||
this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
|
||||
this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
|
||||
this.subtypes = new ArrayList<>(subtypes);
|
||||
this.queriesPerBurst = QUERIES_PER_BURST;
|
||||
this.burstCounter = 0;
|
||||
this.transactionId = 1;
|
||||
this.expectUnicastResponse = true;
|
||||
this.isFirstBurst = true;
|
||||
this.sessionId = sessionId;
|
||||
// Config the scan frequency based on the scan mode.
|
||||
if (this.usePassiveMode) {
|
||||
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then
|
||||
@@ -467,42 +531,61 @@ public class MdnsServiceTypeClient {
|
||||
this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS;
|
||||
}
|
||||
this.socketKey = socketKey;
|
||||
this.queryCount = 0;
|
||||
this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
|
||||
}
|
||||
|
||||
QueryTaskConfig getConfigForNextRun() {
|
||||
if (++transactionId > UNSIGNED_SHORT_MAX_VALUE) {
|
||||
transactionId = 1;
|
||||
long newQueryCount = queryCount + 1;
|
||||
int newTransactionId = transactionId + 1;
|
||||
if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) {
|
||||
newTransactionId = 1;
|
||||
}
|
||||
boolean newExpectUnicastResponse = false;
|
||||
boolean newIsFirstBurst = isFirstBurst;
|
||||
int newQueriesPerBurst = queriesPerBurst;
|
||||
int newBurstCounter = burstCounter + 1;
|
||||
long newDelayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs;
|
||||
int newTimeBetweenBurstsInMs = timeBetweenBurstsInMs;
|
||||
// Only the first query expects uni-cast response.
|
||||
expectUnicastResponse = false;
|
||||
if (++burstCounter == queriesPerBurst) {
|
||||
burstCounter = 0;
|
||||
if (newBurstCounter == queriesPerBurst) {
|
||||
newBurstCounter = 0;
|
||||
|
||||
if (alwaysAskForUnicastResponse) {
|
||||
expectUnicastResponse = true;
|
||||
newExpectUnicastResponse = true;
|
||||
}
|
||||
// In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and
|
||||
// then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE
|
||||
// queries.
|
||||
if (isFirstBurst) {
|
||||
isFirstBurst = false;
|
||||
newIsFirstBurst = false;
|
||||
if (usePassiveMode) {
|
||||
queriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
|
||||
newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE;
|
||||
}
|
||||
}
|
||||
// In active scan mode, sends a burst of QUERIES_PER_BURST queries,
|
||||
// TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and
|
||||
// then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and
|
||||
// doubles until it maxes out at TIME_BETWEEN_BURSTS_MS.
|
||||
timeToRunNextTaskInMs = timeBetweenBurstsInMs;
|
||||
newDelayUntilNextTaskWithoutBackoffMs = timeBetweenBurstsInMs;
|
||||
if (timeBetweenBurstsInMs < TIME_BETWEEN_BURSTS_MS) {
|
||||
timeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
|
||||
newTimeBetweenBurstsInMs = Math.min(timeBetweenBurstsInMs * 2,
|
||||
TIME_BETWEEN_BURSTS_MS);
|
||||
}
|
||||
} else {
|
||||
timeToRunNextTaskInMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
|
||||
newDelayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS;
|
||||
}
|
||||
return this;
|
||||
return new QueryTaskConfig(this, newQueryCount, newTransactionId,
|
||||
newExpectUnicastResponse, newIsFirstBurst, newBurstCounter, newQueriesPerBurst,
|
||||
newTimeBetweenBurstsInMs, newDelayUntilNextTaskWithoutBackoffMs);
|
||||
}
|
||||
|
||||
private boolean shouldUseQueryBackoff() {
|
||||
// Don't enable backoff mode during the burst or in the first burst
|
||||
if (burstCounter != 0 || isFirstBurst) {
|
||||
return false;
|
||||
}
|
||||
return queryCount > numOfQueriesBeforeBackoff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,9 +615,17 @@ public class MdnsServiceTypeClient {
|
||||
private class QueryTask implements Runnable {
|
||||
|
||||
private final QueryTaskConfig config;
|
||||
private final long timeToRun;
|
||||
private final long minTtlExpirationTimeWhenScheduled;
|
||||
private final long sessionId;
|
||||
|
||||
QueryTask(@NonNull QueryTaskConfig config) {
|
||||
QueryTask(@NonNull QueryTaskConfig config, long timeToRun,
|
||||
long minTtlExpirationTimeWhenScheduled,
|
||||
long sessionId) {
|
||||
this.config = config;
|
||||
this.timeToRun = timeToRun;
|
||||
this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -573,13 +664,13 @@ public class MdnsServiceTypeClient {
|
||||
if (MdnsConfigs.useSessionIdToScheduleMdnsTask()) {
|
||||
// In case that the task is not canceled successfully, use session ID to check
|
||||
// if this task should continue to schedule more.
|
||||
if (config.sessionId != currentSessionId) {
|
||||
if (sessionId != currentSessionId) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (MdnsConfigs.shouldCancelScanTaskWhenFutureIsNull()) {
|
||||
if (requestTaskFuture == null) {
|
||||
if (nextQueryTaskFuture == null) {
|
||||
// If requestTaskFuture is set to null, the task is cancelled. We can't use
|
||||
// isCancelled() here because this QueryTask is different from the future
|
||||
// that is returned from executor.schedule(). See b/71646910.
|
||||
@@ -624,14 +715,72 @@ public class MdnsServiceTypeClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
requestTaskFuture = scheduleNextRunLocked(this.config);
|
||||
QueryTaskConfig nextRunConfig = this.config.getConfigForNextRun();
|
||||
final long now = clock.elapsedRealtime();
|
||||
lastSentTime = now;
|
||||
final long minRemainingTtl = getMinRemainingTtlLocked(now);
|
||||
final long timeToRun = calculateTimeToRun(this, nextRunConfig, now,
|
||||
minRemainingTtl, lastSentTime);
|
||||
nextQueryTaskFuture = scheduleNextRunLocked(nextRunConfig,
|
||||
minRemainingTtl, now, timeToRun, lastScheduledTask.sessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long calculateTimeToRun(@NonNull QueryTask lastScheduledTask,
|
||||
QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
|
||||
final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
|
||||
if (!queryTaskConfig.shouldUseQueryBackoff()) {
|
||||
return lastSentTime + baseDelayInMs;
|
||||
}
|
||||
if (minRemainingTtl <= 0) {
|
||||
// There's no service, or there is an expired service. In any case, schedule for the
|
||||
// minimum time, which is the base delay.
|
||||
return lastSentTime + baseDelayInMs;
|
||||
}
|
||||
// If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
|
||||
if (lastSentTime < now
|
||||
&& lastScheduledTask.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
|
||||
// Use the original scheduling time if the TTL has not changed, to avoid continuously
|
||||
// rescheduling to 80% of the remaining TTL as time passes
|
||||
return lastScheduledTask.timeToRun;
|
||||
}
|
||||
return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
private long getMinRemainingTtlLocked(long now) {
|
||||
long minRemainingTtl = Long.MAX_VALUE;
|
||||
for (MdnsResponse response : instanceNameToResponse.values()) {
|
||||
if (!response.isComplete()) {
|
||||
continue;
|
||||
}
|
||||
long remainingTtl =
|
||||
response.getServiceRecord().getRemainingTTL(now);
|
||||
// remainingTtl is <= 0 means the service expired.
|
||||
if (remainingTtl <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (remainingTtl < minRemainingTtl) {
|
||||
minRemainingTtl = remainingTtl;
|
||||
}
|
||||
}
|
||||
return minRemainingTtl == Long.MAX_VALUE ? 0 : minRemainingTtl;
|
||||
}
|
||||
|
||||
@GuardedBy("lock")
|
||||
@NonNull
|
||||
private Future<?> scheduleNextRunLocked(@NonNull QueryTaskConfig lastRunConfig) {
|
||||
QueryTaskConfig config = lastRunConfig.getConfigForNextRun();
|
||||
return executor.schedule(new QueryTask(config), config.timeToRunNextTaskInMs, MILLISECONDS);
|
||||
private Future<?> scheduleNextRunLocked(@NonNull QueryTaskConfig nextRunConfig,
|
||||
long minRemainingTtl,
|
||||
long timeWhenScheduled, long timeToRun, long sessionId) {
|
||||
lastScheduledTask = new QueryTask(nextRunConfig, timeToRun,
|
||||
minRemainingTtl + timeWhenScheduled, sessionId);
|
||||
// The timeWhenScheduled could be greater than the timeToRun if the Runnable is delayed.
|
||||
long timeToNextTasksWithBackoffInMs = Math.max(timeToRun - timeWhenScheduled, 0);
|
||||
sharedLog.log(
|
||||
String.format("Next run: sessionId: %d, in %d ms", lastScheduledTask.sessionId,
|
||||
timeToNextTasksWithBackoffInMs));
|
||||
return executor.schedule(lastScheduledTask, timeToNextTasksWithBackoffInMs,
|
||||
MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -288,6 +288,110 @@ public class MdnsServiceTypeClientTests {
|
||||
verify(expectedSendFutures[5]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_activeScanWithQueryBackoff() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(
|
||||
false).setNumOfQueriesBeforeBackoff(11).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
|
||||
// First burst, 3 queries.
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
1, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Second burst will be sent after initialTimeBetweenBurstsMs, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
3, MdnsConfigs.initialTimeBetweenBurstsMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
4, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
5, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Third burst will be sent after initialTimeBetweenBurstsMs * 2, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
6, MdnsConfigs.initialTimeBetweenBurstsMs() * 2, /* expectsUnicastResponse= */
|
||||
false);
|
||||
verifyAndSendQuery(
|
||||
7, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
8, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// Forth burst will be sent after initialTimeBetweenBurstsMs * 4, 3 queries.
|
||||
verifyAndSendQuery(
|
||||
9, MdnsConfigs.initialTimeBetweenBurstsMs() * 4, /* expectsUnicastResponse= */
|
||||
false);
|
||||
verifyAndSendQuery(
|
||||
10, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
11, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
// 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();
|
||||
client.processResponse(createResponse(
|
||||
"service-instance-1", "192.0.2.123", 5353,
|
||||
SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), TEST_TTL), socketKey);
|
||||
verifyAndSendQuery(12, (long) (TEST_TTL / 2 * 0.8), /* expectsUnicastResponse= */
|
||||
false);
|
||||
currentTime += (long) (TEST_TTL / 2 * 0.8);
|
||||
doReturn(currentTime).when(mockDecoderClock).elapsedRealtime();
|
||||
verifyAndSendQuery(
|
||||
13, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_passiveScanWithQueryBackoff() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(
|
||||
true).setNumOfQueriesBeforeBackoff(3).build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
verifyAndSendQuery(0, 0, /* expectsUnicastResponse= */ true);
|
||||
verifyAndSendQuery(
|
||||
1, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(
|
||||
2, MdnsConfigs.timeBetweenQueriesInBurstMs(), /* expectsUnicastResponse= */ false);
|
||||
verifyAndSendQuery(3, MdnsConfigs.timeBetweenBurstsMs(), /* expectsUnicastResponse= */
|
||||
false);
|
||||
assertEquals(4, currentThreadExecutor.getNumOfScheduledFuture());
|
||||
|
||||
// In backoff mode, the current scheduled task will be canceled and reschedule if the
|
||||
// 0.8 * smallestRemainingTtl is larger than time to next run.
|
||||
doReturn(TEST_ELAPSED_REALTIME + 20000).when(mockDecoderClock).elapsedRealtime();
|
||||
client.processResponse(createResponse(
|
||||
"service-instance-1", "192.0.2.123", 5353,
|
||||
SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), TEST_TTL), socketKey);
|
||||
verify(expectedSendFutures[4]).cancel(true);
|
||||
assertEquals(5, currentThreadExecutor.getNumOfScheduledFuture());
|
||||
verifyAndSendQuery(4, 80000 /* timeInMs */, false /* expectsUnicastResponse */);
|
||||
assertEquals(6, currentThreadExecutor.getNumOfScheduledFuture());
|
||||
// Next run should also be scheduled in 0.8 * smallestRemainingTtl
|
||||
verifyAndSendQuery(5, 80000 /* timeInMs */, false /* expectsUnicastResponse */);
|
||||
assertEquals(7, currentThreadExecutor.getNumOfScheduledFuture());
|
||||
|
||||
// If the records is not refreshed, the current scheduled task will not be canceled.
|
||||
doReturn(TEST_ELAPSED_REALTIME + 20001).when(mockDecoderClock).elapsedRealtime();
|
||||
client.processResponse(createResponse(
|
||||
"service-instance-1", "192.0.2.123", 5353,
|
||||
SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), TEST_TTL,
|
||||
TEST_ELAPSED_REALTIME - 1), socketKey);
|
||||
verify(expectedSendFutures[7], never()).cancel(true);
|
||||
|
||||
// In backoff mode, the current scheduled task will not be canceled if the
|
||||
// 0.8 * smallestRemainingTtl is smaller than time to next run.
|
||||
doReturn(TEST_ELAPSED_REALTIME).when(mockDecoderClock).elapsedRealtime();
|
||||
client.processResponse(createResponse(
|
||||
"service-instance-1", "192.0.2.123", 5353,
|
||||
SERVICE_TYPE_LABELS,
|
||||
Collections.emptyMap(), TEST_TTL), socketKey);
|
||||
verify(expectedSendFutures[7], never()).cancel(true);
|
||||
|
||||
client.stopSendAndReceive(mockListenerOne);
|
||||
verify(expectedSendFutures[7]).cancel(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendQueries_reentry_passiveScanMode() {
|
||||
MdnsSearchOptions searchOptions =
|
||||
@@ -328,7 +432,8 @@ public class MdnsServiceTypeClientTests {
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
QueryTaskConfig config = new QueryTaskConfig(
|
||||
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
|
||||
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 1, socketKey);
|
||||
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
|
||||
socketKey);
|
||||
|
||||
// This is the first query. We will ask for unicast response.
|
||||
assertTrue(config.expectUnicastResponse);
|
||||
@@ -358,7 +463,8 @@ public class MdnsServiceTypeClientTests {
|
||||
MdnsSearchOptions.newBuilder().addSubtype("12345").setIsPassiveMode(false).build();
|
||||
QueryTaskConfig config = new QueryTaskConfig(
|
||||
searchOptions.getSubtypes(), searchOptions.isPassiveMode(),
|
||||
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 1, socketKey);
|
||||
false /* onlyUseIpv6OnIpv6OnlyNetworks */, 3 /* numOfQueriesBeforeBackoff */,
|
||||
socketKey);
|
||||
|
||||
// This is the first query. We will ask for unicast response.
|
||||
assertTrue(config.expectUnicastResponse);
|
||||
@@ -714,8 +820,10 @@ public class MdnsServiceTypeClientTests {
|
||||
return mockPacketWriter;
|
||||
}
|
||||
};
|
||||
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder().setRemoveExpiredService(
|
||||
true).build();
|
||||
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder()
|
||||
.setRemoveExpiredService(true)
|
||||
.setNumOfQueriesBeforeBackoff(Integer.MAX_VALUE)
|
||||
.build();
|
||||
client.startSendAndReceive(mockListenerOne, searchOptions);
|
||||
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
|
||||
|
||||
@@ -1248,13 +1356,16 @@ public class MdnsServiceTypeClientTests {
|
||||
final String ipV4Address = "192.0.2.0";
|
||||
|
||||
final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
|
||||
.setNumOfQueriesBeforeBackoff(Integer.MAX_VALUE)
|
||||
.setResolveInstanceName("instance1").build();
|
||||
|
||||
client.startSendAndReceive(mockListenerOne, resolveOptions);
|
||||
// Ensure the first task is executed so it schedules a future task
|
||||
currentThreadExecutor.getAndClearSubmittedFuture().get(
|
||||
TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
|
||||
client.startSendAndReceive(mockListenerTwo,
|
||||
MdnsSearchOptions.newBuilder().setNumOfQueriesBeforeBackoff(
|
||||
Integer.MAX_VALUE).build());
|
||||
|
||||
// Filing the second request cancels the first future
|
||||
verify(expectedSendFutures[0]).cancel(true);
|
||||
@@ -1317,7 +1428,7 @@ public class MdnsServiceTypeClientTests {
|
||||
|
||||
private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse,
|
||||
boolean multipleSocketDiscovery) {
|
||||
assertEquals(currentThreadExecutor.getAndClearLastScheduledDelayInMs(), timeInMs);
|
||||
assertEquals(timeInMs, currentThreadExecutor.getAndClearLastScheduledDelayInMs());
|
||||
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
|
||||
if (expectsUnicastResponse) {
|
||||
verify(mockSocketClient).sendPacketRequestingUnicastResponse(
|
||||
@@ -1406,6 +1517,10 @@ public class MdnsServiceTypeClientTests {
|
||||
lastSubmittedFuture = null;
|
||||
return val;
|
||||
}
|
||||
|
||||
public int getNumOfScheduledFuture() {
|
||||
return futureIndex - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private MdnsPacket createResponse(
|
||||
@@ -1424,7 +1539,7 @@ public class MdnsServiceTypeClientTests {
|
||||
textAttributes, ptrTtlMillis);
|
||||
}
|
||||
|
||||
// Creates a mDNS response.
|
||||
|
||||
private MdnsPacket createResponse(
|
||||
@NonNull String serviceInstanceName,
|
||||
@Nullable String host,
|
||||
@@ -1432,6 +1547,19 @@ public class MdnsServiceTypeClientTests {
|
||||
@NonNull String[] type,
|
||||
@NonNull Map<String, String> textAttributes,
|
||||
long ptrTtlMillis) {
|
||||
return createResponse(serviceInstanceName, host, port, type, textAttributes, ptrTtlMillis,
|
||||
TEST_ELAPSED_REALTIME);
|
||||
}
|
||||
|
||||
// Creates a mDNS response.
|
||||
private MdnsPacket createResponse(
|
||||
@NonNull String serviceInstanceName,
|
||||
@Nullable String host,
|
||||
int port,
|
||||
@NonNull String[] type,
|
||||
@NonNull Map<String, String> textAttributes,
|
||||
long ptrTtlMillis,
|
||||
long receiptTimeMillis) {
|
||||
|
||||
final ArrayList<MdnsRecord> answerRecords = new ArrayList<>();
|
||||
|
||||
@@ -1442,7 +1570,7 @@ public class MdnsServiceTypeClientTests {
|
||||
final String[] serviceName = serviceNameList.toArray(new String[0]);
|
||||
final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
|
||||
type,
|
||||
TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
|
||||
receiptTimeMillis,
|
||||
false /* cacheFlush */,
|
||||
ptrTtlMillis,
|
||||
serviceName);
|
||||
@@ -1451,7 +1579,7 @@ public class MdnsServiceTypeClientTests {
|
||||
// Set SRV record.
|
||||
final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
|
||||
serviceName,
|
||||
TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
|
||||
receiptTimeMillis,
|
||||
false /* cacheFlush */,
|
||||
TEST_TTL,
|
||||
0 /* servicePriority */,
|
||||
@@ -1465,7 +1593,7 @@ public class MdnsServiceTypeClientTests {
|
||||
final InetAddress addr = InetAddresses.parseNumericAddress(host);
|
||||
final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
|
||||
new String[] {"hostname"} /* name */,
|
||||
TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
|
||||
receiptTimeMillis,
|
||||
false /* cacheFlush */,
|
||||
TEST_TTL,
|
||||
addr);
|
||||
@@ -1479,7 +1607,7 @@ public class MdnsServiceTypeClientTests {
|
||||
}
|
||||
final MdnsTextRecord textRecord = new MdnsTextRecord(
|
||||
serviceName,
|
||||
TEST_ELAPSED_REALTIME /* receiptTimeMillis */,
|
||||
receiptTimeMillis,
|
||||
false /* cacheFlush */,
|
||||
TEST_TTL,
|
||||
textEntries);
|
||||
|
||||
Reference in New Issue
Block a user