This is the mail archive of the pthreads-win32@sources.redhat.com mailing list for the pthreas-win32 project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Problems still open: for discussion


Hi all,

A few weeks back (May 7) Milan Gardian sent me some
patches, which have been merged into the new snapshot.

I'm fowarding Milan's message because it contains some items
that are still open problems that he suggests others may like
to discuss. It will also be of interest to anyone who wants to
know exactly what HAS been fixed in the latest snapshot
(apart from mutexes [Thomas Pfaff], condition variables
[Alexander Terekhov/Louis Thomas], and read/write
locks [Alexander Terekhov]) - and what hasn't.

For those interested, the two zip attachments referred to
in section 5 and the Conclusion can be downloaded from:

ftp://sources.redhat.com/pub/pthreads-win32/Contrib/


Regards and thanks all for the continued improvements to the
library.

Ross

-----------

Dear Ross,

Recently I (finally) decided to bring our pthreads library up-to-date
with
the latest snapshot, 2000-12-29. Unfortunately, I've had some problems
compiling the new stuff and running the test suite correctly (in our
environment). The rest of this mail will provide detailed discussion on
the
changes I have made together with reasons for those changes. Of course
not
all of them might be reasonable, in that case I will be happy to hear
from
you ASAP! Attached please find my version of the pthreads library that
incorporates my fixes of all the problems mentioned below.

My configuration is as follows:
Dual PIII/500 (SMP), 512MB RAM
MS W2k, SP1
MS VC++ 6, SP3
MS Platform SDK Nov/2000

Now, the problems...

-----------------
(1) Makefile typo
-----------------

"pthreads/Makefile" (for VC++ nmake) on line 57 says 'if exit'... Should
be
'if exist'. This is encountered only when using 'realclean' makefile
predicate (i.e. "nmake realclean").



------------------
(2) errno warnings
------------------

Those warnings emerge when compiling pthreads library with /MD (or /MDd)
compiler switch, instead of /MT (or /MTd) (i.e. when compiling pthreads
using Multithreaded DLL CRT instead of Multithreaded statically linked
CRT).
You can reproduce this problem easily by replacing the switch '/MT' in
"pthreads/Makefile", line 21 with switch '/MD'. Afterwards, while
compiling
both VSE and VCE versions of pthreads, compiler produces the following
warning:

warning C4273: '_errno' : inconsistent dll linkage.  dllexport assumed

The problem (as I have identified it) is that pre-processor directive
which
decides whether the current compiler supports thread-safe version of
errno
or not is not correct/complete. After I replaced the snapshot directive
(additional newlines for better readability),
------
#if (! defined(NEED_ERRNO)) ||
    (! defined( _REENTRANT ) &&
     (! defined( _MT ) ||
      ! defined( _MD )
     )
    )
------
with the following directive (again, additional newlines for better
readability),
------
#if (! defined(HAVE_ERRNO)) &&
    (! defined(_REENTRANT)) &&
    (! defined(_MT))
------

there are no linkage warnings anymore. The new directive evaluates to
true
if any of the macros 'HAVE_ERRNO', '_REENTRANT' or '_MT' is defined
(DeMorgan's law) - and true means the current compiler already has
thread-safe version of errno and does not need an additional one. The
scenarios (in the case of Visual C++ & Win32 platform) are as follows:

  a) /MT:  defined macro _MT
  b) /MTd: defined macro _MT and _DEBUG
  c) /MD:  defined macro _MT and _DLL
  d) /MDd: defined macro _MT and _DLL and _DEBUG

The bottom line is that all those 4 cases now evaluate (in
pre-processor) to
true (because all define the '_MT' macro) and that means compiler's
version
of errno will be used (which is correct as VC++ errno is already thread
safe
in both /MT (/MTd) and /MD (/MDd) modes).

The directive was changed at two places:
"pthreads/errno.c", line 27
"pthreads/pthread.h", line 945



------------------------------
(3) Calling convention problem
------------------------------

When I built the debug version of pthreads library and tried to run one
of
the test cases in the integrated environment as a debug build, I
received a
CRT error message that claimed that "checking of ESP register failed"
and
that this problem is usually due to incompatible calling conventions
when
using pointers to functions.

After investigating sources of pthreads library a little, this (i.e.
mixed
calling conventions, __stdcall vs. __cdecl) turned out to be a real
problem.
The main part of the trouble was the unfortunate combination of
(non-uniform) usage of macro "PT_STDCALL" and explicit
pointer-to-function
casting.

The non-uniform usage of "PT_STDCALL" macro means that SOMETIMES the
pointer-to-cleanup-function was defined like this:

