Problem with on-line documentation for libstdc++

Kick Damien-DKICK1 DKICK1@motorola.com
Fri Oct 5 04:32:00 GMT 2001


Phil Edwards [ mailto:pedwards@disaster.jaj.com ] wrote:
> On Thu, Oct 04, 2001 at 12:35:41PM -0500, Kick Damien-DKICK1 wrote:
> >     [...] discussing threading and the IO library. Much of it is
> >     dealing with the C library, but C++ is included as well.
> > 
> > Unfortunately, each of those hyper-links is broken.  Would anyone
> > be able to help me find these e-mail threads, please?

> I really should replace those paragraphs entirely.  More recent,
> more informative, and more interesting messages are available, such
> as
>
>     http://gcc.gnu.org/ml/libstdc++/2001-10/msg00024.html

The bit from the library intro that I quoted in my previous e-mail
that really got my attention was the bit about "threading and the IO
library".  Basically, I'm wondering about the MT safety of the IO
implementation and this thread doesn't seem to mention the IO streams.

I'm currently debugging a multi-threaded applicaiton that uses
'ofstream'; unfortunately, this application is using 'libstdc++-2.8.1'
but there is nothing that I can do about it at the moment.  I know
that there will be significant differences between this version and
the current stable release and that anything I'm seeing in
'libstdc++-2.8.1' probably will not apply to the current stable
release.  But... looking at this older version has lead me to wonder
about the implementation of the current version.  So... I have been
finding a number of core files from this application as follows:

    #0  0x101868 in _IO_un_link (fp=0xedc0f3e4) at genops.c:43
    43      genops.c: No such file or directory.
    (gdb) bt
    #0  0x101868 in _IO_un_link (fp=0xedc0f3e4) at genops.c:43
    #1  0x1027c8 in _IO_file_close_it (fp=0xedc0f3e4) at fileops.c:139
    #2  0x105e4c in filebuf::close (this=0xedc0f3e4) at filebuf.cc:175
    #3  0xf9b94 in fstreambase::close (this=0xedc0f3e0) at fstream.cc:90
    [...]
    Current language:  auto; currently c
    (gdb) print f
    $1 = (_IO_FILE **) 0xeef05748
    (gdb) print *f
    $2 = (_IO_FILE *) 0x1
    (gdb) print **f
    Cannot access memory at address 0x1
    (gdb) print _IO_list_all
    $3 = (_IO_FILE *) 0x3705fc
    (gdb) 

My suspision is that someone is sharing an 'ofstream' instance between
multiple threads without protecting the acess to it and so there is
some race condition that is ultimately messing with the '_IO_list_all'
into which 'f' is meant to be pointing.  Consider the following
example code:

    #include <cassert>
    #include <fstream>
    #include <sstream>
    #include <pthread.h>
    #include <unistd.h>
    
    extern "C" {
        void* start_thread_one(void*);
        void* start_thread_two(void*);
    }
    
    #ifndef UNSAFE
    namespace {
        pthread_mutex_t test_lock;
    }
    #endif
    
    int main()
    {
        std::ofstream test("test_0.dat", std::ios::trunc);
        if (!test.is_open()) {
            std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
            exit(1);
        }
        if (!test) {
            std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
            exit(1);
        }
        if (!test) exit(1);
    #ifndef UNSAFE
        assert(!pthread_mutex_init(&test_lock, 0));
    #endif
        pthread_attr_t attr;
        assert(!pthread_attr_init(&attr));
        assert(!pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM));
        pthread_t one;
        assert(!pthread_create(&one, &attr, &start_thread_one, &test));
        pthread_t two;
        assert(!pthread_create(&two, &attr, &start_thread_two, &test));
        assert(pthread_join(one, 0));
        assert(pthread_join(two, 0));
    }
    
    void*
    start_thread_one(void* arg)
    {
        std::ofstream& test = *static_cast<std::ofstream*>(arg);
        for (int i = 0; ; ++i) {
    #ifndef UNSAFE
            assert(!pthread_mutex_lock(&test_lock));
    #endif
            test << i << '\n';
    #ifndef UNSAFE
            assert(!pthread_mutex_unlock(&test_lock));
    #endif
        }
        return 0;
    }
    
    void*
    start_thread_two(void* arg)
    {
        std::ofstream& test = *static_cast<std::ofstream*>(arg);
        for (int i = 1; ; ++i) {
            sleep(5);
    #ifndef UNSAFE
            assert(!pthread_mutex_lock(&test_lock));
    #endif
            test.flush();
            if (!test) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
            test.close();
            if (test.is_open()) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
    #ifndef UNSAFE
            assert(!pthread_mutex_unlock(&test_lock));
    #endif
            sleep(1);
            std::ostringstream name;
            name << "test_" << i % 2 << ".dat";
            std::cerr << name.str() << '\n';
    #ifndef UNSAFE
            assert(!pthread_mutex_lock(&test_lock));
    #endif
            test.clear();
            test.open(name.str().c_str(), std::ios::trunc);
            if (!test.is_open()) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
            if (!test) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
    #ifndef UNSAFE
            assert(!pthread_mutex_unlock(&test_lock));
    #endif
        }
        return 0;
    }

