Skip to content

Fix the matter server shutdown flow and add DataModelShutdown #38515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions src/app/InteractionModelEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,39 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS

/** Previously, ShutdowActiveReads() was only available in unit test builds via `#if CONFIG_BUILD_FOR_HOST_UNIT_TEST`.
* However, we now require proper cleanup of active read handlers during the application's shutdown sequence,
* especially before calling SetDataModelProvider(nullptr). This ensures that there are no ongoing IM reads,
* which could otherwise result in crashes or undefined behavior.

* Therefore, this method has been moved out of the CONFIG_BUILD_FOR_HOST_UNIT_TEST block so that it can be
* called as part of the normal shutdown flow.

* Note: This was originally introduced for unit tests to clean up subscriptions created via the high-level
* APIs in src/controller/ReadInteraction.h, which did not expose a way to shut down active subscriptions.
**/

void ShutdownActiveReads()
{
#if CHIP_CONFIG_ENABLE_READ_CLIENT
for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
{
readClient->mpImEngine = nullptr;
auto * tmpClient = readClient->GetNextClient();
readClient->SetNextClient(nullptr);
readClient->Close(CHIP_NO_ERROR);
readClient = tmpClient;
}

//
// After that, we just null out our tracker.
//
mpActiveReadClientList = nullptr;
#endif // CHIP_CONFIG_ENABLE_READ_CLIENT

mReadHandlers.ReleaseAll();
}

#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
//
// Get direct access to the underlying read handler pool
Expand Down Expand Up @@ -391,33 +424,6 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
mSubscriptionResumptionRetrySecondsOverride = seconds;
}
#endif

//
// When testing subscriptions using the high-level APIs in src/controller/ReadInteraction.h,
// they don't provide for the ability to shut down those subscriptions after they've been established.
//
// So for the purposes of unit tests, add a helper here to shut down and clean-up all active handlers.
//
void ShutdownActiveReads()
{
#if CHIP_CONFIG_ENABLE_READ_CLIENT
for (auto * readClient = mpActiveReadClientList; readClient != nullptr;)
{
readClient->mpImEngine = nullptr;
auto * tmpClient = readClient->GetNextClient();
readClient->SetNextClient(nullptr);
readClient->Close(CHIP_NO_ERROR);
readClient = tmpClient;
}

//
// After that, we just null out our tracker.
//
mpActiveReadClientList = nullptr;
#endif // CHIP_CONFIG_ENABLE_READ_CLIENT

mReadHandlers.ReleaseAll();
}
#endif

DataModel::Provider * GetDataModelProvider() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1302,3 +1302,10 @@ void CameraAvSettingsUserLevelMgmtServer::HandleDPTZRelativeMove(HandlerContext
*
*/
void MatterCameraAvSettingsUserLevelManagementPluginServerInitCallback() {}

/** @brief Camera AV Settings User Level Management Cluster Server Shutdown
*
* Server Shutdown
*
*/
void MatterCameraAvSettingsUserLevelManagementPluginServerShutdownCallback() {}
2 changes: 2 additions & 0 deletions src/app/clusters/scenes-server/scenes-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ CHIP_ERROR ScenesServer::Init()
void ScenesServer::Shutdown()
{
Server::GetInstance().GetFabricTable().RemoveFabricDelegate(&gFabricDelegate);
SceneTable * sceneTable = scenes::GetSceneTableImpl();
sceneTable->Finish();
CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this);
AttributeAccessInterfaceRegistry::Instance().Unregister(this);

Expand Down
11 changes: 11 additions & 0 deletions src/app/server/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,14 @@ void Server::Shutdown()
#if CHIP_CONFIG_ENABLE_ICD_SERVER
app::DnssdServer::Instance().SetICDManager(nullptr);
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER

/** Shutdown all active read handlers to ensure a clean shutdown of the Interaction Model.
* This is required before resetting the DataModelProvider, as ongoing reads may access it
* and lead to crashes or undefined behavior.
**/
app::InteractionModelEngine::GetInstance()->ShutdownActiveReads();

app::InteractionModelEngine::GetInstance()->SetDataModelProvider(nullptr);
app::DnssdServer::Instance().SetCommissioningModeProvider(nullptr);
Dnssd::ServiceAdvertiser::Instance().Shutdown();

Expand Down Expand Up @@ -681,11 +689,13 @@ void Server::Shutdown()
mCommissioningWindowManager.Shutdown();
mMessageCounterManager.Shutdown();
mExchangeMgr.Shutdown();
mFabrics.RemoveFabricDelegate(&mFabricDelegate);
mSessions.Shutdown();
mTransports.Close();
mAccessControl.Finish();
Access::ResetAccessControlToDefault();
Credentials::SetGroupDataProvider(nullptr);
app::EventManagement::GetInstance().DestroyEventManagement();
#if CHIP_CONFIG_ENABLE_ICD_SERVER
// Remove Test Event Trigger Handler
if (mTestEventTriggerDelegate != nullptr)
Expand All @@ -695,6 +705,7 @@ void Server::Shutdown()
mICDManager.Shutdown();
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER

mFabrics.Shutdown();
// TODO(16969): Remove chip::Platform::MemoryInit() call from Server class, it belongs to outer code
chip::Platform::MemoryShutdown();
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/tests/test-ember-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ uint16_t emberAfGetClusterServerEndpointIndex(chip::EndpointId endpoint, chip::C
return endpoint;
}

// Mock function for linking
// Mock functions for linking
void InitDataModelHandler() {}
void ShutdownDataModelHandler() {}
5 changes: 5 additions & 0 deletions src/app/util/DataModelHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ void InitDataModelHandler()
emberAfEndpointConfigure();
emberAfInit();
}

