Subject: Multithreading segfaults

From: Jacek Swiergocki <[email-masked]>
Date: Sat, 29 Apr 2017 13:16:13 +0200

Hi all,

I have two examples of multi threading code that crash with segmentation
fault. If there is a bug in guile please fix it. If there is a problem only
in my code please help me how it can be workaround.

I am using Ubuntu 14.04 and guile compiled from the repository. The
examples are compiled by:
g++ demo.cc -Wall -std=c++11 -pthread -I/usr/local/test/include/guile/2.2
-L/usr/local/test/lib -lguile-2.2 -lgc

The first example has started to segfault since guile tagged v2.1.7. I have
not encountered problems for v2.1.6 and older versions. However for recent
version v2.2.2 it requires much more iterations to fail than v2.1.7 that
fails instantly.

////////////////////////////////////////////////////////////////////////////
// Example 1.

#include <libguile.h>

#include <thread>
#include <vector>
#include <atomic>
#include <mutex>

static volatile bool hold = true;
static std::atomic_int start_cnt(0);

static std::mutex init_once_mtx;
static bool start_inited_once = false;
static bool is_inited_once = false;

static std::mutex gc_mtx;

class Eval
{
public:
    Eval();
    ~Eval();
};

void* c_wrap_init(void*)
{
    return nullptr;
}

Eval::Eval()
{
    scm_with_guile(c_wrap_init, this);
}

Eval::~Eval()
{
    std::lock_guard<std::mutex> lck(gc_mtx);
    scm_gc();
}

void* c_wrap_init_only_once(void*)
{
    is_inited_once = true;
    return nullptr;
}

void init_only_once()
{
    std::lock_guard<std::mutex> lck(init_once_mtx);
    if (!start_inited_once)
    {
        start_inited_once = true;
        scm_with_guile(c_wrap_init_only_once, nullptr);
    }
    while (!is_inited_once); // spin;
}

void threadedInit(int thread_id)
{
    start_cnt ++;
    while (hold) {} // spin

    init_only_once();
    Eval* ev = new Eval();

    delete ev;
}

void test_init_race()
{
    int n_threads = 120;
    start_cnt = 0;
    hold = true;

    std::vector<std::thread> thread_pool;
    for (int i = 0; i < n_threads; i++)
        thread_pool.push_back(std::thread(&threadedInit, i));

    while (start_cnt != n_threads) {}  // spin
    printf("Done creating %d threads\n", n_threads);
    hold = false;

    for (std::thread& t : thread_pool) t.join();
    printf("Done joining %d threads\n", n_threads);
}

int main()
{
    for (int k = 0; k < 10000; k++)
    {
        test_init_race();
        printf("------------------ done iteration %d\n", k);
}

The second example requires much more iterations to crash with segfault.
Sometimes hundreds, sometimes thousands, it seems to be random. You need to
wait over a dozen of minutes, sometimes you need try again and restart. I
have found this problem for old versions e.g. v2.0.11 as well as for recent
version v2.2.2, so it seems to be an old problem.

////////////////////////////////////////////////////////////////////////////
// Example 2.

#include <libguile.h>

#include <thread>
#include <vector>
#include <atomic>
#include <mutex>

static volatile bool hold = true;
static std::atomic_int start_cnt(0);

static std::mutex init_once_mtx;
static bool start_inited_once = false;
static bool is_inited_once = false;

void* c_wrap_init_only_once(void*)
{
    is_inited_once = true;
    return nullptr;
}

void* c_wrap_eval(void*)
{
    return nullptr;
}

void init_only_once()
{
    std::lock_guard<std::mutex> lck(init_once_mtx);
    if (!start_inited_once)
    {
        start_inited_once = true;
        scm_with_guile(c_wrap_init_only_once, nullptr);
    }
    while (!is_inited_once); // spin;
}

void threadedInit(int thread_id)
{
    start_cnt ++;
    while (hold) {} // spin

    init_only_once();
    for (int i = 0; i < 100; ++i)
    {
        scm_with_guile(c_wrap_eval, nullptr);
    }
}

void test_init_race()
{
    int n_threads = 120;
    start_cnt = 0;
    hold = true;

    std::vector<std::thread> thread_pool;
    for (int i = 0; i < n_threads; i++)
        thread_pool.push_back(std::thread(&threadedInit, i));

    while (start_cnt != n_threads) {}  // spin
    printf("Done creating %d threads\n", n_threads);
    hold = false;

    for (std::thread& t : thread_pool) t.join();
    printf("Done joining %d threads\n", n_threads);
}

int main()
{
    for (int k = 0; k < 10000; k++)
    {
        test_init_race();
        printf("------------------ done iteration %d\n", k);
    }
}

--
Jacek