Call to local static object constructor not thread-safe 
Author Message
 Call to local static object constructor not thread-safe

In MSVC++ 6.0 SP3, the code generated to call the constructor of a local
static object doesn't guard against race conditions for the construction
flag read and write operations or the constructor call.  This occurs even
though I'm specifying either the -MD or -MT flags, which both indicate
I'm compiling for a multi-threaded environment and thus have made an
explicit indication I would value thread-safety.

For example, here's the assembly language generated for construction of a
local static object:

0050DE49   xor         eax,eax

0050DE50   and         eax,1
0050DE53   test        eax,eax
0050DE55   jne         NGInit+5Ch (0050de87)

(005e3ae8)]
0050DE5D   or          cl,1

(005e3ae8)],cl
0050DE66   push        offset InitNetworking (0050e10f)
0050DE6B   push        offset string "ProcL"+0E04h (005d1520)
0050DE70   mov         ecx,offset oneTime (005e3a28)
0050DE75   call        CallOnce::CallOnce (0052e720)
0050DE7A   push        offset $E35 (0050e11f)
0050DE7F   call        _atexit (00532452)
0050DE84   add         esp,4

The flag isn't protected by a synchronization primitive or use of a Win32
function like InterlockedCompareExchange (the latter requiring a change
to a 32-bit flag).  Thus, one thread could read the flag and then run out
of its time slice, and a second thread could read the same flag and run
the constructor, and by the time the first thread continued, it would be
working with a stale value but would go ahead and run the constructor a
second time.

Here's some source code to reproduce the problem:

#include <windows.h>

class callonce
{
public:
        callonce(void);
        ~callonce();

        void someFunction(void);

Quote:
};

callonce::callonce(void)
{

Quote:
}

callonce::~callonce()
{

Quote:
}

void callonce::someFunction(void)
{

Quote:
}

void testCallOnce(void)
{
        static class callonce oneTime;

        oneTime.someFunction();

Quote:
}

int main(void)
{
        testCallOnce();

        return 0;

Quote:
}

I'm also submitting this to the Visual C++ bug report web page.  I'm
curious if anybody else has encountered this.  I didn't see anything when
searching DejaNews.

--
.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:
Bob McDonald
Remove spam block when replying directly.
I'll cancel this account when I receive too much spam,
so don't expect this address to remain valid long-term.
.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:



Tue, 10 Sep 2002 03:00:00 GMT  
 Call to local static object constructor not thread-safe

Quote:

>In MSVC++ 6.0 SP3, the code generated to call the constructor of a local
>static object doesn't guard against race conditions for the construction
>flag read and write operations or the constructor call.  This occurs even
>though I'm specifying either the -MD or -MT flags, which both indicate
>I'm compiling for a multi-threaded environment and thus have made an
>explicit indication I would value thread-safety.

>For example, here's the assembly language generated for construction of a
>local static object:

>0050DE49   xor         eax,eax

>0050DE50   and         eax,1
>0050DE53   test        eax,eax
>0050DE55   jne         NGInit+5Ch (0050de87)

>(005e3ae8)]
>0050DE5D   or          cl,1

>(005e3ae8)],cl
>0050DE66   push        offset InitNetworking (0050e10f)
>0050DE6B   push        offset string "ProcL"+0E04h (005d1520)
>0050DE70   mov         ecx,offset oneTime (005e3a28)
>0050DE75   call        CallOnce::CallOnce (0052e720)
>0050DE7A   push        offset $E35 (0050e11f)
>0050DE7F   call        _atexit (00532452)
>0050DE84   add         esp,4

>The flag isn't protected by a synchronization primitive or use of a Win32
>function like InterlockedCompareExchange (the latter requiring a change
>to a 32-bit flag).  Thus, one thread could read the flag and then run out
>of its time slice, and a second thread could read the same flag and run
>the constructor, and by the time the first thread continued, it would be
>working with a stale value but would go ahead and run the constructor a
>second time.

>Here's some source code to reproduce the problem:

>#include <windows.h>

>class callonce
>{
>public:
>    callonce(void);
>    ~callonce();

>    void someFunction(void);
>};

>callonce::callonce(void)
>{
>}

>callonce::~callonce()
>{
>}

>void callonce::someFunction(void)
>{
>}

>void testCallOnce(void)
>{
>    static class callonce oneTime;

>    oneTime.someFunction();

>}

>int main(void)
>{
>    testCallOnce();

>    return 0;
>}

>I'm also submitting this to the Visual C++ bug report web page.  I'm
>curious if anybody else has encountered this.  I didn't see anything when
>searching DejaNews.

You can use the so-called Double-Checked Locking Pattern to get
thread-safe initialization in this context. See this message for more:

Newsgroups: microsoft.public.vc.language,microsoft.public.vc.mfc
Subject: Re: "Call Once" function? How to implement?
Date: Mon, 13 Mar 2000 23:18:54 -0600

--
Doug Harrison

Visual C++ MVP



Tue, 10 Sep 2002 03:00:00 GMT  
 Call to local static object constructor not thread-safe

says...

Quote:

> >In MSVC++ 6.0 SP3, the code generated to call the constructor of a local
> >static object doesn't guard against race conditions for the construction
> >flag read and write operations or the constructor call.
<snip>

> >For example, here's the assembly language generated for construction of a
> >local static object:

> >0050DE49   xor         eax,eax

