| Summary: | C++ bug (please see the body) | ||
|---|---|---|---|
| Product: | Base System | Reporter: | osa <osa> |
| Component: | gnu | Assignee: | David E. O'Brien <obrien> |
| Status: | Closed FIXED | ||
| Severity: | Affects Only Me | ||
| Priority: | Normal | ||
| Version: | Unspecified | ||
| Hardware: | Any | ||
| OS: | Any | ||
Responsible Changed From-To: gnats-admin->obrien Over to c++ maintainer. I've analyzed this PR and am the opinion that it is not a threads library
bug. It seems to have something to do with either the compiler (libgcc2)
or the implementation of string in libstdc++. In the sample program included
with the original PR submittal, there are the following functions:
void
SS::run()
{
string s; // !!!
string s1; // !!!
sleep(1);
}
static void sp_call(SS *ss)
{
string s; // !!!
ss->spawn();
}
SS::run() is called from a thread that is created by the "ss->spawn()" call in
sp_call(). sp_call initializes/allocates the local string s, then creates a
thread that calls SS::run(). SS::run() is called which also initializes/allocates
2 strings (s and s1). The thread then sleeps and a thread switch back to the
main thread occurs. ss->spawn returns and the local string in sp_call() is
then finalized. When the spawned thread returns from the sleep it also tries
to finalize the two strings (s and s1). But it seems that the finalization
of the string in sp_call() has corrupted one of the strings in SS::run().
enclosed is another test program for this problem. execute it a bunch of
times. you'll see three or four different things depending on what causes
death first:
these two mean an exception warped from one thread to another:
$ ./testex
B catches Z
Abort trap (core dumped)
or:
$ ./testex
A catches Y
Abort trap (core dumped)
this means that unexpected() was called by the runtime:
$ ./testex
unexpected exception thrown
Abort trap (core dumped)
and this means that the bit of unwinding code blew it:
$ ./testex
Segmentation fault (core dumped)
it dies in:
leal 4(%edi),%edx
movl (%edx),%eax
addl $4,%eax
movl (%eax),%edx
movl (%edx),%ecx
movl %ecx,(%eax)
because edx is zero.
/*
* testex.cpp
*
* this dumb program spins off a few threads that just throw and catch some
* exceptions. it's supposed to run silently forever. it'll print some
stuff
* and exit if one of two things happens:
*
* 1) "unexpected()" is invoked by the runtime
* 2) the wrong exception is caught in a thread
*/
#include <stdio.h> // 11 15 37
#include <stdlib.h>
#include <pthread.h>
#include <new>
#include <exception>
// this is the exception thrown by instances of threadA
class exZ : public std::exception {
public:
inline exZ(void) : std::exception() { }
virtual ~exZ(void);
}; // class exZ
exZ::~exZ(
void
)
{
}
// this is the exception thrown by instances of threadB
class exY : public std::exception {
public:
inline exY(void) : std::exception() { }
virtual ~exY(void);
}; // class exY
exY::~exY(
void
)
{
}
// this is a synchronization device that mixes up the scheduling of threadA
// instances
class Flipper {
public:
Flipper(void);
~Flipper(void);
void Acquire(void);
void Release(void);
private:
pthread_t _owner;
pthread_mutex_t _m;
pthread_t _wanter;
pthread_cond_t *_wc;
}; // class Flipper
Flipper::Flipper(
void
) : _owner(static_cast<pthread_t>(0)),
_wanter(static_cast<pthread_t>(0)),
_wc(NULL)
{
pthread_mutex_init(&_m, NULL);
}
Flipper::~Flipper(
void
)
{
pthread_mutex_destroy(&_m);
}
void Flipper::Acquire(
void
)
{
pthread_t me = pthread_self();
if (_owner == me)
abort();
pthread_mutex_lock(&_m);
if (_owner == 0) {
_owner = me;
} else {
pthread_cond_t myc;
pthread_cond_init(&myc, NULL);
_wanter = me;
_wc = &myc;
while (_owner != me)
pthread_cond_wait(&myc, &_m);
pthread_cond_destroy(&myc);
}
pthread_mutex_unlock(&_m);
}
void Flipper::Release(
void
)
{
pthread_mutex_lock(&_m);
if (_wanter == 0) {
_owner = static_cast<pthread_t>(0);
} else {
pthread_cond_t *c = _wc;
_owner = _wanter;
_wanter = 0;
_wc = NULL;
pthread_cond_signal(c);
}
pthread_mutex_unlock(&_m);
}
// a stack object to automate flippage
class AutoFlip {
public:
inline AutoFlip(Flipper *flip) : _flip(flip) {
_flip->Acquire();
}
inline ~AutoFlip(void) {
_flip->Release();
}
private:
Flipper *_flip;
}; // class AutoFlip
// threadA's main processing loop. pretty dumb. it just trades off with
// another instance of threadA.
static void *threadAMain(
void *arg
) throw()
{
Flipper *flip = reinterpret_cast<Flipper *>(arg);
do {
try {
AutoFlip f(flip);
//printf("A throws Z\n");
throw exZ();
} catch (const exZ &z) {
//printf("A catches Z\n");
} catch (const exY &y) {
printf("A catches Y\n");
abort();
}
} while(true);
return NULL;
}
// threadB's main processing loop. even dumber. it just throws exceptions
// at itself.
static void *threadBMain(
void *arg
) throw()
{
do {
try {
//printf("B throws Y\n");
throw exY();
} catch (const exY &y) {
//printf("B catches Y\n");
} catch (const exZ &z) {
printf("B catches Z\n");
abort();
}
} while(true);
return NULL;
}
// handle unexpected exceptions (there shouldn't be any)
static void unexpectedHandler(
void
)
{
printf("unexpected exception thrown\n");
terminate();
}
// program entry point. the join() will never complete, so there's no point
// in cleaning up the flipper.
int main(
int argc,
char *argv[]
) throw()
{
Flipper *f;
pthread_t thread;
set_unexpected(unexpectedHandler);
f = new Flipper();
pthread_create(&thread, NULL, threadAMain, f);
pthread_create(&thread, NULL, threadAMain, f);
pthread_create(&thread, NULL, threadBMain, NULL);
pthread_join(thread, NULL);
return 0;
}
_________________________________________________________________
Get your FREE download of MSN Explorer at http://explorer.msn.com
The following hack stops test case from coredumping. a) cd /usr/src/gnu/lib/libgcc_r b) change CFLAGS+=-D_PTHREADS ÎÁ CFLAGS+=-D_PTHREADS -DGTHREAD_USE_WEAK=1 c) make all install d) cp /usr/lib/libgcc/libgcc_r_pic.a /usr/lib/libgcc/libgcc_pic.a e) cd /usr/src/gnu/lib/libstdc++; make clean all install David, why can't we build libgcc_pic.a with -D_PTHREADS -DGTHREAD_USE_WEAK=1 always and get rid of libgcc_r_pic.a altogether? I just finished buildworld after doing just that and everything seems to work so far. I will continue my testing and will let you know if something breaks. On Thu, Dec 21, 2000 at 05:50:34PM -0500, Alexander N. Kabaev wrote:
> David, why can't we build libgcc_pic.a with -D_PTHREADS -DGTHREAD_USE_WEAK=1
> always and get rid of libgcc_r_pic.a altogether? I just finished buildworld
I am working on something simular.
State Changed From-To: open->closed fix committed to -current and will be MFC'ed on the fast track |
C++ bug. Please see the source code. How-To-Repeat: Here is a special code. /*--------------------------------------------------------*/ #include <stdio.h> #include <assert.h> #include <string> #include <pthread.h> #include <unistd.h> #include <errno.h> #define Debug(x) printf x extern "C" { typedef void *(*_THR_C_FUNC)(void *args); } typedef void *(*_THR_FUNC)(void *args); /*-----------------------------------------------------------------*/ class Mutex { public: Mutex() { assert(::pthread_mutex_init(&this->lock_, 0) == 0); } ~Mutex (void) { assert(::pthread_mutex_destroy(&this->lock_)==0); } int acquire (void) { return ::pthread_mutex_lock(&this->lock_); } int release (void) { return ::pthread_mutex_unlock (&this->lock_); } pthread_mutex_t lock_; }; /*-----------------------------------------------------------------*/ class Condition { public: Condition (Mutex &m); ~Condition (void); int wait (void); void signal (void); protected: pthread_cond_t cond_; Mutex &mutex_; }; Condition::Condition (Mutex &m) : mutex_ (m) { assert (pthread_cond_init(&this->cond_, 0) == 0); } Condition::~Condition (void) { while(::pthread_cond_destroy(&this->cond_) == -1 && errno == EBUSY) { assert(::pthread_cond_broadcast(&this->cond_) == 0); #ifdef __linux__ ::sched_yield (); #else ::pthread_yield(); #endif } } int Condition::wait (void) { return ::pthread_cond_wait(&this->cond_, &this->mutex_.lock_); } void Condition::signal (void) { assert(::pthread_cond_signal(&this->cond_) == 0); } /*-----------------------------------------------------------------*/ class Guard { public: Guard (Mutex &l); ~Guard (void); private: Mutex *lock_; }; Guard::Guard (Mutex &l) : lock_ (&l) { this->lock_->acquire (); } Guard::~Guard (void) { this->lock_->release (); } /*-----------------------------------------------------------------*/ class _Base_Thread_Adapter { public: _Base_Thread_Adapter (_THR_FUNC user_func, void *arg); void *invoke (void); _THR_C_FUNC entry_point (void) { return entry_point_; } private: _THR_FUNC user_func_; void *arg_; _THR_C_FUNC entry_point_; }; extern "C" void * _thread_adapter (void *args) { _Base_Thread_Adapter *thread_args = (_Base_Thread_Adapter*)args; void *status = thread_args->invoke (); return status; } _Base_Thread_Adapter::_Base_Thread_Adapter (_THR_FUNC user_func, void *arg) : user_func_ (user_func), arg_ (arg), entry_point_ (_thread_adapter) { } void * _Base_Thread_Adapter::invoke (void) { void *(*func)(void *) = this->user_func_; void *arg = this->arg_; delete this; return func(arg); } /*-----------------------------------------------------------------*/ class SS { public: void spawn(); static void run(); static void *WThread( void *data ); }; /*---------------------------------------------------------------------*/ static Mutex CMutex; static Condition Cond(CMutex); static Mutex m1; /*---------------------------------------------------------------------*/ #define REL(m,n) assert(m.release() != -1) #define ACQ(m,n) assert(m.acquire() != -1) /*---------------------------------------------------------------------*/ void * SS::WThread( void *data ) { Cond.signal(); Debug(("run thread...\n")); SS::run(); Debug(("thread ended\n")); return NULL; } /*---------------------------------------------------------------------*/ int thr_create (_THR_FUNC func, void *args) { _Base_Thread_Adapter *thread_args; thread_args = new _Base_Thread_Adapter(func, args); pthread_attr_t attr; if (::pthread_attr_init (&attr) != 0) return -1; ::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_t thr_id; assert( ::pthread_create (&thr_id, &attr, thread_args->entry_point(), thread_args) == 0 ); ::pthread_attr_destroy (&attr); } /*---------------------------------------------------------------------*/ void SS::spawn() { #ifdef BAD int rc; Guard guard(m1); // !!! #else Guard guard(m1); // !!! int rc; #endif pthread_attr_t attr; if (::pthread_attr_init (&attr) != 0) return; ::pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); thr_create(SS::WThread, (void *)0); ::pthread_attr_destroy (&attr); ACQ(CMutex, "CMutex"); rc = Cond.wait(); if( rc == -1 ) Debug(("Cond wait failed: %s\n", strerror(errno))); REL(CMutex, "CMutex"); } /*---------------------------------------------------------------------*/ void SS::run() { string s; // !!! string s1; // !!! sleep(1); } /*=====================================================================*/ static void sp_call(SS *ss) { string s; // !!! ss->spawn(); } /*------------------------------------------------------------------*/ int main(int argc, char **argv) { SS ss; sp_call(&ss); sleep(2); Debug(("Exitting...\n")); sleep(3); return 0; } /*--------------------------------------------------------*/ And here is a makefile for gmake: Goal: bad good bad: tt.cpp $(CXX) -DBAD tt.cpp -pthread -g -o bad $(CXX) -S -DBAD tt.cpp -pthread -g -o bad.s good: tt.cpp $(CXX) tt.cpp -pthread -g -o good $(CXX) -S tt.cpp -pthread -g -o good.s Put the source code in the one directory and say: $ gmake After build a programms bad and good, try to run: % ./good run thread... thread ended Exitting... % ./bad run thread... Segmentation fault (core dumped)