[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.
This commit is contained in:
Abtin Keshavarzian
2018-02-09 17:00:21 +00:00
committed by Jonathan Hui
parent 6159e3a646
commit 9473c04b86
2 changed files with 54 additions and 8 deletions
+50 -4
View File
@@ -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<uint32_t>(mDelay));
otOperationalDataset dataset;
uint32_t delayInMs;
otError error;
VerifyOrExit(mState == kStateChangeRequested);
VerifyOrExit(mChannel != GetInstance().Get<Mac::Mac>().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<uint32_t>(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
+4 -4
View File
@@ -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