[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-mac
Subject:    Re: [KDE/Mac] [CODE] Multithreaded SIGSEGV signal handling,
From:       Jonas_Bähr <jonas.baehr () web ! de>
Date:       2011-09-25 12:34:49
Message-ID: 8AC44E9E-36AA-45B3-B148-8321F20D95CD () web ! de
[Download RAW message or body]

Hi,

Am 25.09.2011 um 01:37 schrieb Michael Pyne:

>  [...]
> With that said I don't think the code uses as many POSIX options as  
> the rest
> of KSharedDataCache (e.g. process-shared mutexes as unimplemented on  
> Mac OS X)
> but I wanted to give a chance for you guys to test the prototype code
> beforehand and let me know if there's problems.
>
> You'll want to compile with something like this:
> $CXX -O2 -o sigcatcher -lrt -pthread sigcatcher.cpp

Here is the first issue: librt doesn't exist here (Mac OS X 10.5  
"Leopard").
However, the code compiles without the "-lrt".
$ g++ -O2 -o sigcatcher -pthread sigcatcher.cpp
$ g++ --version
i686-apple-darwin9-g++-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5493)

> Make sure optimization is enabled to ensure volatile is used where  
> it's
> needed. Assuming everything works right you should get output like:
>
> $ ./sigcatcher
> Hello, World!
> Got a result of 1
> Exited thread
> $

The next problem is, that sem_init(..) isn't implemented here.
$ ./sigcatcher
Error: sem_init: Function not implemented
$

It seems that only named semaphores are implemented.
I've changed this part of your code to use sem_open(..) instead [see  
attachment] and then I get the expected result:

["sigcatcher-sem_open.cpp" (sigcatcher-sem_open.cpp)]

