İsa Yurdagül
İsa Yurdagül

Reputation: 83

Why are log lines missing without a flush?

I have a simple class in C++20 that logs data to a file line by line for Windows platform. To ensure thread safety, I use a static std::mutex to serialize the write operations.

However, I encounter an issue: if I don't explicitly call flush after each write while holding the lock, some lines are missing in the output file. When I call flush, all lines are correctly written to the file.

Here’s a simplified version of the code:

class Logger {
public:
    void Log(const std::string& message) {
        std::lock_guard lock(mtx_);
        log_file_ << std::format("{}\n", message);
        // log_file.flush(); // Uncommenting this works fine
    }

private:
    static std::mutex mtx_;
    std::ofstream log_file_{ "log.txt", std::ios::app };
};

std::mutex Logger::mtx_;

int main()
{
    std::vector<std::future<void>> futures;

    // Launch the function asynchronously
    for (int i = 0; i < 25; ++i) {
        futures.push_back(std::async(std::launch::async, []() { 
            Logger logger;
            logger.Log("Test message"); }));
    }

    // Wait for all tasks to complete
    for (auto& future : futures) {
        future.get();
    }
}

Why does this happen? Why doesn't the operating system automatically handle flushing the output when it's serialized using a mutex, without explicitly calling flush?

Is this expected behavior for file streams in C++?

Upvotes: 2

Views: 152

Answers (1)

zdf
zdf

Reputation: 4808

In your example, every thread creates a new Logger instance. Each instance has its own stream, which means each instance also has its own internal buffer. You do not synchronize these buffers.

One solution is to use a single shared stream. While broken messages (e.g., "this is a te" followed by "this is a st") could occur in your example, this won't happen here because there is only one buffer. Note that depending on your platform and compiler, it might be necessary to define _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR.

A possible implementation:

#include <fstream>
#include <mutex>
#include <vector>
#include <thread>

class logger_t
{
public:
  static logger_t& instance()
  {
    static logger_t inst; // thread safe since c++11
    return inst;
  }

  void message(const std::string m)
  {
    std::lock_guard lock(mutex_);
    os_ << m;
  }

protected:
  logger_t() = default;
  ~logger_t() = default;

  std::mutex mutex_;
  std::ofstream os_{"c:/temp/log.txt", std::ios::app};
};

void f()
{
  logger_t::instance().message( "this is a test " );
}

int main()
{
  std::vector<std::thread> threads;

  for (int i = 0; i < 123; ++i)
    threads.emplace_back(f);

  for (auto& t : threads)
    t.join();
}

Upvotes: 3

Related Questions