The behavior when 'UNSAFE' is defined is, as I would expect, unsafe.
However, what I'm wondering is if the following example code is safe?

    #include <cassert>
    #include <fstream>
    #include <sstream>
    #include <pthread.h>
    #include <unistd.h>
    
    extern "C" {
        void* start_thread(void*);
    }
    
    int main()
    {
        pthread_attr_t attr;
        assert(!pthread_attr_init(&attr));
        assert(!pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM));
        pthread_t one;
        char one_data[] = "test1_one_";
        assert(!pthread_create(&one, &attr, &start_thread, one_data));
        pthread_t two;
        char two_data[] = "test1_two_";
        assert(!pthread_create(&two, &attr, &start_thread, two_data));
        assert(pthread_join(one, 0));
        assert(pthread_join(two, 0));
    }
    
    void* start_thread(void* arg)
    {
        char* name_prefix = static_cast<char*>(arg);
        std::ofstream out;
        for (int i = 0; ; ++i) {
            std::ostringstream name;
            name << name_prefix << i % 5 << ".dat";
            out.clear();
            out.open(name.str().c_str(), std::ios::trunc);
            if (!out.is_open()) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
            if (!out) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
            out << i << '\n';
            out.close();
            if (out.is_open()) {
                std::cerr << "Ack: " << __FILE__ << " " << __LINE__ << '\n';
                exit(1);
            }
        }
        return 0;
    }

I would imagine that it would be safe as each 'std::basic_ofstream' in
each of the threads is not being shared between the threads as in the
previous example.  However, the only thing that has me wondering if
this is truly safe is that, as far as I can see, '_IO_list_all' is
global and, therefore, is used by all instances of
'std::basic_ofstream', or rather the 'std::basic_filebuf's contained
within them (or rather just plain 'ofstream' and 'filebuf' for
'libstdc++-2.8.1').  Was this true of 'libstdc++-2.8.1' () or am I
mistaken?  If it was true for '2.8.1', is it still true for the
current release?  If this is true, I would think that access to
'_IO_list_all' would have to have some sort of mutex protection to
make the above example safe.  However, the libstdc++ FAQ tells me that
the library implementation is not thread-safe
< http://gcc.gnu.org/onlinedocs/libstdc++/faq/index.html#5_6 >, with
'std::basic_string' being the only exception and, therefore, I would
think that there are no mutexes used in the library implementation
(expect for 'std::basic_string').

Or has this question really already been answered in the documentation
and I have either not found it or not fully understood it?
Anyway... any help would be appreciated, even a request that I go back
and RTFM as long as it contains a reference to the relavent part of
the FM.

--
Damien Kick



More information about the Libstdc++ mailing list