void (PT_STDCALL * ptr1)(void *)

and at other times the same pointer was defined WITHOUT the PT_STDCALL
macro:

void (* ptr2)(void *)

Because "PT_STDCALL" is defined as "__stdcall" in VC++, and because
default
calling convention in C/C++ programs is "__cdecl", the above
pointers-to-cleanup-functions are actually compiled differently:

void (__stdcall * ptr1)(void *)

vs.

void (__cdecl * ptr2)(void *)

Of course, compiler WOULD detect the violation of calling convention
(e.g.
using C/C++ function pointers (__cdecl calling convention) where WINAPI
function pointers (PASCAL alias __stdcall calling convention) are
expected
(and vice-versa), if there were no explicit casts in the
"pthread_cleanup_push" macro...

Those explicit casts forced compiler to accept
pointer-to-cdecl-functions
(see ptr2 above) in constructor of PThreadCleanup internal class even
though
the constructor expected pointer-to-stdcall-function (see ptr1 above).
This
caused the PThreadCleanup class to call the given cleanup function
according
to "__stdcall" invocation rules, no matter what was the actual (true)
calling convention of the function.

Because by default all C/C++ functions use "__cdecl" calling convention
and
because pthreads VCE expect C++ compiler and uses the PThreadCleanup for
cleanup, anytime pthreads VCE was using cleanup functionality
internally, it
was forcing the cleanup function to be invoked as "__stdcall" function
even
though it was actually "__cdecl" function... The same is also true for
cleanup test cases.

I think I need not stress the fact that mixing calling conventions might
have unpredictable consequences in production code (especially in C++
where
we rely heavily on stack unrolling and correct automatic variables
deletion). While in "__cdecl" calling convention the caller is
responsible
for cleaning-up the stack, in "__stdcall" calling convention the callee
in
responsible for cleaning-up the stack - therefore if we have a function
compiled as "__cdecl" (default in C/C++), it expects that stack is
cleaned-up by the one that calls it. Now when we call this function
through
a pointer from a code that is compiled believing the function uses
"__stdcall", the code supposes the function will clean-up the stack
itself... and that is not good ;).

I fixed this problem by removing the macro "PT_STDCALL" completely from
the
code and defined a new type that describes what form should the cleanup
function have:

typedef void (__cdecl *ptw32_cleanup_callback_t)(void *);

I changed the pthreads library to use this pointer-to-cleanup-function
type
exclusively throughout the code (i.e. uniformly, I hope ;) ). I will not
list all the changes here, instead you may want to compare the snapshot
version with my new version using your favourite diff tool...



-------------------------------------------
(4) Cancellation problems in optimised code
-------------------------------------------

This is a problem that I have NOT fixed - I haven't found a way to solve
it.
Therefore I post it only as a warning / suggestion for discussion on
pthreads mailing list...

The cancellation (actually, cleanup-after-cancel) tests fail when using
VC
(professional) optimisation switches (/O1 or /O2) in pthreads library. I
have not investigated which concrete optimisation technique causes this
problem (/Og, /Oi, /Ot, /Oy, /Ob1, /Gs, /Gf, /Gy, etc.), but here is a
summary of builds and corresponding failures:

  * Original pthreads VSE (optimised tests): OK
  * Original pthreads VCE (optimised tests): Failed "cleanup0" test
(runtime)

  * Original pthreads VSE (DLL CRT, optimised tests): OK
  * Original pthreads VCE (DLL CRT, optimised tests): Failed "cleanup0"
test
(runtime)

  * My pthreads VSE (optimised tests): OK
  * My pthreads VCE (optimised tests): Failed "cleanup1" test (runtime)

  * My pthreads VSE (DLL in CRT, optimised tests): OK
  * My pthreads VCE (DLL in CRT, optimised tests): Failed "cleanup1"
test
(runtime)

Please note that while in VSE version of the pthreads library the
optimisation does not really have any impact on the tests (they pass
OK), in
VCE version addition of optimisation (/O2 in this case) causes the tests
to
fail uniformly - either in "cleanup0" or "cleanup1" test cases.

Please note that all the tests above use default pthreads DLL (no
optimisations, linked with either static or DLL CRT, based on test
type).
Therefore the problem lies not within the pthreads DLL but within the
compiled client code (the application using pthreads -> involvement of
"pthread.h").

I think the message of this section is that usage of VCE version of
pthreads
in applications relying on cancellation/cleanup AND using optimisations
for
creation of production code is highly unreliable for the current version
of
the pthreads library.



---------------------------
(5) Terminate problem in VC
---------------------------

The exception handling code in pthreads library takes advantage of the
standard C++ termination handler, "terminate()". I would recommend the
following article discussing this topic:

