在C++中关闭一个流时发生分段故障

Segfault while closing a ofstream in C++

本文关键字:一个 故障 分段 C++      更新时间:2023-10-16

我有一个函数CloseLogFile,在记录器向其写入10000行后,它会被调用以关闭日志文件。我将要记录的行存储在类型为std::stringdeque中。这是标题

#pragma once
#include <deque>
#include <string>
#include <fstream>
#include <map>
#include <iostream>
#include <pthread.h>
#include <time.h>
#define MAX_LINES 1000
#define MESSAGES_PER_WRITE 100
class AtlLogger
{
friend class Driver;
friend class OrderManagementSystem;
public:
    static AtlLogger* Instance();
    void Log(const std::string line, const std::string prefix);
    void DeleteInstance();
    void WriteToFile();
private:
    AtlLogger();
    //the pointer versions of logging is reserved for internal use
    //we don't want a strategy to log with pointers and deal with
    //memory management
    void Log(const std::string*);
    void Log(const std::string*, std::string prefix);
    struct LogRequest
    {
        const std::string* line;
        std::string prefix;
    };
    struct FileInfo
    {
        std::string* name;
        std::ofstream ofs;
        int lines;
    };
    static AtlLogger* instance;
    void OpenLogFile(const std::string&);
    void CloseLogFile(const std::string&);
    bool run;
    std::deque<LogRequest*> message_queue;
    std::map<std::string, FileInfo*> file_map;
};

这是.cpp文件:

#include "AtlLogger.h"
AtlLogger* AtlLogger::instance = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
using std::cout;
using std::endl;
/*
* @construct
* @param
* @description  creates a logger to record system information
*/
AtlLogger::AtlLogger()
{
std::string prefix("Audit");
OpenLogFile(prefix);
run = true;
}
/*
* @return instance: pointer to singleton class
* @description  creates an instance of the singleton
*       if it does not already exist
*/
AtlLogger* AtlLogger::Instance()
{
if(instance == NULL)
{
    instance = new AtlLogger;
}
return instance;
}
/*
* @param
* @return
* @description  deletes the logger after closing all IO
*/
void AtlLogger::DeleteInstance()
{
usleep(100000);
pthread_mutex_lock(&mutex);
run = false;
std::map<std::string, FileInfo* >::iterator it;
for (it = file_map.begin(); it != file_map.end(); it++)
{
    //TODO ofstream* file = (*file_it).second;
    //file->close();
}
pthread_mutex_unlock(&mutex);
delete instance;
instance = NULL;
}
/*
* @param line:  string to be logged
* @return
* @description  adds a line to the queue of lines that 
*       will be written to the log
*/
void AtlLogger::Log(const std::string* line)
{
pthread_mutex_lock(&mutex);
    LogRequest* request = new LogRequest;
    request->line = line;
    request->prefix = "Audit";
    message_queue.push_back(request);
pthread_mutex_unlock(&mutex);
}
/*
* @param line:  string to be logged
* @param name:  name of the file to log with
* @return
* @description  add the line to the given log file
*/
void AtlLogger::Log(const std::string* line, std::string prefix)
{
pthread_mutex_lock(&mutex);
    if (file_map.find(prefix) == file_map.end())
    {
        OpenLogFile(prefix);
    }
    LogRequest* request = new LogRequest;
    request->line = line;
    request->prefix = prefix;
    message_queue.push_back(request);
pthread_mutex_unlock(&mutex);
}
/*
* @param line:  string to be logged
* @param name:  name of the file to log with
* @return
* @description  add the line to the given log file
*/
void AtlLogger::Log(const std::string line, std::string prefix)
{
pthread_mutex_lock(&mutex);
    if (file_map.find(prefix) == file_map.end())
    {
        OpenLogFile(prefix);
    }
    LogRequest* request = new LogRequest;
    request->line = new std::string(line);
    request->prefix = prefix;
    message_queue.push_back(request);
pthread_mutex_unlock(&mutex);
}
/*
* @param
* @return
* @description  runs in its own thread, checking whether it needs
*       to write log statements periodically
*/
void AtlLogger::WriteToFile()
{
    std::map<std::string, FileInfo* >::iterator it;
while(run)
{
    char timestamp[16];
    time_t now;
    time(&now);
    struct tm* current = localtime(&now);
    sprintf(timestamp, "%02u%02u%04u|%02u%02u%02u|", (current->tm_mon+1),
        current->tm_mday,(1900 + current->tm_year), current->tm_hour,
        current->tm_min, current->tm_sec);
    pthread_mutex_lock(&mutex);
        for(it=file_map.begin(); it != file_map.end(); ++it)
        {
            if(it->second->lines > MAX_LINES)
            {
                CloseLogFile(it->first);
                OpenLogFile(it->first);
            }
            else
            {
                int written = 0;
                while(!message_queue.empty() && written < MESSAGES_PER_WRITE)
                {
                    LogRequest* request = message_queue.front();
                    message_queue.pop_front();
                    std::string line(timestamp, 16);
                    line.append(*(request->line));
                    FileInfo* info = file_map[request->prefix];
                    info->ofs << line << std::endl;
                    info->lines++;
                    written++;
                    delete request;
                }
            }
        }
    pthread_mutex_unlock(&mutex);
    usleep(1000);
}
}
/*
* @param
* @return
* @description  opens a new file for logging with a timestamp
*       as the filename
*/
void AtlLogger::OpenLogFile(const std::string& prefix)
{
//get timestamp to use
char timestamp[15];
time_t now;
time(&now);
struct tm* current = localtime(&now);
sprintf(timestamp, "%02u%02u%04u_%02u%02u%02u", (current->tm_mon+1),
    current->tm_mday,(1900 + current->tm_year), current->tm_hour,
    current->tm_min, current->tm_sec);
FileInfo* info = new FileInfo;
cout << "1" << endl;
cout << prefix << endl;
info->name = new std::string("logs/" + prefix + ".log_" + timestamp);
cout << "2" << endl;
cout << "3" << endl;
cout << info->name->c_str() << endl;
info->ofs.open(info->name->c_str());
cout << "4" << endl;
info->lines = 0;
cout << "5" << endl;
file_map[prefix] = info;
cout << "Creating New Log File: " << timestamp << endl;
}
/*
* @param
* @return
* @description  closes the current log file
*/
void AtlLogger::CloseLogFile(const std::string& prefix)
{
cout << "Attempting to Close File!" << endl;
cout << prefix << endl;
cout << "Is Open?: " << file_map[prefix]->ofs.is_open() << endl;
cout << "good?: " << file_map[prefix]->ofs.good() << endl;
cout << "eof?: " << file_map[prefix]->ofs.eof() << endl;
cout << "fail?: " << file_map[prefix]->ofs.fail() << endl;
cout << "bad?: " << file_map[prefix]->ofs.bad() << endl;
cout << "name? " << *file_map[prefix]->name << endl;
cout << "lines? " << file_map[prefix]->lines << endl;
//cout << "rdbuf: " << file_map[prefix]->ofs.rdbuf() << endl;
cout << "rdbuf open?: " << file_map[prefix]->ofs.rdbuf()->is_open() << endl;
file_map[prefix]->ofs.close();
cout << "closed stream" << endl;
delete file_map[prefix];
cout << "deleted memory" << endl;
file_map.erase(prefix);
cout << "Close File End!"<< endl;
}

