1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity.mdns; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 22 /** 23 * The query scheduler class for calculating next query tasks parameters. 24 * <p> 25 * The class is not thread-safe and needs to be used on a consistent thread. 26 */ 27 public class MdnsQueryScheduler { 28 29 /** 30 * The argument for tracking the query tasks status. 31 */ 32 public static class ScheduledQueryTaskArgs { 33 public final QueryTaskConfig config; 34 public final long timeToRun; 35 public final long minTtlExpirationTimeWhenScheduled; 36 public final long sessionId; 37 ScheduledQueryTaskArgs(@onNull QueryTaskConfig config, long timeToRun, long minTtlExpirationTimeWhenScheduled, long sessionId)38 ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun, 39 long minTtlExpirationTimeWhenScheduled, long sessionId) { 40 this.config = config; 41 this.timeToRun = timeToRun; 42 this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled; 43 this.sessionId = sessionId; 44 } 45 } 46 47 @Nullable 48 private ScheduledQueryTaskArgs mLastScheduledQueryTaskArgs; 49 MdnsQueryScheduler()50 public MdnsQueryScheduler() { 51 } 52 53 /** 54 * Cancel the scheduled run. The method needed to be called when the scheduled task need to 55 * be canceled and rescheduling is not need. 56 */ cancelScheduledRun()57 public void cancelScheduledRun() { 58 mLastScheduledQueryTaskArgs = null; 59 } 60 61 /** 62 * Calculates ScheduledQueryTaskArgs for rescheduling the current task. Returns null if the 63 * rescheduling is not necessary. 64 */ 65 @Nullable maybeRescheduleCurrentRun(long now, long minRemainingTtl, long lastSentTime, long sessionId)66 public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(long now, 67 long minRemainingTtl, long lastSentTime, long sessionId) { 68 if (mLastScheduledQueryTaskArgs == null) { 69 return null; 70 } 71 if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) { 72 return null; 73 } 74 75 final long timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs, 76 mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime); 77 78 if (timeToRun <= mLastScheduledQueryTaskArgs.timeToRun) { 79 return null; 80 } 81 82 mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(mLastScheduledQueryTaskArgs.config, 83 timeToRun, 84 minRemainingTtl + now, 85 sessionId); 86 return mLastScheduledQueryTaskArgs; 87 } 88 89 /** 90 * Calculates the ScheduledQueryTaskArgs for the next run. 91 */ 92 @NonNull scheduleNextRun( @onNull QueryTaskConfig currentConfig, long minRemainingTtl, long now, long lastSentTime, long sessionId)93 public ScheduledQueryTaskArgs scheduleNextRun( 94 @NonNull QueryTaskConfig currentConfig, 95 long minRemainingTtl, 96 long now, 97 long lastSentTime, 98 long sessionId) { 99 final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun(); 100 final long timeToRun; 101 if (mLastScheduledQueryTaskArgs == null) { 102 timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs; 103 } else { 104 timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs, 105 nextRunConfig, now, minRemainingTtl, lastSentTime); 106 } 107 mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun, 108 minRemainingTtl + now, 109 sessionId); 110 return mLastScheduledQueryTaskArgs; 111 } 112 113 /** 114 * Calculates the ScheduledQueryTaskArgs for the initial run. 115 */ scheduleFirstRun(@onNull QueryTaskConfig taskConfig, long now, long minRemainingTtl, long currentSessionId)116 public ScheduledQueryTaskArgs scheduleFirstRun(@NonNull QueryTaskConfig taskConfig, 117 long now, long minRemainingTtl, long currentSessionId) { 118 mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */, 119 now + minRemainingTtl/* minTtlExpirationTimeWhenScheduled */, 120 currentSessionId); 121 return mLastScheduledQueryTaskArgs; 122 } 123 calculateTimeToRun(@onNull ScheduledQueryTaskArgs taskArgs, QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime)124 private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs, 125 QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) { 126 final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs; 127 if (!queryTaskConfig.shouldUseQueryBackoff()) { 128 return lastSentTime + baseDelayInMs; 129 } 130 if (minRemainingTtl <= 0) { 131 // There's no service, or there is an expired service. In any case, schedule for the 132 // minimum time, which is the base delay. 133 return lastSentTime + baseDelayInMs; 134 } 135 // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun. 136 if (lastSentTime < now 137 && taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) { 138 // Use the original scheduling time if the TTL has not changed, to avoid continuously 139 // rescheduling to 80% of the remaining TTL as time passes 140 return taskArgs.timeToRun; 141 } 142 return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs); 143 } 144 } 145