diff --git a/frameworks/libs/distributeddb/common/include/log_print.h b/frameworks/libs/distributeddb/common/include/log_print.h index 39028c36a7a7775936debe8863be218c28f7e883..e64af563337e42d7879cd60d7ca6d20f58608d4a 100644 --- a/frameworks/libs/distributeddb/common/include/log_print.h +++ b/frameworks/libs/distributeddb/common/include/log_print.h @@ -21,7 +21,9 @@ #include #include #include +#include #include +#include namespace DistributedDB { constexpr const char *LOG_TAG_KV = "DistributedDB"; @@ -40,12 +42,16 @@ public: static std::shared_ptr GetInstance(); static void DeleteInstance(); static void Log(Level level, const std::string &tag, const char *func, int line, const char *format, ...); + // for test + static void SetInstanceDestroyed(bool isDestroyed); private: virtual void Print(Level level, const std::string &tag, const std::string &msg) = 0; static void PreparePrivateLog(const char *format, std::string &outStrFormat); static std::shared_ptr logHandler; static std::shared_mutex logMutex; + static std::once_flag initFlag; + static std::atomic instanceDestroyed; static const std::string PRIVATE_TAG; }; diff --git a/frameworks/libs/distributeddb/common/src/log_print.cpp b/frameworks/libs/distributeddb/common/src/log_print.cpp index 30a0a47ba4c7c6195a2e6d2cea1e355b1ef53b0d..ae1895dd91766f30d5920d80fd48dba55ef01e06 100644 --- a/frameworks/libs/distributeddb/common/src/log_print.cpp +++ b/frameworks/libs/distributeddb/common/src/log_print.cpp @@ -27,6 +27,8 @@ namespace DistributedDB { std::shared_ptr Logger::logHandler = nullptr; std::shared_mutex Logger::logMutex; +std::once_flag Logger::initFlag; +std::atomic Logger::instanceDestroyed{false}; const std::string Logger::PRIVATE_TAG = "s{private}"; class HiLogger : public Logger { @@ -66,31 +68,34 @@ public: std::shared_ptr Logger::GetInstance() { - std::shared_ptr inst = nullptr; + // Fast path: use shared_lock for read operation (allows concurrent readers) { std::shared_lock readLock(logMutex); if (logHandler != nullptr) { - inst = logHandler; - return inst; + return logHandler; } } - { + + // Slow path: initialize using call_once for thread-safety + // call_once ensures the initialization code runs exactly once + std::call_once(initFlag, []() { + auto newPtr = std::make_shared(); std::unique_lock writeLock(logMutex); - if (logHandler != nullptr) { - inst = logHandler; - return inst; - } - inst = std::make_shared(); - logHandler = inst; - } - LOGI("[Logger] init"); + logHandler = newPtr; + }); + + // Since call_once guarantees initialization, return the instance + // Note: logHandler is now guaranteed to be non-null after call_once + std::shared_lock readLock(logMutex); return logHandler; } void Logger::DeleteInstance() { + // Use unique_lock for write operation (exclusive access) std::unique_lock writeLock(logMutex); logHandler.reset(); + instanceDestroyed.store(true, std::memory_order_release); } void Logger::Log(Level level, const std::string &tag, const char *func, int line, const char *format, ...) @@ -101,6 +106,11 @@ void Logger::Log(Level level, const std::string &tag, const char *func, int line return; } + // Early return if instance has been destroyed (e.g., during process shutdown) + if (instanceDestroyed.load(std::memory_order_acquire)) { + return; + } + static const int maxLogLength = 1024; va_list argList; va_start(argList, format); @@ -115,7 +125,11 @@ void Logger::Log(Level level, const std::string &tag, const char *func, int line msg = logBuff; } va_end(argList); - Logger::GetInstance()->Print(level, tag, msg); + + auto logger = GetInstance(); + if (logger != nullptr) { + logger->Print(level, tag, msg); + } } void Logger::PreparePrivateLog(const char *format, std::string &outStrFormat) @@ -130,4 +144,9 @@ void Logger::PreparePrivateLog(const char *format, std::string &outStrFormat) #endif } } + +void Logger::SetInstanceDestroyed(bool isDestroyed) +{ + instanceDestroyed.store(isDestroyed, std::memory_order_release); +} } // namespace DistributedDB diff --git a/frameworks/libs/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp b/frameworks/libs/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp index 8ce9a784034b49a42a22526aadb7747a106c62b8..3c6f0eef75a41b89375a9d6f2b9123c6e561b7fd 100644 --- a/frameworks/libs/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp +++ b/frameworks/libs/distributeddb/test/unittest/common/common/distributeddb_common_test.cpp @@ -46,6 +46,13 @@ namespace { auto g_kvNbDelegateCallback = bind(&DistributedDBToolsUnitTest::KvStoreNbDelegateCallback, std::placeholders::_1, std::placeholders::_2, std::ref(g_kvDelegateStatus), std::ref(g_kvNbDelegatePtr)); + // Constants for Logger tests + constexpr int LOGGER_THREAD_COUNT_HIGH = 100; + constexpr int LOGGER_THREAD_COUNT_LOW = 20; + constexpr int LOGGER_REPEAT_COUNT = 10; + constexpr int LOGGER_TEST_LINE_NUMBER = 100; + constexpr int LOGGER_DELETE_THREAD_RATIO = 3; + class DistributedDBCommonTest : public testing::Test { public: static void SetUpTestCase(void); @@ -61,7 +68,11 @@ void DistributedDBCommonTest::SetUpTestCase(void) g_mgr.SetKvStoreConfig(g_config); } -void DistributedDBCommonTest::TearDownTestCase(void) {} +void DistributedDBCommonTest::TearDownTestCase() +{ + // set the instance destroyed flag to false, for next test case + Logger::SetInstanceDestroyed(false); +} void DistributedDBCommonTest::SetUp(void) { @@ -958,4 +969,228 @@ HWTEST_F(DistributedDBCommonTest, PropertiesTest002, TestSize.Level0) properties2 = copyProperties; EXPECT_EQ(isEncrypted, properties2.IsEncrypted()); } + +/** + * @tc.name: LoggerCallOnceTest + * @tc.desc: Test Logger only initializes once using std::call_once. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerCallOnceTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + std::vector> instances; + const int threadCount = LOGGER_THREAD_COUNT_HIGH; + + // Launch multiple threads calling GetInstance simultaneously + std::vector threads; + for (int i = 0; i < threadCount; ++i) { + threads.emplace_back([&instances]() { + auto logger = Logger::GetInstance(); + instances.push_back(logger); + }); + } + + // Wait for all threads + for (auto &thread : threads) { + thread.join(); + } + + // Verify all instances point to the same object (call_once ensures single initialization) + ASSERT_GT(instances.size(), 0); + for (size_t i = 1; i < instances.size(); ++i) { + EXPECT_EQ(instances[0], instances[i]); + } + + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerInstanceDestroyedFlagTest + * @tc.desc: Test instanceDestroyed atomic flag behavior. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerInstanceDestroyedFlagTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // Get instance - should not be destroyed + auto logger = Logger::GetInstance(); + ASSERT_NE(logger, nullptr); + + // Delete instance - should set destroyed flag + Logger::DeleteInstance(); + + // Get new instance after deletion + auto newLogger = Logger::GetInstance(); + ASSERT_NE(newLogger, nullptr); + + // Verify instances are different (new instance created) + EXPECT_NE(logger, newLogger); + + // Cleanup + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerLogAfterDestroyTest + * @tc.desc: Test Log function after instance is destroyed. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerLogAfterDestroyTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // Create and destroy instance + auto logger = Logger::GetInstance(); + ASSERT_NE(logger, nullptr); + Logger::DeleteInstance(); + + // Log calls after DeleteInstance should be safe (early return) + // This simulates process exit scenario + Logger::Log(Logger::Level::LEVEL_INFO, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, "Test message after destroy"); + + // Cleanup + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerConcurrentDeleteAndLogTest + * @tc.desc: Test concurrent DeleteInstance and Log calls. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerConcurrentDeleteAndLogTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // Create instance + auto logger = Logger::GetInstance(); + ASSERT_NE(logger, nullptr); + + std::vector threads; + const int threadCount = LOGGER_THREAD_COUNT_LOW; + + // Launch mixed operations: some threads delete, some log + for (int i = 0; i < threadCount; ++i) { + if (i % LOGGER_DELETE_THREAD_RATIO == 0) { + // Delete threads + threads.emplace_back([]() { + Logger::DeleteInstance(); + }); + } else { + // Log threads + threads.emplace_back([]() { + Logger::Log(Logger::Level::LEVEL_INFO, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, + "Test message in concurrent scenario"); + }); + } + } + + // Wait for all threads + for (auto &thread : threads) { + thread.join(); + } + + // Cleanup + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerRecreateAfterDestroyTest + * @tc.desc: Test Logger recreation after destruction. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerRecreateAfterDestroyTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // First instance + auto logger1 = Logger::GetInstance(); + ASSERT_NE(logger1, nullptr); + + // Destroy + Logger::DeleteInstance(); + + // Recreate + auto logger2 = Logger::GetInstance(); + ASSERT_NE(logger2, nullptr); + + // Verify they are different instances + EXPECT_NE(logger1, logger2); + + // Recreate again + Logger::DeleteInstance(); + auto logger3 = Logger::GetInstance(); + ASSERT_NE(logger3, nullptr); + + // Verify logger2 and logger3 are different + EXPECT_NE(logger2, logger3); + + // Cleanup + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerGetInstanceNullptrCheckTest + * @tc.desc: Test GetInstance returns valid pointer after initialization. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerGetInstanceNullptrCheckTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // Get instance should not return nullptr + auto logger = Logger::GetInstance(); + ASSERT_NE(logger, nullptr); + + // Multiple calls should also not return nullptr + for (int i = 0; i < LOGGER_REPEAT_COUNT; ++i) { + auto checkLogger = Logger::GetInstance(); + EXPECT_NE(checkLogger, nullptr); + EXPECT_EQ(logger, checkLogger); + } + + // Cleanup + Logger::DeleteInstance(); +} + +/** + * @tc.name: LoggerLogWithNullLoggerTest + * @tc.desc: Test Log function handles null logger gracefully. + * @tc.type: FUNC + * @tc.author: zqq + */ +HWTEST_F(DistributedDBCommonTest, LoggerLogWithNullLoggerTest, TestSize.Level1) +{ + // Reset instance for clean test + Logger::DeleteInstance(); + + // Create instance first to ensure proper initialization + auto logger = Logger::GetInstance(); + ASSERT_NE(logger, nullptr); + + // Delete and simulate destroyed state + Logger::DeleteInstance(); + + // These log calls should be safe (early return on destroyed check) + Logger::Log(Logger::Level::LEVEL_DEBUG, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, "Debug message"); + Logger::Log(Logger::Level::LEVEL_INFO, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, "Info message"); + Logger::Log(Logger::Level::LEVEL_WARN, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, "Warn message"); + Logger::Log(Logger::Level::LEVEL_ERROR, "TestTag", "TestFunc", LOGGER_TEST_LINE_NUMBER, "Error message"); + + // Cleanup + Logger::DeleteInstance(); +} } \ No newline at end of file diff --git a/frameworks/libs/distributeddb/test/unittest/common/interfaces/distributeddb_cloud_interfaces_relational_ext_test.cpp b/frameworks/libs/distributeddb/test/unittest/common/interfaces/distributeddb_cloud_interfaces_relational_ext_test.cpp index a9576556278c1e48aa3b3239d1b56dc2f5949c7b..a16bd38898870c9534debc5da8b4ec8c7e308204 100644 --- a/frameworks/libs/distributeddb/test/unittest/common/interfaces/distributeddb_cloud_interfaces_relational_ext_test.cpp +++ b/frameworks/libs/distributeddb/test/unittest/common/interfaces/distributeddb_cloud_interfaces_relational_ext_test.cpp @@ -1951,7 +1951,7 @@ HWTEST_F(DistributedDBCloudInterfacesRelationalExtTest, CleanTest001, TestSize.L Clean(false); loggerInstance = nullptr; auto newLoggerInstance = Logger::GetInstance(); - ASSERT_NE(newLoggerInstance, nullptr); + ASSERT_EQ(newLoggerInstance, nullptr); } /**