偶尔,我的程序会出现seg错误,我似乎无法确定原因。它工作了很多次,最终出现seg故障,有时是第一次调用,有时是多次调用。

这是我从gdb:的回溯

0  0x0000003d8786d1b3 in _IO_un_link_internal () from /lib64/libc.so.6
1  0x0000003d87860da7 in fclose@@GLIBC_2.2.5 () from /lib64/libc.so.6
2  0x000000336febb968 in std::__basic_file<char>::close() () from /usr/lib64/libstdc++.so.6
3  0x000000336fe69c17 in std::basic_filebuf<char, std::char_traits<char> >::close() () 
from /usr/lib64/libstdc++.so.6
4  0x000000336fe69cad in std::basic_ofstream<char, std::char_traits<char> >::close() () from /usr/lib64/libstdc++.so.6
5  0x00000000004c2a25 in AtlLogger::CloseLogFile() ()
6  0x00000000004c2ef1 in AtlLogger::WriteToFile() ()
7  0x0000000000482270 in Driver::launchLog (this=0x7fffffffe86f) at driver.cpp:672
8  0x000000000048228f in launchLogThread (ptr=0x7fffffffe86f) at driver.cpp:654
9  0x0000003d8840673d in start_thread () from /lib64/libpthread.so.0
10 0x0000003d878d3d1d in clone () from /lib64/libc.so.6

以下是seg故障前的控制台输出:

Attempting to Close File!
test
Is Open?: 1
good?: 1
eof?: 0
fail?: 0
bad?: 0
name? logs/test.log_09132012_095549
lines? 1001
rdbuf open?: 1

有人能告诉我哪里可能出了问题吗?(顺便说一句,为什么在gdb中,我看到的是跟踪的某些部分的行号,而不是其他部分的行号?)

日志记录功能可与以下内容一起使用:Logger::Instance()->Log("Log This", "File");

这是一个奇特而不同寻常的体系结构。

(显然)有一个全局变量files[],其中包含一个预先打开的文件。

CloseLogFile()使用files["default"]然后关闭它。如果CloseLogFile()再次关闭会发生什么?该函数不会检查是否已关闭。如果其他逻辑访问files["default"],它会在假设它是打开的之前检查它是否是打开的吗?

让日志文件成为一个自包含的对象会更"正常"(正如大多数程序员所期望的那样构建);也就是说,不依赖于非成员功能来关闭它—或者使数组的内容不是指针,而是指向的对象,这样删除数组就会导致自动清理。

从堆栈跟踪看起来就像是在登录线程。当您在记录器线程中输入CloseLogFile()时,您看到的条件可能是在主线程中删除或无效的流*的竞争条件。

您可能需要同步对CloseLogFile()的(simultenous?)调用以及主线程中的任何清理逻辑。

如果你发布你的main()代码,我可能会给你确切的修复/建议