MSDN: Handling Exceptions (Part 12)
http://msdn.microsoft.com/library/Welcome/dsmsdn/deep121699.htm

One thing of particular interest should be the sentence "unlike an
unexpected handler, a terminate handler must end the program" -> there
might
be no further exception thrown from a terminate handler, as stated by
the
C++ standard. But this is exactly what pthreads library relies on - e.g.
"exception3" test (which fails in default pthreads VCE build) uses
"pthread_exit" to exit the current thread in the terminate handler.
Unfortunately, pthread_exit throws an exception internally. This
exception
is caught later on by pthreads code and allows graceful termination of
the
thread.

However, this design causes the fact that usage of "pthread_exit" in
terminate handler ultimately breaks the rule "terminate handler is not
allowed to throw further exceptions". There's still a possibility of
using
"unexpected" instead of "terminate" (unexpected is, unlike terminate,
allowed to throw further exceptions), but I will leave this decision to
a
more in-depth discussion between people using pthreads library.

What I did change in the pthreads library is the way caught exceptions
are
handled. Instead of calling "terminate()" function directly, I only use
"set_terminate()" function to acquire address of terminate handler,
invoking
it manually (in file "private.c"):

    terminate_function term_func = set_terminate(0);
    set_terminate(term_func);

    if (term_func != 0) {
        term_func();
    }

This (magically :) ) solves the problem of "exception3" test in VCE
build of
pthreads library compiled using VC++ compiler, but ONLY when using DLL
CRT... (/MD or /MDd switch). When using statically linked CRT, it does
not
work.

The problem with the static CRT version seems to be that the address of
user-defined terminate handler set by the call to "set_terminate"
function
in an application is not propagated correctly to the attached pthreads
DLL.
Let me illustrate my point with the following (fictive) example:

    //application code
    void my_function() { ... };

    void main() {
1.    set_terminate(my_function);
2.    dll_function()
        |
        ---> //dll code
             void dll_function() {
3.             terminate_function fn = set_terminate(0);
               set_terminate(fn);
             }

On line 1, we set a custom terminate handler using "set_terminate". Then
we
call a function in an attached DLL, line 2. If we use "set_terminate"
INSIDE
the DLL code to get the address of the termination handler (line 3), we
either receive a totally different address (when using static CRT), or
we
receive the correct address (when using DLL CRT).

To further illustrate this problem I have created a VC++ project that
implements the above example (see the attached "dll_test.zip"). It
contains
a trivial dll exporting one simple function and an application that
calls
this DLL function. It has 4 compilation targets, "Static Debug", "DLL
Debug", "Static Release" and "DLL Release" (static debug/release use
static
CRT i.e. /MTd and /MT respectively, DLL debug/release use DLL CRT i.e.
/MDd
and /MD respectively). Like expected, "DLL Debug" and "DLL Release" work
correctly while "Static Debug" and "Static Release" work incorrectly.

I suppose that once we are able to solve the problem of the
"set_terminate"
with static CRT in the "dll_test" example, we will be able to apply the
same
solution to the pthreads library. Until then, I have to give up on the
pthreads with static CRT (our production code uses DLL CRT, fortunately
;)
).



----------
Conclusion
----------

The attached archive "test_results.zip" contains summary of tests I have
performed with the pthreads. Excel table contains two sheets, one
describing
the makefiles used in tests and the other one describing the actual
tests
and their results.

The tests were performed using the "pthreads_test.rb" script (for Ruby
scripting language). Example usage is like this:

ruby pthreads_test.rb c:\temp\pthreads c:\temp\tst01 prj_ab tst_a VSE

This causes the script to copy the sources located in c:\temp\pthreads
to a
new location, c:\temp\tst01. Then it replaces c:\temp\tst01\Makefile
with a
copy of "prj_ab" (renamed to Makefile) and also replaces
c:\temp\tst01\tests\Makefile with a copy of "tst_a" (also renamed to
Makefile). Afterwards it proceeds in making a clean build of pthreads
library and a clean build of the tests.

This automation enabled me to perform all 16 tests using only two base
directories (one for the original pthreads snapshot, the other one for
my
pthreads version), 6 makefiles (i.e. perform optimisation or not, use
static
CRT or DLL CRT, etc.) by simply invoking the script with different
parameters.


I hope this mail is clear enough and that some of the points will
contribute
to a better quality of the pthreads library. If you have any
questions/comments/flames :), please contact me. I am looking forward to
hearing from you.

Best regards,

	Milan Gardian, Styrker Leibinger (TatraMed)

my_pthreads.zip

dll_test.zip

test_results.zip


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]