[mle] add Mle::Detacher class to manage graceful detach process (#11723)

This commit introduces a new `Mle::Detacher` class to encapsulate all
state and logic for the graceful detach process. By managing its own
internal state, timer, and completion callback, the `Detacher` class
centralizes the detach logic, improving code clarity and
maintainability.
This commit is contained in:
Abtin Keshavarzian
2025-07-15 14:09:40 -07:00
committed by GitHub
parent dbfc0cd94b
commit 6e1a6ff190
2 changed files with 101 additions and 33 deletions
+67 -29
View File
@@ -52,7 +52,6 @@ Mle::Mle(Instance &aInstance)
, mRequestRouteTlv(false)
, mHasRestored(false)
, mReceivedResponseFromParent(false)
, mDetachingGracefully(false)
, mInitiallyAttachedAsSleepy(false)
, mRole(kRoleDisabled)
, mLastSavedRole(kRoleDisabled)
@@ -75,6 +74,7 @@ Mle::Mle(Instance &aInstance)
, mNeighborTable(aInstance)
, mDelayedSender(aInstance)
, mSocket(aInstance, *this)
, mDetacher(aInstance)
, mRetxTracker(aInstance)
, mAnnounceHandler(aInstance)
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
@@ -273,11 +273,7 @@ void Mle::Stop(StopMode aMode)
SetRole(kRoleDisabled);
exit:
if (mDetachingGracefully)
{
mDetachingGracefully = false;
mDetachGracefullyCallback.InvokeAndClearIfSet();
}
mDetacher.HandleStop();
}
Error Mle::RestorePrevRole(void)
@@ -1465,12 +1461,6 @@ void Mle::HandleAttachTimer(void)
bool shouldAnnounce = true;
ParentRequestType type;
if (mDetachingGracefully)
{
Stop();
ExitNow();
}
#if OPENTHREAD_FTD
if (IsDetached() && mRouterRoleRestorer.IsActive())
{
@@ -3519,14 +3509,8 @@ void Mle::HandleChildUpdateResponseOnChild(RxInfo &aRxInfo)
switch (Tlv::Find<TimeoutTlv>(aRxInfo.mMessage, timeout))
{
case kErrorNone:
if (timeout == 0 && mDetachingGracefully)
{
Stop();
}
else
{
SetTimeout(timeout, kDoNotSendChildUpdateToParent);
}
SuccessOrExit(mDetacher.HandleChildUpdateResponse(timeout));
SetTimeout(timeout, kDoNotSendChildUpdateToParent);
break;
case kErrorNotFound:
break;
@@ -4264,32 +4248,32 @@ exit:
}
#endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE
Error Mle::DetachGracefully(DetachCallback aCallback, void *aContext)
Error Mle::Detacher::Detach(DetachCallback aCallback, void *aContext)
{
Error error = kErrorNone;
uint32_t timeout = kDetachGracefullyTimeout;
uint32_t timeout = kTimeout;
VerifyOrExit(!mDetachingGracefully, error = kErrorBusy);
VerifyOrExit(mState == kIdle, error = kErrorBusy);
mDetachGracefullyCallback.Set(aCallback, aContext);
mCallback.Set(aCallback, aContext);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
Get<BorderRouter::RoutingManager>().RequestStop();
#endif
switch (mRole)
switch (Get<Mle>().GetRole())
{
case kRoleLeader:
break;
case kRoleRouter:
#if OPENTHREAD_FTD
SendAddressRelease();
Get<Mle>().SendAddressRelease();
#endif
break;
case kRoleChild:
IgnoreError(SendChildUpdateRequestToParent(kAppendZeroTimeout));
IgnoreError(Get<Mle>().SendChildUpdateRequestToParent(kAppendZeroTimeout));
break;
case kRoleDisabled:
@@ -4302,8 +4286,8 @@ Error Mle::DetachGracefully(DetachCallback aCallback, void *aContext)
break;
}
mDetachingGracefully = true;
mAttachTimer.Start(timeout);
mState = kDetaching;
mTimer.Start(timeout);
exit:
return error;
@@ -5382,6 +5366,60 @@ void Mle::ParentCandidate::CopyTo(Parent &aParent) const
aParent = *candidateAsParent;
}
//---------------------------------------------------------------------------------------------------------------------
// Detacher
Mle::Detacher::Detacher(Instance &aInstance)
: InstanceLocator(aInstance)
, mState(kIdle)
, mTimer(aInstance)
{
}
void Mle::Detacher::HandleTimer(void)
{
if (mState == kDetaching)
{
Get<Mle>().Stop();
}
}
Error Mle::Detacher::HandleChildUpdateResponse(uint32_t aTimeout)
{
// Called while processing a "Child Update Response" from parent
//
// To gracefully detach as a child, we send a "Child Update
// Request" with zero timeout value to the parent. Receiving
// a "Child Update Response" confirms the parent has received the
// request, allowing the device to then stop its MLE operations.
// Returns `kErrorDetached` to signal that MLE is stopped and the
// incoming response should be ignored. Returns `kErrorNone` if
// the response should be processed (i.e., not detaching or if
// the conditions for MLE stop are not yet met).
Error error = kErrorNone;
VerifyOrExit(mState == kDetaching);
VerifyOrExit(aTimeout == 0);
Get<Mle>().Stop();
error = kErrorDetached;
exit:
return error;
}
void Mle::Detacher::HandleStop(void)
{
// Called upon `Mle::Stop()` after role change.
if (mState == kDetaching)
{
mState = kIdle;
mCallback.InvokeAndClearIfSet();
}
}
//---------------------------------------------------------------------------------------------------------------------
// RetxTracker::RetryInfo
+34 -4
View File
@@ -216,7 +216,7 @@ public:
* @retval kErrorNone Successfully started detaching.
* @retval kErrorBusy Detaching is already in progress.
*/
Error DetachGracefully(DetachCallback aCallback, void *aContext);
Error DetachGracefully(DetachCallback aCallback, void *aContext) { return mDetacher.Detach(aCallback, aContext); }
/**
* Indicates whether or not the Thread device is attached to a Thread network.
@@ -1182,7 +1182,6 @@ private:
static constexpr uint32_t kMaxLinkAcceptDelay = 1000; // Max delay to tx Link Accept for multicast Req
static constexpr uint32_t kChildIdRequestTimeout = 5000; // Max delay to rx a Child ID Req after Parent Res
static constexpr uint32_t kLinkRequestTimeout = 2000; // Max delay to rx a Link Accept
static constexpr uint32_t kDetachGracefullyTimeout = 1000; // Timeout for graceful detach
static constexpr uint32_t kUnicastRetxDelay = 1000; // Base delay for MLE unicast retx
static constexpr uint32_t kMulticastRetxDelay = 5000; // Base delay for MLE multicast retx
static constexpr uint32_t kMulticastRetxDelayMin = kMulticastRetxDelay * 9 / 10; // 0.9 * base delay
@@ -1721,6 +1720,38 @@ private:
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HandleDetacherTimer(void) { mDetacher.HandleTimer(); }
class Detacher : public InstanceLocator
{
// Manages graceful detach process.
public:
explicit Detacher(Instance &aInstance);
Error Detach(DetachCallback aCallback, void *aContext);
void HandleTimer(void);
Error HandleChildUpdateResponse(uint32_t aTimeout);
void HandleStop(void);
private:
static constexpr uint32_t kTimeout = 1000;
enum State : uint8_t
{
kIdle,
kDetaching,
};
using DetachTimer = TimerMilliIn<Mle, &Mle::HandleDetacherTimer>;
State mState;
Callback<DetachCallback> mCallback;
DetachTimer mTimer;
};
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HandleRetxTrackerTimer(void) { mRetxTracker.HandleTimer(); }
class RetxTracker : public InstanceLocator
@@ -2171,7 +2202,6 @@ private:
bool mRequestRouteTlv : 1;
bool mHasRestored : 1;
bool mReceivedResponseFromParent : 1;
bool mDetachingGracefully : 1;
bool mInitiallyAttachedAsSleepy : 1;
DeviceRole mRole;
@@ -2204,6 +2234,7 @@ private:
ParentCandidate mParentCandidate;
MleSocket mSocket;
Counters mCounters;
Detacher mDetacher;
RetxTracker mRetxTracker;
AnnounceHandler mAnnounceHandler;
#if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
@@ -2212,7 +2243,6 @@ private:
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
ServiceAloc mServiceAlocs[kMaxServiceAlocs];
#endif
Callback<DetachCallback> mDetachGracefullyCallback;
#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE
Callback<otThreadParentResponseCallback> mParentResponseCallback;
#endif