From 9473c04b860296c11ef9f0559bf4d121d0ebda77 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Fri, 9 Feb 2018 09:00:21 -0800 Subject: [PATCH] [channel-manager] handle simultaneous channel change requests (#2543) This commit adds new logic in `ChannelManager` to help handle situations where multiple devices within network request channel change around the same time. This simplifies how the channel change can be triggered by users allowing them to request a channel change on all routers/devices simultaneously (this would help with cases where the Thread network contains multiple partitions). In particular, this commit adds a jitter delay to start processing of a channel change request (random delay before a Pending Dataset is prepared and sent to leader). Also this commit changes how the Pending Dataset is prepared and sent. The code now checks if there is a valid Pending Dataset and if it is changing the channel to same one as the current channel change request, then it skips updating the Pending Dataset. --- src/core/utils/channel_manager.cpp | 54 +++++++++++++++++++++++++++--- src/core/utils/channel_manager.hpp | 8 ++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/core/utils/channel_manager.cpp b/src/core/utils/channel_manager.cpp index 2f2fef41e..5f3c908ef 100644 --- a/src/core/utils/channel_manager.cpp +++ b/src/core/utils/channel_manager.cpp @@ -77,7 +77,8 @@ otError ChannelManager::RequestChannelChange(uint8_t aChannel) mState = kStateChangeRequested; mChannel = aChannel; mActiveTimestamp = 0; - PreparePendingDataset(); + + mTimer.Start((otPlatRandomGet() % kRequestStartJitterInterval) + 1); exit: return error; @@ -98,18 +99,44 @@ void ChannelManager::PreparePendingDataset(void) { ThreadNetif &netif = GetInstance().GetThreadNetif(); uint64_t pendingTimestamp = 0; + uint64_t pendingActiveTimestamp = 0; + uint32_t delayInMs = TimerMilli::SecToMsec(static_cast(mDelay)); otOperationalDataset dataset; - uint32_t delayInMs; otError error; VerifyOrExit(mState == kStateChangeRequested); + VerifyOrExit(mChannel != GetInstance().Get().GetChannel()); + + if ((mSupportedChannels & (1U << mChannel)) == 0) + { + otLogInfoUtil(GetInstance(), "ChannelManager: Request rejected! Channel %d not in supported mask 0x%x", + mChannel, mSupportedChannels); + mState = kStateIdle; + ExitNow(); + } + if (netif.GetPendingDataset().Get(dataset) == OT_ERROR_NONE) { if (dataset.mIsPendingTimestampSet) { pendingTimestamp = dataset.mPendingTimestamp; } + + // We check whether the Pending Dataset is changing the + // channel to same one as the current request (i.e., channel + // should match and delay should be less than the requested + // delay). + + if (dataset.mIsChannelSet && (mChannel == dataset.mChannel) && + dataset.mIsDelaySet && (dataset.mDelay <= delayInMs) && + dataset.mIsActiveTimestampSet) + { + // We save the active timestamp to later check and ensure it + // is ahead of current ActiveDataset timestamp. + + pendingActiveTimestamp = dataset.mActiveTimestamp; + } } pendingTimestamp += (otPlatRandomGet() % kMaxTimestampIncrease) + 1; @@ -137,6 +164,23 @@ void ChannelManager::PreparePendingDataset(void) ExitNow(); } + // `pendingActiveTimestamp` will be non-zero if the Pending + // Dataset is valid and is performing the same channel change. + // We check to ensure its timestamp is indeed ahead of current + // Active Dataset's timestamp, and if so, we skip updating + // the Pending Dataset. + + if (pendingActiveTimestamp != 0) + { + if (dataset.mActiveTimestamp < pendingActiveTimestamp) + { + otLogInfoUtil(GetInstance(), "ChannelManager: Pending Dataset is valid for change channel to %d", mChannel); + mState = kStateSentMgmtPendingDataset; + mTimer.Start(delayInMs + kChangeCheckWaitInterval); + ExitNow(); + } + } + // A non-zero `mActiveTimestamp` indicates that this is not the first // attempt to update the Dataset for the ongoing requested channel // change. In that case, if the Timestamp in current Active Dataset is @@ -170,7 +214,6 @@ void ChannelManager::PreparePendingDataset(void) dataset.mPendingTimestamp = pendingTimestamp; dataset.mIsPendingTimestampSet = true; - delayInMs = TimerMilli::SecToMsec(static_cast(mDelay)); dataset.mDelay = delayInMs; dataset.mIsDelaySet = true; @@ -187,6 +230,8 @@ void ChannelManager::PreparePendingDataset(void) { otLogInfoUtil(GetInstance(), "ChannelManager: %s error in dataset update (channel change %d), retry in %d sec", otThreadErrorToString(error), mChannel, TimerMilli::MsecToSec(kPendingDatasetTxRetryInterval)); + + mTimer.Start(kPendingDatasetTxRetryInterval); } exit: @@ -206,7 +251,8 @@ void ChannelManager::HandleTimer(void) break; case kStateSentMgmtPendingDataset: - otLogInfoUtil(GetInstance(), "ChannelManager: Timed out waiting for channel change to %d", mChannel); + otLogInfoUtil(GetInstance(), "ChannelManager: Timed out waiting for change to %d, trying again.", mChannel); + mState = kStateChangeRequested; // fall through diff --git a/src/core/utils/channel_manager.hpp b/src/core/utils/channel_manager.hpp index 644e68895..3d5117d9e 100644 --- a/src/core/utils/channel_manager.hpp +++ b/src/core/utils/channel_manager.hpp @@ -150,11 +150,11 @@ public: private: enum { - kDefaultSupprotedChannelMask = OT_RADIO_SUPPORTED_CHANNELS, - kMaxTimestampIncrease = 128, - + kDefaultSupprotedChannelMask = OT_RADIO_SUPPORTED_CHANNELS, + kMaxTimestampIncrease = 128, kPendingDatasetTxRetryInterval = 20000, // in ms - kChangeCheckWaitInterval = 30000, // in ms + kChangeCheckWaitInterval = 30000, // in ms + kRequestStartJitterInterval = 10000, // in ms }; enum State