void ShutdownDataModelHandler()
{
emberAfShutdown();
}
5 changes: 5 additions & 0 deletions src/app/util/DataModelHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@
* data model messages.
*/
void InitDataModelHandler();

/**
* Shutdown the data model.
*/
void ShutdownDataModelHandler();
2 changes: 2 additions & 0 deletions src/app/util/attribute-storage-detail.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extern uint8_t attributeData[]; // main storage bucket for all attributes

void emAfCallInits();

void emAfCallShutdowns();

chip::Protocols::InteractionModel::Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord,
const EmberAfAttributeMetadata ** metadata, uint8_t * buffer,
uint16_t readLength, bool write);
Expand Down
16 changes: 15 additions & 1 deletion src/app/util/attribute-storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ static void shutdownEndpoint(EmberAfDefinedEndpoint * definedEndpoint)
const EmberAfEndpointType * epType = definedEndpoint->endpointType;
for (clusterIndex = 0; clusterIndex < epType->clusterCount; clusterIndex++)
{
const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]);
const EmberAfCluster * cluster = &(epType->cluster[clusterIndex]);
emberAfClusterShutdownCallback(definedEndpoint->endpoint, cluster->clusterId);
EmberAfGenericClusterFunction f = emberAfFindClusterFunction(cluster, MATTER_CLUSTER_FLAG_SHUTDOWN_FUNCTION);
if (f != nullptr)
{
Expand All @@ -503,6 +504,19 @@ void emAfCallInits()
}
}

// Calls the shutdown functions.
void emAfCallShutdowns()
{
uint16_t index;
for (index = 0; index < emberAfEndpointCount(); index++)
{
if (emberAfEndpointIndexIsEnabled(index))
{
shutdownEndpoint(&(emAfEndpoints[index]));
}
}
}

