【问题标题】:glibc memory corruption with libmysqlcppconn prepared statement使用 libmysqlcppconn 准备好的语句导致 glibc 内存损坏
【发布时间】:2012-03-28 09:54:49
【问题描述】:

我目前正在 Linux 上用 C++ 编写一个网络爬虫/蜘蛛,我在更新数据库时遇到了一些问题。我是相当 C/C++ 新手,仅供参考。

数据库更新由单独的线程(使用 pthreads)执行,但如果在 main() 中执行也会存在同样的问题,所以我可能天真地丢弃了线程的东西作为任何事情的原因。

我正在为数据库 API 使用 libmysqlcppconn

我正在使用带有 -O2 -Wall -pedantic 的 gcc 版本 4.4.3 (Ubuntu 4.4.3-4ubuntu5.1) 进行编译,并且可以干净地编译。

尽管如此,当调用下面的函数 commitChangesToDatabase() 时,它基本上从 std::map (url_queue) 中挑选出项目,将它们放入 std::vector (更新) 并从原始 std 中删除所述项目: :map,然后继续迭代 std::vector,为向量中的每个项目执行 MySQL 准备语句。这就是它失败的地方。

它是随机的:

  • 没有任何错误输出的崩溃(没有段错误,没有堆栈跟踪,没有任何内容)
  • 检测到 glibc 内存损坏导致崩溃(请参阅此处的输出:http://pastie.org/private/wlkuorivq5tptlcr7ojg
  • 报告 MySQL 服务器已消失(捕获异常),但仍在尝试(未崩溃)

我尝试将准备好的语句切换为简单的 executeUpdate(),但无济于事。 我已经尝试消除挑选项目的步骤,而只是在找到要更新的项目时执行更新,在 url_queue 的第一个循环中。

此应用程序中的其他函数也使用准备好的语句(另一个 UPDATE)并且工作正常。这些函数也由单独的线程运行。

我会通过 valgrind 运行应用程序,但坦率地说,我不了解大部分输出,所以它对我没有多大帮助 - 但如果有人想要它的输出,请告诉我运行它的选项有,我会提供的。

我不知道如何从这里开始。任何人都知道出了什么问题?

struct queue_item_t {
    int id;
    int sites_id;
    int priority;
    int depth;
    int handler;
    int state;  // 0 = Pending, 1 = Working, 2 = Completed, 3 = Checked
    double time_allowed_crawl;

    bool status;
    bool was_redirected;

    double time;
    double time_end;
    double time_curl;
    double size;

    std::string hash;
    std::string url;
    std::string file;
    std::string host;
};

void commitChangesToDatabase()
{
    map< string, queue_item_t >::iterator it, end;
    sql::PreparedStatement *pstmt;
    int i = 0;

    if (!url_queue.size()) {
        return;
    }

    pthread_mutex_lock(&dbCommitMutex);
    pthread_mutex_lock(&itemMutex);

    cout << "commitChangesToDatabase()" << endl;
    pstmt = dbPrepareStatement("UPDATE crawler_queue SET process_hash = NULL, date_crawled = NOW(), url = ?, hash = ? WHERE id = ?");

    for (it = url_queue.begin(); it != url_queue.end();)
    {
        if (it->second.state == 2)
        {
            pstmt->setString(1, it->second.url);
            pstmt->setString(2, it->second.hash);
            pstmt->setInt(3, it->second.id);

            try {
                pstmt->executeUpdate();
                ++i;

            } catch (sql::SQLException &e) {
                cerr << "# ERR: SQLException in " << __FILE__;
                cerr << "(" << __FUNCTION__ << ") on line " << __LINE__ << endl;
                cerr << "# ERR: " << e.what();
                cerr << " (MySQL error code: " << e.getErrorCode();
                cerr << ", SQLState: " << e.getSQLState() << " )" << endl;
            }

            url_queue.erase(it++);
        }
        else {
            ++it;
        }
    }

    delete pstmt;

    cout << "~commitChangesToDatabase()" << endl;

    pthread_mutex_unlock(&itemMutex);
    pthread_mutex_unlock(&dbCommitMutex);
}

// this function is defined in another file but is written here just to show the contents of it
sql::PreparedStatement *dbPrepareStatement(const std::string &query)
{
    return con->prepareStatement(query);
}

编辑:

有些人似乎认为问题出在 url_queue 集合上的迭代上,但是我已经排除了这一点,但注释掉了在数据库上运行的所有内容,而不是迭代。此外,这里的迭代是原始版本的简化(但工作)版本,它从地图中挑选项目,抛出一个向量并从地图中删除,如下所示,并且该部分程序运行良好 - 它 only 在使用数据库时崩溃。

for (it = url_queue.begin(); it != url_queue.end();)
{
    if (it->second.state == 2)
    {
        update_item.type = (!it->second.was_redirected ? 1 : 2);
        update_item.item = it->second;

        updates.push_back(update_item);

        url_queue.erase(it++);
    }
    else {
        ++it;
    }
}

编辑 2:

valgrind --leak-check=yes 的输出:http://pastie.org/private/2ypk0bmawwsqva3ikfazw

【问题讨论】:

  • 您可以在您的问题中添加queue_item_t 的声明吗?
  • 另外,您不应该从您正在迭代的集合中删除项目。它可能会使迭代器无效。另外,您总是将迭代器增加两次,这是有意跳过每个第二项吗?
  • 我在 StackOverflow 上找到了从您正在迭代的集合中擦除的方法,该方法建议在每次迭代时评估集合的 .begin() 和 .end()。抱歉,双倍增加是一个不影响问题的错误 - for(;;) 中的 ++it 不应该存在。

标签: c++ mysql linux memory


【解决方案1】:

看来,迭代器不必要地增加了;首先在循环体中,也在 for 语句中。在此代码中,可以增加 end 迭代器,这是一个有问题的操作,可能是问题的根源。

下面的循环结构更适合这种情况:
it = url_queue.begin();
while( it != url_queue.end() ){ //loop body }

【讨论】:

  • 我刚刚编辑了我的问题以表明这不是问题,因为我已经排除了程序的这一部分是罪魁祸首。
  • 好的。我还有两个笔记,不确定它们是否适用。第一的;检查conn-&gt;prepareStatement() 的输出是个好习惯,这可能会失败,但您仍然可以使用它。第二:url_queue.size() 应该在互斥保护块内。
【解决方案2】:

我不认为弄乱迭代器是个好主意。替换:

else {
        ++it;
    }

作者:

else continue;

或者只是删除它。

【讨论】:

  • 正如我在我的问题的第一次编辑中所写,迭代器逻辑有效 - valgrind 没有显示任何问题,只有来自数据库 API 的错误 - 但它适用于应用程序的其他区域这就是为什么我对为什么它在这个函数中不起作用一无所知。据我了解,递增迭代器是将其推进到下一个元素的唯一方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-04
  • 1970-01-01
  • 2017-02-02
  • 2010-10-05
相关资源
最近更新 更多