diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 2d214b197f2384..b38f1a50a26475 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -144,6 +144,22 @@ uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::Interacti return count; } +uint32_t InteractionModelEngine::GetNumActiveReadHandlers(ReadHandler::InteractionType aType, FabricIndex aFabricIndex) const +{ + uint32_t count = 0; + + mReadHandlers.ForEachActiveObject([aType, aFabricIndex, &count](const ReadHandler * handler) { + if (handler->IsType(aType) && handler->GetAccessingFabricIndex() == aFabricIndex) + { + count++; + } + + return Loop::Continue; + }); + + return count; +} + ReadHandler * InteractionModelEngine::ActiveHandlerAt(unsigned int aIndex) { if (aIndex >= mReadHandlers.Allocated()) diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 9344248a6f2995..5f00f0df8e9e06 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -138,6 +138,11 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, uint32_t GetNumActiveReadHandlers() const; uint32_t GetNumActiveReadHandlers(ReadHandler::InteractionType type) const; + /** + * Returns the number of active readhandlers with a specific type on a specific fabric. + */ + uint32_t GetNumActiveReadHandlers(ReadHandler::InteractionType type, FabricIndex fabricIndex) const; + uint32_t GetNumActiveWriteHandlers() const; /** diff --git a/src/app/reporting/Engine.h b/src/app/reporting/Engine.h index 45d9441429ced5..854eaa5b7c84e5 100644 --- a/src/app/reporting/Engine.h +++ b/src/app/reporting/Engine.h @@ -126,6 +126,10 @@ class Engine void ScheduleUrgentEventDeliverySync(); +#if CONFIG_IM_BUILD_FOR_UNIT_TEST + size_t GetGlobalDirtySetSize() { return mGlobalDirtySet.Allocated(); } +#endif + private: friend class TestReportingEngine; diff --git a/src/controller/tests/data_model/TestRead.cpp b/src/controller/tests/data_model/TestRead.cpp index 82d722b71092e2..a3b7941a5eddf5 100644 --- a/src/controller/tests/data_model/TestRead.cpp +++ b/src/controller/tests/data_model/TestRead.cpp @@ -1556,11 +1556,9 @@ class TestReadCallback : public app::ReadClient::Callback CHIP_ERROR mLastError = CHIP_NO_ERROR; }; -void EstablishSubscriptions(nlTestSuite * apSuite, void * apContext, int32_t numSubs, int32_t pathPerSub, +void EstablishSubscriptions(nlTestSuite * apSuite, SessionHandle sessionHandle, int32_t numSubs, int32_t pathPerSub, app::ReadClient::Callback * callback, std::vector> & readClients) { - TestContext & ctx = *static_cast(apContext); - auto sessionHandle = ctx.GetSessionBobToAlice(); std::vector attributePaths( pathPerSub, app::AttributePathParams(kTestEndpointId, TestCluster::Id, TestCluster::Attributes::Int16u::Id)); @@ -1572,9 +1570,9 @@ void EstablishSubscriptions(nlTestSuite * apSuite, void * apContext, int32_t num for (int32_t i = 0; i < numSubs; i++) { - std::unique_ptr readClient = - std::make_unique(app::InteractionModelEngine::GetInstance(), &ctx.GetExchangeManager(), *callback, - app::ReadClient::InteractionType::Subscribe); + std::unique_ptr readClient = std::make_unique( + app::InteractionModelEngine::GetInstance(), app::InteractionModelEngine::GetInstance()->GetExchangeManager(), *callback, + app::ReadClient::InteractionType::Subscribe); NL_TEST_ASSERT(apSuite, readClient->SendRequest(readParams) == CHIP_NO_ERROR); readClients.push_back(std::move(readClient)); } @@ -1582,7 +1580,6 @@ void EstablishSubscriptions(nlTestSuite * apSuite, void * apContext, int32_t num } // namespace SubscriptionPathQuotaHelpers -// TODO: (#17381) Need to add the case with more than one fabrics. void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite * apSuite, void * apContext) { using namespace SubscriptionPathQuotaHelpers; @@ -1596,24 +1593,26 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&gTestReadInteraction); TestReadCallback readCallback; + TestReadCallback readCallbackFabric2; std::vector> readClients; // Intentially establish subscriptions using exceeded resources. app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(false); - EstablishSubscriptions(apSuite, apContext, kExpectedParallelSubs, + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, &readCallback, readClients); + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), kExpectedParallelSubs, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallback, readClients); // There are too many messages and the test (gcc_debug, which includes many sanity checks) will be quite slow. Note: report // engine is using ScheduleWork which cannot be handled by DrainAndServiceIO correctly. - ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), [&]() { - return readCallback.mOnSubscriptionEstablishedCount == static_cast(CHIP_IM_MAX_NUM_READ_HANDLER); - }); + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), + [&]() { return readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs + 1; }); NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == - kExpectedParallelSubs * - static_cast(app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1)); - NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs); + static_cast(kExpectedParallelSubs * app::InteractionModelEngine::kMinSupportedPathsPerSubscription + + app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1)); + NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == kExpectedParallelSubs + 1); // We have set up the environment for testing the evicting logic. app::InteractionModelEngine::GetInstance()->SetForceHandlerQuota(true); @@ -1624,8 +1623,8 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite { TestReadCallback callback; std::vector> outReadClient; - EstablishSubscriptions(apSuite, apContext, 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, &callback, - outReadClient); + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, &callback, outReadClient); ctx.DrainAndServiceIO(); @@ -1636,16 +1635,15 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite // The following check will trigger the logic in im to kill the read handlers that uses more paths than the limit per fabric. { - TestReadCallback callback; - std::vector> outReadClient; - EstablishSubscriptions(apSuite, apContext, 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &callback, - outReadClient); + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallback, readClients); + readCallback.ClearCounters(); ctx.DrainAndServiceIO(); // This read handler should evict some existing subscriptions for enough space - NL_TEST_ASSERT(apSuite, callback.mOnSubscriptionEstablishedCount == 1); - NL_TEST_ASSERT(apSuite, callback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerSubscription); + NL_TEST_ASSERT(apSuite, readCallback.mOnSubscriptionEstablishedCount == 1); + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == app::InteractionModelEngine::kMinSupportedPathsPerSubscription); } { @@ -1656,12 +1654,74 @@ void TestReadInteraction::TestReadHandler_KillOverQuotaSubscriptions(nlTestSuite app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(path); } readCallback.ClearCounters(); + ctx.DrainAndServiceIO(); + // Ensure the global dirty set is cleared. + NL_TEST_ASSERT(apSuite, app::InteractionModelEngine::GetInstance()->GetReportingEngine().GetGlobalDirtySetSize() == 0); - NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount <= kExpectedParallelPaths); + // We should evict the subscriptions with excess resources, so we should use exactly all resources. + NL_TEST_ASSERT(apSuite, readCallback.mAttributeCount == kExpectedParallelPaths); NL_TEST_ASSERT(apSuite, - app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() <= - static_cast(kExpectedParallelSubs)); + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers() == + static_cast(kExpectedParallelSubs)); + + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Subscribe, ctx.GetAliceFabricIndex()) > + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + + // The following check will trigger the logic in im to kill the read handlers that use more paths than the limit per fabric. + { + EstablishSubscriptions(apSuite, ctx.GetSessionAliceToBob(), + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallbackFabric2, readClients); + + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), + [&]() { return readCallbackFabric2.mOnSubscriptionEstablishedCount == 1; }); + + // This read handler should evict some existing subscriptions for enough space + NL_TEST_ASSERT(apSuite, + readCallbackFabric2.mOnSubscriptionEstablishedCount == + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + NL_TEST_ASSERT(apSuite, + readCallbackFabric2.mAttributeCount == + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + } + + { + app::AttributePathParams path; + path.mEndpointId = kTestEndpointId; + path.mClusterId = TestCluster::Id; + path.mAttributeId = TestCluster::Attributes::Int16u::Id; + app::InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(path); + } + readCallback.ClearCounters(); + readCallbackFabric2.ClearCounters(); + + // Run until the global dirtyset is cleared. + ctx.GetIOContext().DriveIOUntil(System::Clock::Seconds16(60), []() { + return app::InteractionModelEngine::GetInstance()->GetReportingEngine().GetGlobalDirtySetSize() == 0 && + app::InteractionModelEngine::GetInstance()->GetReportingEngine().GetNumReportsInFlight() == 0; + }); + + // Some subscriptions on fabric 1 should be evicted since fabric 1 is using more resources than the limits. + NL_TEST_ASSERT(apSuite, + readCallback.mAttributeCount == + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + NL_TEST_ASSERT(apSuite, + readCallbackFabric2.mAttributeCount == + app::InteractionModelEngine::kMinSupportedPathsPerSubscription * + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Subscribe, ctx.GetAliceFabricIndex()) == + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); + NL_TEST_ASSERT(apSuite, + app::InteractionModelEngine::GetInstance()->GetNumActiveReadHandlers( + app::ReadHandler::InteractionType::Subscribe, ctx.GetBobFabricIndex()) == + app::InteractionModelEngine::kMinSupportedSubscriptionsPerFabric); app::InteractionModelEngine::GetInstance()->ShutdownActiveReads(); ctx.DrainAndServiceIO(); @@ -1695,7 +1755,7 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * app::InteractionModelEngine::GetInstance()->SetPathPoolCapacityForSubscriptions(kExpectedParallelPaths); // This should just use all availbale resources. - EstablishSubscriptions(apSuite, apContext, kExpectedParallelSubs, + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), kExpectedParallelSubs, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallback, readClients); ctx.DrainAndServiceIO(); @@ -1713,8 +1773,8 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * { TestReadCallback callback; std::vector> outReadClient; - EstablishSubscriptions(apSuite, apContext, 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, &callback, - outReadClient); + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription + 1, &callback, outReadClient); ctx.DrainAndServiceIO(); @@ -1725,8 +1785,8 @@ void TestReadInteraction::TestReadHandler_KillOldestSubscriptions(nlTestSuite * // The following check will trigger the logic in im to kill the read handlers that uses more paths than the limit per fabric. { - EstablishSubscriptions(apSuite, apContext, 1, app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallback, - readClients); + EstablishSubscriptions(apSuite, ctx.GetSessionBobToAlice(), 1, + app::InteractionModelEngine::kMinSupportedPathsPerSubscription, &readCallback, readClients); readCallback.ClearCounters(); ctx.DrainAndServiceIO();