/*
 * Copyright  © 2011 Michael Pyne <mpyne@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License version 2 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <iostream>

#include <errno.h>
#include <string.h>
#include <semaphore.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>

class KSDCOperation
{
public:
    virtual ~KSDCOperation();
    virtual void operator()() = 0;
};

KSDCOperation::~KSDCOperation()
{
}

struct SigCatcherSharedData
{
    sem_t* workSemaphore;
    volatile int keepLooping;
    volatile bool resultValid;
    volatile sig_atomic_t resultLock;

    KSDCOperation *operation;
};

struct SigCatcherSignalHandlerData
{
    sigjmp_buf env;
};

pthread_key_t gSigThreadKey;

static void memoryFaultHandler(int signal)
{
    std::cout << "Hello from sig handler." << std::endl;

    // Figure out what KSDC object we need to jump back to.
    SigCatcherSignalHandlerData *localData = reinterpret_cast<SigCatcherSignalHandlerData *>(
        pthread_getspecific(gSigThreadKey)
    );

    // Nothing to do now but jump to our pre-arranged error handler.
    siglongjmp(localData->env, signal);
}

void* signalCatcherThread(void *in)
{
    SigCatcherSharedData *shared = reinterpret_cast<SigCatcherSharedData *>(in);

    // Store the information needed for our thread's signal handler now.
    pthread_key_create(&gSigThreadKey, free);

    SigCatcherSignalHandlerData *localData = new SigCatcherSignalHandlerData;
    pthread_setspecific(gSigThreadKey, localData);

    // Set the signals we'll handle
    sigset_t handledSignals;

    // Block all signals
    sigfillset(&handledSignals);

    // Except these...
    sigdelset(&handledSignals, SIGSEGV);
    sigdelset(&handledSignals, SIGBUS);
    sigdelset(&handledSignals, SIGFPE);
    sigdelset(&handledSignals, SIGILL);

    int err;
    if ((err = pthread_sigmask(SIG_SETMASK, &handledSignals, 0)) != 0) {
        std::cerr << "error: pthread_sigmask: " << strerror(err) << std::endl;
    }

    // Now establish handlers for signals that are not blocked
    struct sigaction sigHandler;

    sigHandler.sa_handler = memoryFaultHandler;
    sigemptyset(&sigHandler.sa_mask);
    sigHandler.sa_flags = 0;

    if ((err = sigaction(SIGSEGV, &sigHandler, 0)) < 0) {
        std::cerr << "Error: sigaction: " << strerror(err) << std::endl;
    }
    if ((err = sigaction(SIGBUS, &sigHandler, 0)) < 0) {
        std::cerr << "Error: sigaction: " << strerror(err) << std::endl;
    }
    if ((err = sigaction(SIGFPE, &sigHandler, 0)) < 0) {
        std::cerr << "Error: sigaction: " << strerror(err) << std::endl;
    }
    if ((err = sigaction(SIGILL, &sigHandler, 0)) < 0) {
        std::cerr << "Error: sigaction: " << strerror(err) << std::endl;
    }

    int jmpResult = sigsetjmp(localData->env, 1);
    if (jmpResult) {
        // siglongjmp called
        std::cerr << "Caught signal: " << jmpResult << std::endl;

        // Mark whatever operation was going on as failed.
        shared->resultValid = false;

        // Ensure we unlock our shared data if we had it locked before.
        shared->resultLock = 0;
        pthread_exit(0);
    }
    else {
        // jmpbuf set, continue as normal
        shared->resultValid = true;

        while (shared->keepLooping) {
            while (sem_wait(shared->workSemaphore) != 0) {
                if (errno != EINTR) {
                    err = errno;
                    std::cerr << "Error: sem_wait: " << strerror(err) << std::endl;
                    shared->resultValid = false;
                    shared->resultLock = 0;
                    pthread_exit(0);
                }
            }

            // This should only happen if the parent thread has told us to
            // exit.
            if (!shared->keepLooping) {
                pthread_exit(0);
            }

            // Actually do the freakin' work
            (*shared->operation)();

            // Uncomment one of these to simulate a crash.
            //*(reinterpret_cast<int*>(0)) = 5;
            //volatile int dummy = 1/0;

            // Free up out parent thread currently spinning.
            shared->resultLock = 0;
        }
    }

    return 0;
}

class HelloOperation : public KSDCOperation
{
public:
    HelloOperation() : KSDCOperation(), m_done(false)
    {
    }

    virtual void operator()()
    {
        std::cout << "Hello, World!" << std::endl;
        m_done = true; // Result
    }

    // Just to show how results would have to be stored in the functor object
    // itself.
    bool m_done;
};

int main()
{
    SigCatcherSharedData sharedData;
    sharedData.keepLooping = true;
    sharedData.resultValid = true;
    sharedData.operation = 0;

    int err;
    sem_t* sem = sem_open("sharedData.workSemaphore", O_CREAT,  0777, 0);
    if (sem == SEM_FAILED) {
        err = errno;
        std::cerr << "Error: sem_init: " << strerror(err) << std::endl;
        exit(1);
    }
    else {
        sharedData.workSemaphore = sem;
    }

    pthread_t exeThread;
    if ((err = pthread_create(&exeThread, 0, signalCatcherThread, &sharedData)) < 0) {
        std::cerr << "Error: pthread_create: " << strerror(err) << std::endl;
        exit(1);
    }

    // For each task to complete, we need to have a KSDCOperation object, and
    // get our spinlock ready by calling ref. Then we post to the workSemaphore
    // to free our work thread and spin on resultLock. We must ALWAYS check the
    // validity of the result however. If invalid the thread has terminated,
    // though we can re-create and try again.
    sharedData.resultLock = 1;
    HelloOperation *op = new HelloOperation;
    sharedData.operation = op;

    sem_post(sharedData.workSemaphore);
    while(sharedData.resultLock)
        ;

    if(sharedData.resultValid) {
        std::cout << "Got a result of " << op->m_done << std::endl;
    }
    else {
        std::cerr << "WHOA, something happened! :(\n";
        pthread_join(exeThread, 0);
        exit(1);
    }

    // Normal termination. No need to use resultLock spinlock here.

    sharedData.keepLooping = false;
    sem_post(sharedData.workSemaphore);
    pthread_join(exeThread, 0);
    delete sharedData.operation;
    if (sem_close(sharedData.workSemaphore) < 0) {
        err = errno;
        std::cerr << "Error: sem_close: " << strerror(err) << std::endl;
        exit(1);
    }

    std::cout << "Exited thread\n";

    return 0;
}


$ g++ -O2 -o sigcatcher-sem_open -pthread sigcatcher-sem_open.cpp
$ ./sigcatcher-sem_open
Hello, World!
Got a result of 1
Exited thread
$

bye,
Jonas

>
> If you want to see the signal handler in action there's a couple of  
> lines you
> can uncomment (just grep for uncomment to find where).
>
> Obviously any other feedback on possible issues is appreciated as  
> well, but
> basically I'd like to catch any portability issues early this time  
> instead of
> after-the-fact.
>
> Please CC me on any replies as I'm not subscribed.
>
> Regards,
> - Michael  
> Pyne<sigcatcher.cpp>_______________________________________________
> kde-mac@kde.org
> List Information: https://mail.kde.org/mailman/listinfo/kde-mac
> KDE/Mac Information: http://techbase.kde.org/index.php?title=Projects/KDE_on_Mac_OS_X



_______________________________________________
kde-mac@kde.org
List Information: https://mail.kde.org/mailman/listinfo/kde-mac
KDE/Mac Information: http://techbase.kde.org/index.php?title=Projects/KDE_on_Mac_OS_X

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic