[address-resolver] implement ramp-down mechanism for retry delay (#9162)

Address resolver retry delay determines how long the device needs to
wait after a failed address query for a target address to allow a
next query attempt. This is tracked per target (in `CacheEntry`).

The retry delay starts with an initial value (15 seconds). If
consecutive queries fail, the retry delay doubles each time, up to a
maximum value (120 seconds). In the current code, the retry delay is
only reset to the initial value after a successful address
resolution.

This commit implements a new mechanism to reduce the retry delay over
time when there is no activity (no queries for the target address).
The ramp-down countdown starts after a previous retry delay has
expired. If no query is requested within the maximum retry delay
interval (120 seconds), the retry delay is divided by two. This
process is repeated until the retry delay reaches the initial
(minimum) value.

If a query is requested during the ramp-down process, the ramp-down is
stopped. The next actions are determined based on whether the query
is successful or fails. If the query is successful, the retry delay
is reset to the initial value. If the query fails, the retry delay
timeout is applied and the ramp-down process is started again after
the retry delay duration has expired.

This commit also updates the `test-014-address-resolver`  to validate
the newly added ramp-down behavior.
This commit is contained in:
Abtin Keshavarzian
2023-07-01 11:12:25 -07:00
committed by GitHub
parent 422f673c7c
commit 96eb2a4386
6 changed files with 75 additions and 9 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ extern "C" {
* @note This number versions both OpenThread platform and user APIs.
*
*/
#define OPENTHREAD_API_VERSION (337)
#define OPENTHREAD_API_VERSION (338)
/**
* @addtogroup api-instance
+1
View File
@@ -104,6 +104,7 @@ typedef struct otCacheEntryInfo
otShortAddress mRloc16; ///< RLOC16
otCacheEntryState mState; ///< Entry state
bool mCanEvict : 1; ///< Indicates whether the entry can be evicted.
bool mRampDown : 1; ///< Whether in ramp-down mode while in `OT_CACHE_ENTRY_STATE_RETRY_QUERY`.
bool mValidLastTrans : 1; ///< Indicates whether last transaction time and ML-EID are valid.
uint32_t mLastTransTime; ///< Last transaction time (applicable in cached state).
otIp6Address mMeshLocalEid; ///< Mesh Local EID (applicable if entry in cached state).
+1 -1
View File
@@ -2580,7 +2580,7 @@ void Interpreter::OutputEidCacheEntry(const otCacheEntryInfo &aEntry)
if (aEntry.mState == OT_CACHE_ENTRY_STATE_RETRY_QUERY)
{
OutputFormat(" retryDelay=%u", aEntry.mRetryDelay);
OutputFormat(" retryDelay=%u rampDown=%d", aEntry.mRetryDelay, aEntry.mRampDown);
}
OutputNewLine();
+33 -4
View File
@@ -152,7 +152,8 @@ Error AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator)
}
else
{
aInfo.mState = MapEnum(EntryInfo::kStateRetryQuery);
aInfo.mState = MapEnum(EntryInfo::kStateRetryQuery);
aInfo.mRampDown = entry->IsInRampDown();
}
aInfo.mCanEvict = entry->CanEvict();
@@ -542,10 +543,10 @@ Error AddressResolver::Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRlo
if (list == &mQueryRetryList)
{
// Allow an entry in query-retry mode to resend an Address
// Query again only if the timeout (retry delay interval) is
// expired.
// Query again only if it is in ramp down mode, i.e., the
// retry delay timeout is expired.
VerifyOrExit(entry->IsTimeoutZero(), error = kErrorDrop);
VerifyOrExit(entry->IsInRampDown(), error = kErrorDrop);
mQueryRetryList.PopAfter(prev);
}
@@ -939,6 +940,33 @@ void AddressResolver::HandleTimeTick(void)
continueRxingTicks = true;
entry.DecrementTimeout();
if (entry.IsTimeoutZero())
{
if (!entry.IsInRampDown())
{
entry.SetRampDown(true);
entry.SetTimeout(kAddressQueryMaxRetryDelay);
LogInfo("Starting ramp down of %s retry-delay:%u", entry.GetTarget().ToString().AsCString(),
entry.GetTimeout());
}
else
{
uint16_t retryDelay = entry.GetRetryDelay();
retryDelay >>= 1;
retryDelay = Max(retryDelay, kAddressQueryInitialRetryDelay);
if (retryDelay != entry.GetRetryDelay())
{
entry.SetRetryDelay(retryDelay);
entry.SetTimeout(kAddressQueryMaxRetryDelay);
LogInfo("Ramping down %s retry-delay:%u", entry.GetTarget().ToString().AsCString(), retryDelay);
}
}
}
}
{
@@ -963,6 +991,7 @@ void AddressResolver::HandleTimeTick(void)
entry->SetRetryDelay(retryDelay);
entry->SetCanEvict(true);
entry->SetRampDown(false);
// Move the entry from `mQueryList` to `mQueryRetryList`
mQueryList.PopAfter(prev);
+4
View File
@@ -285,6 +285,9 @@ private:
bool CanEvict(void) const { return mInfo.mOther.mCanEvict; }
void SetCanEvict(bool aCanEvict) { mInfo.mOther.mCanEvict = aCanEvict; }
bool IsInRampDown(void) const { return mInfo.mOther.mRampDown; }
void SetRampDown(bool aRampDown) { mInfo.mOther.mRampDown = aRampDown; }
bool Matches(const Ip6::Address &aEid) const { return GetTarget() == aEid; }
private:
@@ -308,6 +311,7 @@ private:
uint16_t mTimeout;
uint16_t mRetryDelay;
bool mCanEvict;
bool mRampDown;
} mOther;
} mInfo;
+35 -3
View File
@@ -201,17 +201,18 @@ verify_within(check_cache_entry_switch_to_retry_state, 20)
# Now wait for all entries to reach zero timeout.
def check_cache_entry_in_retry_state_to_get_to_zero_timeout():
def check_cache_entry_in_retry_state_to_enter_rampdown():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'retry')
verify(fields[3] == 'canEvict=1')
verify(fields[4].startswith('timeout='))
verify(int(fields[4].split('=')[1]) == 0)
verify(fields[5].startswith('retryDelay='))
verify(fields[6] == 'rampDown=1')
verify_within(check_cache_entry_in_retry_state_to_get_to_zero_timeout, 20)
verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 20)
# Now send again to the same addresses.
@@ -231,6 +232,37 @@ def check_cache_entry_switch_to_query_state():
verify_within(check_cache_entry_switch_to_query_state, 20)
def check_cache_entry_switch_to_retry_state_with_double_retry_delay():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'retry')
verify(fields[3] == 'canEvict=1')
verify(fields[4].startswith('timeout='))
verify(fields[5].startswith('retryDelay='))
verify(int(fields[5].split('=')[1]) == 2 * initial_retry_delay)
verify_within(check_cache_entry_switch_to_retry_state_with_double_retry_delay, 40)
verify_within(check_cache_entry_in_retry_state_to_enter_rampdown, 40)
def check_cache_entry_ramp_down_to_initial_retry_delay():
cache_table = r1.get_eidcache()
for entry in cache_table:
fields = entry.strip().split(' ')
verify(fields[2] == 'retry')
verify(fields[3] == 'canEvict=1')
verify(fields[4].startswith('timeout='))
verify(fields[5].startswith('retryDelay='))
verify(int(fields[5].split('=')[1]) == initial_retry_delay)
verify(fields[6] == 'rampDown=1')
verify_within(check_cache_entry_ramp_down_to_initial_retry_delay, 60)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Verify snoop optimization behavior.