From bff4829e1f17b84be519a1f5e413ded37cbba485 Mon Sep 17 00:00:00 2001 From: tabbas651 Date: Fri, 7 Aug 2026 19:21:06 -0400 Subject: [PATCH 1/2] TCXB8-4164: Re-enable profile scheduler when hash-match skips dead thread When a profile's TimeoutThread dies silently (e.g. pthread init failure at boot during GFO restore), subsequent WebConfig or XConf pushes with the same hash skip re-registration, leaving the profile permanently unscheduled. Add isProfileSchedulerRunning() to check if a profile's scheduler thread is alive. In both msgpack and JSON hash-match paths, call enableProfile() to restart the scheduler when the thread is not running. --- source/bulkdata/reportprofiles.c | 12 ++++++++++-- source/scheduler/scheduler.c | 23 +++++++++++++++++++++++ source/scheduler/scheduler.h | 2 ++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/source/bulkdata/reportprofiles.c b/source/bulkdata/reportprofiles.c index ce64989e..62a4c884 100644 --- a/source/bulkdata/reportprofiles.c +++ b/source/bulkdata/reportprofiles.c @@ -993,7 +993,11 @@ void ReportProfiles_ProcessReportProfilesBlob(cJSON *profiles_root, bool rprofil if(!strcmp(existingProfileHash, profileEntry->hash)) { - T2Debug("%s Profile hash for %s is same as previous profile, ignore processing config\n", __FUNCTION__, profileName); + if(!isProfileSchedulerRunning(profileName)) + { + T2Warning("Profile %s scheduler not running, re-enabling\n", profileName); + enableProfile(profileName); + } free(existingProfileHash); continue; } @@ -1404,7 +1408,11 @@ int __ReportProfiles_ProcessReportProfilesMsgPackBlob(void *msgpack, bool checkP { if(0 == msgpack_strcmp(hashObj, existingProfileHash)) { - T2Info("Profile %s with %s hash already exist \n", profileName, existingProfileHash); + if(!isProfileSchedulerRunning(profileName)) + { + T2Warning("Profile %s scheduler not running, re-enabling\n", profileName); + enableProfile(profileName); + } free(profileName); free(existingProfileHash); continue; diff --git a/source/scheduler/scheduler.c b/source/scheduler/scheduler.c index 5c9d3195..d768fa0d 100644 --- a/source/scheduler/scheduler.c +++ b/source/scheduler/scheduler.c @@ -430,6 +430,29 @@ T2ERROR SendInterruptToTimeoutThread(char* profileName) return T2ERROR_SUCCESS; } +bool isProfileSchedulerRunning(const char* profileName) +{ + if(!sc_initialized || profileName == NULL) + return false; + + if(pthread_mutex_lock(&scMutex) != 0) + return false; + + size_t index = 0; + for(; index < profileList->count; ++index) + { + SchedulerProfile *tProfile = (SchedulerProfile *)Vector_At(profileList, index); + if(strcmp(tProfile->name, profileName) == 0) + { + bool running = (tProfile->repeat && !tProfile->terminated); + pthread_mutex_unlock(&scMutex); + return running; + } + } + pthread_mutex_unlock(&scMutex); + return false; +} + T2ERROR initScheduler(TimeoutNotificationCB notificationCb, ActivationTimeoutCB activationCB, NotifySchedulerstartCB notifyschedulerCB) { T2Debug("%s ++in\n", __FUNCTION__); diff --git a/source/scheduler/scheduler.h b/source/scheduler/scheduler.h index 356cf398..f4a8b443 100644 --- a/source/scheduler/scheduler.h +++ b/source/scheduler/scheduler.h @@ -60,6 +60,8 @@ T2ERROR unregisterProfileFromScheduler(const char* profileName); T2ERROR SendInterruptToTimeoutThread(char* profileName); +bool isProfileSchedulerRunning(const char* profileName); + bool get_retainseekmap(); void set_retainseekmap(bool value); From 987ebaa18a256badd6fe38bb3e01bb8e8536dcbb Mon Sep 17 00:00:00 2001 From: tabbas651 Date: Fri, 7 Aug 2026 19:40:17 -0400 Subject: [PATCH 2/2] Addressed the Copilot Review comments --- source/scheduler/scheduler.c | 8 ++++-- source/test/bulkdata/SchedulerMock.cpp | 7 +++++ source/test/bulkdata/SchedulerMock.h | 1 + source/test/scheduler/schedulerTest.cpp | 38 +++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/source/scheduler/scheduler.c b/source/scheduler/scheduler.c index d768fa0d..6f1b2ef9 100644 --- a/source/scheduler/scheduler.c +++ b/source/scheduler/scheduler.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -432,7 +433,7 @@ T2ERROR SendInterruptToTimeoutThread(char* profileName) bool isProfileSchedulerRunning(const char* profileName) { - if(!sc_initialized || profileName == NULL) + if(!sc_initialized || profileName == NULL || profileList == NULL) return false; if(pthread_mutex_lock(&scMutex) != 0) @@ -442,9 +443,12 @@ bool isProfileSchedulerRunning(const char* profileName) for(; index < profileList->count; ++index) { SchedulerProfile *tProfile = (SchedulerProfile *)Vector_At(profileList, index); + if(tProfile == NULL || tProfile->name == NULL) + continue; if(strcmp(tProfile->name, profileName) == 0) { - bool running = (tProfile->repeat && !tProfile->terminated); + /* pthread_kill with signal 0 checks if the thread is still alive */ + bool running = (pthread_kill(tProfile->tId, 0) == 0); pthread_mutex_unlock(&scMutex); return running; } diff --git a/source/test/bulkdata/SchedulerMock.cpp b/source/test/bulkdata/SchedulerMock.cpp index 0611b642..b53cb382 100755 --- a/source/test/bulkdata/SchedulerMock.cpp +++ b/source/test/bulkdata/SchedulerMock.cpp @@ -129,4 +129,11 @@ int getLapsedTime(struct timespec *result, struct timespec *x, struct timespec * return 0; } +bool isProfileSchedulerRunning(const char* profileName) +{ + if (g_schedulerMock) + return g_schedulerMock->isProfileSchedulerRunning(profileName); + return false; +} + } // extern "C" diff --git a/source/test/bulkdata/SchedulerMock.h b/source/test/bulkdata/SchedulerMock.h index ad1c6d30..9c1a3da0 100755 --- a/source/test/bulkdata/SchedulerMock.h +++ b/source/test/bulkdata/SchedulerMock.h @@ -38,6 +38,7 @@ class SchedulerMock MOCK_METHOD(bool, get_retainseekmap, (), ()); MOCK_METHOD(void, set_retainseekmap, (bool value), ()); MOCK_METHOD(int, getLapsedTime, (struct timespec *result, struct timespec *x, struct timespec *y), ()); + MOCK_METHOD(bool, isProfileSchedulerRunning, (const char* profileName), ()); }; extern SchedulerMock* g_schedulerMock; diff --git a/source/test/scheduler/schedulerTest.cpp b/source/test/scheduler/schedulerTest.cpp index 53ec7ada..f5d55037 100644 --- a/source/test/scheduler/schedulerTest.cpp +++ b/source/test/scheduler/schedulerTest.cpp @@ -433,6 +433,44 @@ TEST(UNREGISTERPROFILEFROMSCHEDULER, ALREADY_REMOVED) EXPECT_EQ(T2ERROR_FAILURE, unregisterProfileFromScheduler("REMOVEME")); uninitScheduler(); } +TEST(ISPROFILESCHEDULERRUNNING, NOT_INITIALIZED) +{ + uninitScheduler(); + EXPECT_FALSE(isProfileSchedulerRunning("RDKB_Profile")); +} + +TEST(ISPROFILESCHEDULERRUNNING, NULL_PROFILE) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + EXPECT_FALSE(isProfileSchedulerRunning(NULL)); + uninitScheduler(); +} + +TEST(ISPROFILESCHEDULERRUNNING, PROFILE_NOT_FOUND) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + EXPECT_FALSE(isProfileSchedulerRunning("NONEXISTENT")); + uninitScheduler(); +} + +TEST(ISPROFILESCHEDULERRUNNING, RUNNING_PROFILE) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + registerProfileWithScheduler("RUNNING_TEST", 300, 3600, false, true, false, 0, "0001-01-01T00:00:00Z"); + EXPECT_TRUE(isProfileSchedulerRunning("RUNNING_TEST")); + unregisterProfileFromScheduler("RUNNING_TEST"); + uninitScheduler(); +} + +TEST(ISPROFILESCHEDULERRUNNING, AFTER_UNREGISTER) +{ + initScheduler((TimeoutNotificationCB)ReportProfiles_ToutCb, (ActivationTimeoutCB)ReportProfiles_ActivationToutCb, (NotifySchedulerstartCB)NotifySchedulerstartCb); + registerProfileWithScheduler("UNREG_TEST", 300, 3600, false, true, false, 0, "0001-01-01T00:00:00Z"); + unregisterProfileFromScheduler("UNREG_TEST"); + EXPECT_FALSE(isProfileSchedulerRunning("UNREG_TEST")); + uninitScheduler(); +} + #ifdef GTEST_ENABLE extern "C" {