mardi 17 janvier 2023

Static member function and thread safety

I've a private static method in a class that has all the methods as static. It's a helper class that helps with logging and other stuff. This helper class would be called by multiple threads. I don't understand how the method in question is working safely with multiple threads without a lock.

//Helper.cpp


std::recursive_mutex Logger::logMutex_;


void Logger::write(const std::string &logFilePath, const std::string &formattedLog)
{
    std::lock_guard<std::mutex> guard(logMutex_);
    std::ofstream logFile(logFilePath.c_str(), std::ios::out | std::ios::app);

    if (logFile.is_open())
    {
        logFile << formattedLog;
        logFile.close();
    }
}

void Logger::error(const string &appId, const std::string &fmt, ...)
{
    auto logFile = validateLogFile(appId); //Also a private static method that validates if a file exists for this app ID.
    if (!logFile.empty())
    {
        //Format the log

        write(logFile, log);
    }
}
//Helper.h


class Logger
{
    public:
        static void error(const std::string &Id, const std::string &fmt, ...);
    private:
        static void write(const std::string &fileName, const std::string &formattedLog);
        static std::recursive_mutex logMutex_;
};

I understand that the local variables inside the static methods are purely local. i.e., A stack is created every time these methods are called and the variables are initialized in them. Now, in the Logger::write method I'm opening a file and writing to it. So when multiple threads calls the write method through the Logger::error method(Which is again static) and when there's no lock, I believe I should see some data race/crash.
Because multiple threads are trying to open the same file. Even if the kernel allows to open a file multiple times, I must see some fault in the data written to the file.

I tested this with running up to 100 threads and I see no crash, all the data is being written to the file concurrently. I can't completely understand how this is working. With or without the lock, I see the data is being written to the file perfectly.

TEST_F(GivenALogger, WhenLoggerMethodsAreCalledFromMultipleThreads_AllTheLogsMustBeLogged)
{
    std::vector<std::thread> threads;
    int num_threads = 100;

    int i = 0;

    for (; i < num_threads / 2; i++)
    {
        threads.push_back(std::thread(&Logger::error, validId, "Trial %d", i));
    }

    for (; i < num_threads; i++)
    {
        threads.push_back(std::thread(&Logger::debug, validId, "Trial %d", i));
    }

    std::for_each(threads.begin(), threads.end(), [](std::thread &t) { t.join(); });

    auto actualLog = getActualLog(); // Returns a vector of log lines.
    EXPECT_EQ(num_threads, actualLog.size());
}

Also, how should I properly/safely access the file?

Aucun commentaire:

Enregistrer un commentaire