// Returns the pointer to metadata, or null if it is not found
const EmberAfAttributeMetadata * emberAfLocateAttributeMetadata(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId)
{
Expand Down
11 changes: 11 additions & 0 deletions src/app/util/generic-callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
*/
void emberAfClusterInitCallback(chip::EndpointId endpoint, chip::ClusterId clusterId);

/** @brief Cluster Shutdown
*
* This function is called when a specific cluster is shutdown. It gives the
* application an opportunity to take care of cluster shutdown procedures.
* It is called exactly once for each endpoint where cluster is present.
*
* @param endpoint Ver.: always
* @param clusterId Ver.: always
*/
void emberAfClusterShutdownCallback(chip::EndpointId endpoint, chip::ClusterId clusterId);

/** @brief Attribute Read Access
*
* This function is called whenever the Application Framework needs to check
Expand Down
69 changes: 69 additions & 0 deletions src/app/util/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ void emberAfInit()
emAfCallInits();
}

// ****************************************
// Shutdown Clusters
// ****************************************
void emberAfShutdown()
{
emAfCallShutdowns();
MATTER_PLUGINS_SHUTDOWN
}

// Cluster init functions that don't have a cluster implementation to define
// them in.
void MatterBallastConfigurationPluginServerInitCallback() {}
Expand Down Expand Up @@ -142,6 +151,66 @@ void MatterWaterHeaterModePluginServerInitCallback() {}
void MatterCommodityPricePluginServerInitCallback() {}
void MatterElectricalGridConditionsPluginServerInitCallback() {}

// Cluster shutdown functions that don't have a cluster implementation to define
// them in.
void MatterBallastConfigurationPluginServerShutdownCallback() {}
void MatterBooleanStatePluginServerShutdownCallback() {}
void MatterRelativeHumidityMeasurementPluginServerShutdownCallback() {}
void MatterIlluminanceMeasurementPluginServerShutdownCallback() {}
void MatterBinaryInputBasicPluginServerShutdownCallback() {}
void MatterPressureMeasurementPluginServerShutdownCallback() {}
void MatterTemperatureMeasurementPluginServerShutdownCallback() {}
void MatterFlowMeasurementPluginServerShutdownCallback() {}
void MatterThermostatUserInterfaceConfigurationPluginServerShutdownCallback() {}
void MatterBridgedDeviceBasicInformationPluginServerShutdownCallback() {}
void MatterPowerConfigurationPluginServerShutdownCallback() {}
void MatterPowerProfilePluginServerShutdownCallback() {}
void MatterPulseWidthModulationPluginServerShutdownCallback() {}
void MatterAlarmsPluginServerShutdownCallback() {}
void MatterTimePluginServerShutdownCallback() {}
void MatterAclPluginServerShutdownCallback() {}
void MatterPollControlPluginServerShutdownCallback() {}
void MatterProxyValidPluginServerShutdownCallback() {}
void MatterProxyDiscoveryPluginServerShutdownCallback() {}
void MatterProxyConfigurationPluginServerShutdownCallback() {}
void MatterFanControlPluginServerShutdownCallback() {}
void MatterActivatedCarbonFilterMonitoringPluginServerShutdownCallback() {}
void MatterHepaFilterMonitoringPluginServerShutdownCallback() {}
void MatterAirQualityPluginServerShutdownCallback() {}
void MatterCarbonMonoxideConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterCarbonDioxideConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterFormaldehydeConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterNitrogenDioxideConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterOzoneConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterPm10ConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterPm1ConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterPm25ConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterRadonConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterTotalVolatileOrganicCompoundsConcentrationMeasurementPluginServerShutdownCallback() {}
void MatterRvcRunModePluginServerShutdownCallback() {}
void MatterRvcCleanModePluginServerShutdownCallback() {}
void MatterDishwasherModePluginServerShutdownCallback() {}
void MatterLaundryWasherModePluginServerShutdownCallback() {}
void MatterRefrigeratorAndTemperatureControlledCabinetModePluginServerShutdownCallback() {}
void MatterOperationalStatePluginServerShutdownCallback() {}
void MatterRvcOperationalStatePluginServerShutdownCallback() {}
void MatterOvenModePluginServerShutdownCallback() {}
void MatterOvenCavityOperationalStatePluginServerShutdownCallback() {}
void MatterDishwasherAlarmPluginServerShutdownCallback() {}
void MatterMicrowaveOvenModePluginServerShutdownCallback() {}
void MatterDeviceEnergyManagementModePluginServerShutdownCallback() {}
void MatterEnergyEvseModePluginServerShutdownCallback() {}
void MatterPowerTopologyPluginServerShutdownCallback() {}
void MatterElectricalEnergyMeasurementPluginServerShutdownCallback() {}
void MatterElectricalPowerMeasurementPluginServerShutdownCallback() {}
void MatterServiceAreaPluginServerShutdownCallback() {}
void MatterWaterHeaterManagementPluginServerShutdownCallback() {}
void MatterWaterHeaterModePluginServerShutdownCallback() {}
void MatterMeterIdentificationPluginServerShutdownCallback() {}
void MatterClosureDimensionPluginServerShutdownCallback() {}
void MatterElectricalGridConditionsPluginServerShutdownCallback() {}
void MatterCommodityPricePluginServerShutdownCallback() {}

bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId)
{
return (emberAfGetServerAttributeIndexByAttributeId(endpoint, clusterId, attributeId) != UINT16_MAX);
Expand Down
1 change: 1 addition & 0 deletions src/app/util/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

void emberAfInit();

void emberAfShutdown();
/**
* Retrieves the difference between the two passed values.
* This function assumes that the two values have the same endianness.
Expand Down
1 change: 1 addition & 0 deletions src/controller/EmptyDataModelHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
#include <app/util/DataModelHandler.h>

void InitDataModelHandler() {}
void ShutdownDataModelHandler() {}
4 changes: 3 additions & 1 deletion src/controller/tests/TestServerCommandDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,10 @@ class DispatchTestDataModel : public CodegenDataModelProvider

protected:
// Since the current unit tests do not involve any cluster implementations, we override InitDataModelForTesting
// to do nothing, thereby preventing calls to the Ember-specific InitDataModelHandler.
// ShutdownDataModelForTesting to do nothing, thereby preventing calls to the Ember-specific InitDataModelHandler
// and ShutdownDataModelHandler.
void InitDataModelForTesting() override {}
void ShutdownDataModelForTesting() override {}
};

class TestServerCommandDispatch : public chip::Test::AppContext
Expand Down
3 changes: 2 additions & 1 deletion src/controller/tests/data_model/DataModelFixtures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ using namespace chip::app::Clusters;
using namespace chip::app::Clusters::UnitTesting;
using namespace chip::Protocols;

// Mock function for linking
// Mock functions for linking
void InitDataModelHandler() {}
void ShutdownDataModelHandler() {}

namespace chip {
namespace app {
Expand Down
1 change: 1 addition & 0 deletions src/credentials/FabricTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ void FabricTable::Shutdown()
delegate->next = nullptr;
delegate = temp;
}
mDelegateListRoot = nullptr;

RevertPendingFabricData();
for (FabricInfo & fabricInfo : mStates)
Expand Down
8 changes: 8 additions & 0 deletions src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ void emberAfClusterInitCallback(EndpointId endpoint, ClusterId clusterId)
// clusters dont use it.
}

void emberAfClusterShutdownCallback(EndpointId endpoint, ClusterId clusterId)
{
assertChipStackLockedByCurrentThread();

// No-op: Descriptor and OTA do not need this, and our client-defined
// clusters dont use it.
}

Protocols::InteractionModel::Status emAfWriteAttributeExternal(const ConcreteAttributePath & path,
const EmberAfWriteDataInput & input)
{
Expand Down
2 changes: 2 additions & 0 deletions src/darwin/Framework/CHIP/app/PluginApplicationCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@
*/

void MatterDescriptorPluginServerInitCallback();
void MatterDescriptorPluginServerShutdownCallback();

#define MATTER_PLUGINS_INIT MatterDescriptorPluginServerInitCallback();
#define MATTER_PLUGINS_SHUTDOWN MatterDescriptorPluginServerShutdownCallback();
7 changes: 7 additions & 0 deletions src/data-model-providers/codegen/CodegenDataModelProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ DefaultAttributePersistenceProvider gDefaultAttributePersistence;
CHIP_ERROR CodegenDataModelProvider::Shutdown()
{
Reset();
ShutdownDataModelForTesting();
mRegistry.ClearContext();
return CHIP_NO_ERROR;
}
Expand Down Expand Up @@ -596,6 +597,12 @@ void CodegenDataModelProvider::InitDataModelForTesting()
InitDataModelHandler();
}

void CodegenDataModelProvider::ShutdownDataModelForTesting()
{
// Call the Ember-specific ShutdownDataModelHandler
ShutdownDataModelHandler();
}

CHIP_ERROR CodegenDataModelProvider::DeviceTypes(EndpointId endpointId, ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> & builder)
{
std::optional<unsigned> endpoint_index = TryFindEndpointIndex(endpointId);
Expand Down
5 changes: 3 additions & 2 deletions src/data-model-providers/codegen/CodegenDataModelProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ class CodegenDataModelProvider : public DataModel::Provider

protected:
// Temporary hack for a test: Initializes the data model for testing purposes only.
// This method serves as a placeholder and should NOT be used outside of specific tests.
// It is expected to be removed or replaced with a proper implementation in the future.TODO:(#36837).
// These methods serve as placeholders and should NOT be used outside of specific tests.
// They are expected to be removed or replaced with a proper implementation in the future.TODO:(#36837).
virtual void InitDataModelForTesting();
virtual void ShutdownDataModelForTesting();

private:
// Iteration is often done in a tight loop going through all values.
Expand Down
Loading
Loading