> >0050DE50   and         eax,1
> >0050DE53   test        eax,eax
> >0050DE55   jne         NGInit+5Ch (0050de87)

> >(005e3ae8)]
> >0050DE5D   or          cl,1

> >(005e3ae8)],cl
> >0050DE66   push        offset InitNetworking (0050e10f)
> >0050DE6B   push        offset string "ProcL"+0E04h (005d1520)
> >0050DE70   mov         ecx,offset oneTime (005e3a28)
> >0050DE75   call        CallOnce::CallOnce (0052e720)
> >0050DE7A   push        offset $E35 (0050e11f)
> >0050DE7F   call        _atexit (00532452)
> >0050DE84   add         esp,4

> >The flag isn't protected by a synchronization primitive or use of a Win32
> >function like InterlockedCompareExchange (the latter requiring a change
> >to a 32-bit flag).  Thus, one thread could read the flag and then run out
> >of its time slice, and a second thread could read the same flag and run
> >the constructor, and by the time the first thread continued, it would be
> >working with a stale value but would go ahead and run the constructor a
> >second time.
<snip>

> You can use the so-called Double-Checked Locking Pattern to get
> thread-safe initialization in this context. See this message for more:

> Newsgroups: microsoft.public.vc.language,microsoft.public.vc.mfc
> Subject: Re: "Call Once" function? How to implement?
> Date: Mon, 13 Mar 2000 23:18:54 -0600


Thanks for the reply.  The pattern looks like something I was doing
before I tried to encapsulate it into a class.  I'm going to try
something like the following:

// doOnceOnly
//
// Arguments:
//   dwDone - external flag that is initialized to zero
//              before main is called.  Holds zero if initFunction never
called.
//              Holds nonzero if initFunction already called.
//       initFunction - function to call if OK to initialize
//
// Returns TRUE if the caller did the initialization, FALSE if not.
//

#define DOO_DONE 1
#define DOO_NOT_DONE 0

BOOL doOnceOnly(LPLONG plDone, void ((*initFunction)(void)))
{
        BOOL bCalledInitFunction = FALSE;

        // Do the cheap check first
        if (!*plDone)
        {
                // Do the expensive check
                LONG lInitStatus;
                // Casts used here because winbase.h declares ICE
                // as using PVOIDs, contrary to the
                // man page in the Platform SDK in MSDN library,
                // which uses LONGs.
                lInitStatus = (LONG) InterlockedCompareExchange(
                        (void**)plDone,
                        (void*)DOO_DONE,
                        (void*)DOO_NOT_DONE);
                if (DOO_NOT_DONE == lInitStatus)
                {
                        (*initFunction)();
                        bCalledInitFunction = TRUE;
                }
        }

        return bCalledInitFunction;

Quote:
}
> Doug Harrison

> Visual C++ MVP

--
.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:
Bob McDonald
Remove spam block when replying directly.
I'll cancel this account when I receive too much spam,
so don't expect this address to remain valid long-term.
.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:.:O:


Fri, 13 Sep 2002 03:00:00 GMT  
 Call to local static object constructor not thread-safe

Quote:

>Thanks for the reply.  The pattern looks like something I was doing
>before I tried to encapsulate it into a class.  I'm going to try
>something like the following:

>// doOnceOnly
>//
>// Arguments:
>//   dwDone - external flag that is initialized to zero
>//          before main is called.  Holds zero if initFunction never
>called.
>//          Holds nonzero if initFunction already called.
>//   initFunction - function to call if OK to initialize
>//
>// Returns TRUE if the caller did the initialization, FALSE if not.
>//

>#define DOO_DONE 1
>#define DOO_NOT_DONE 0

>BOOL doOnceOnly(LPLONG plDone, void ((*initFunction)(void)))
>{
>    BOOL bCalledInitFunction = FALSE;

>    // Do the cheap check first
>    if (!*plDone)
>    {
>            // Do the expensive check
>            LONG lInitStatus;
>            // Casts used here because winbase.h declares ICE
>            // as using PVOIDs, contrary to the
>            // man page in the Platform SDK in MSDN library,
>            // which uses LONGs.
>            lInitStatus = (LONG) InterlockedCompareExchange(
>                    (void**)plDone,
>                    (void*)DOO_DONE,
>                    (void*)DOO_NOT_DONE);
>            if (DOO_NOT_DONE == lInitStatus)
>            {
>                    (*initFunction)();
>                    bCalledInitFunction = TRUE;
>            }
>    }

>    return bCalledInitFunction;

>}

I dunno; imagine you inserted this statement after ICE or inside your
initialization function:

 Sleep(for_a_day);

What are the implications for secondary threads which subsequently
find themselves inside doOnceOnly while the first thread is sleeping?

--
Doug Harrison

Visual C++ MVP



Fri, 13 Sep 2002 03:00:00 GMT  
 
 [ 4 post ] 

 Relevant Pages 

1. Why is function static singleton not thread-safe?

2. Static constructor does not get called when using get accessor

3. Why OleDbCommand objects are not thread safe?

4. ATL Objects Are Not Thread-safe?

5. _endthread() Safe to call if not in thread?

6. global object in library - constructor not called

7. Thread-safe Local Variables

8. Are local statics thread-safe?

9. how? thread/process/module local storage constructors/destructors

10. Thread Safe Events and Static Methods

11. Are static methods thread safe?

12. Is static variable initialization thread-safe?

 

 
Powered by phpBB® Forum Software