This is the mail archive of the cygwin-developers mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [RFC] libgfortran dll i/o redirection lossage caused by order-of-termination issue


2009/7/8 Dave Korn:
>
> Â ÂHippos,
>
> ÂAs you may remember, there is a problem in current gfortran-4.3.2-2, when
> linking against the libgfortran DLL, and redirecting stdio. ÂTrivial testcase:
>
>> $ cat hw.F
>> Â Â Â program main
>> Â Â Â WRITE(*,*) Â Â 'SUCCESS'
>> Â Â Â end
>>
>> $ gfortran-4 hw.F -o hello
>>
>> $ ./hello.exe
>> ÂSUCCESS
>>
>> $ ./hello.exe | cat
>>
>> $
>
> ÂI've looked at this in some detail. ÂThe fortran runtime has its own fairly
> involved i/o stream library, quite akin to the FILE*-based f-stdio functions
> in libc. ÂAlso like libc, these streams ("units" in fortran terminology) can
> be buffered or unbuffered. ÂIn the case where you're not redirecting stdout,
> the bytes of the string are emitted immediately using calls to libc write(),
> but when you redirect on the command line, it notices that it's a non-tty and
> switches to buffered i/o mode.
>
> ÂIn buffered i/o mode, the runtime library allows the streams to accumulate a
> buffer's-worth of data at a time before it does an actual write() call. ÂThis
> means that there can still be buffered data when the user's program exits.
> That's OK; libgfortran includes a .dtors entry that calls a shutdown function
> that closes and flushes all the buffered data. ÂSo the user's program exits,
> the global dtors run, the buffered data is flushed and everything works just fine.
>
> ÂThat's how it goes with static linking, anyway. ÂBut not when you use a DLL.
> ÂThere's a problem in the shutdown ordering, and the final buffer of data gets
> lost. ÂAt the very end of dll_crt0_0, we have ...
>
>> Â if (user_data->main)
>> Â Â cygwin_exit (user_data->main (__argc, __argv, *user_data->envptr));
>
> ... and cygwin_exit passes control to exit() ...
>
>> extern "C" void
>> cygwin_exit (int n)
>> {
>> Â if (atexit_lock)
>> Â Â atexit_lock.acquire ();
>> Â exit (n);
>> }
>
> ... which is where I think all the exit paths (exit(), abort(), return from
> main) come back together:
>
>> void
>> _DEFUN (exit, (code),
>> Â Â Â int code)
>> {
>> Â __call_exitprocs (code, NULL);
>>
>> Â if (_GLOBAL_REENT->__cleanup)
>> Â Â (*_GLOBAL_REENT->__cleanup) (_GLOBAL_REENT);
>> Â _exit (code);
>> }
>
> ... and which passes control to _exit() ...
>
>> extern "C" void
>> _exit (int n)
>> {
>> Â do_exit (((DWORD) n & 0xff) << 8);
>> }
>>
>
> ... which calls do_exit(); and that's where process shutdown gets properly
> underway:
>
>> void __stdcall
>> do_exit (int status)
>> {
>> Â syscall_printf ("do_exit (%d), exit_state %d", status, exit_state);
>>
>> #ifdef NEWVFORK
>> Â vfork_save *vf = vfork_storage.val ();
>> Â if (vf != NULL && vf->pid < 0)
>> Â Â {
>> Â Â Â exit_state = ES_NOT_EXITING;
>> Â Â Â vf->restore_exit (status);
>> Â Â }
>> #endif
>>
>> Â lock_process until_exit (true);
>>
>> Â if (exit_state < ES_GLOBAL_DTORS)
>> Â Â {
>> Â Â Â exit_state = ES_GLOBAL_DTORS;
>> Â Â Â dll_global_dtors ();
>> Â Â }
>>
>> Â if (exit_state < ES_EVENTS_TERMINATE)
>> Â Â {
>> Â Â Â exit_state = ES_EVENTS_TERMINATE;
>> Â Â Â events_terminate ();
>> Â Â }
>
> (etc.).
>
> ÂNow, in this sequence of events, first we call the atexit hooks, which
> includes all static dtors:
>
>> void
>> _DEFUN (exit, (code),
>> Â Â Â int code)
>> {
>> Â __call_exitprocs (code, NULL);
>
> ... then we call newlib cleanup ...
>
>> Â if (_GLOBAL_REENT->__cleanup)
>> Â Â (*_GLOBAL_REENT->__cleanup) (_GLOBAL_REENT);
>
> ... then we terminate the loaded DLLs:
>
>> Â Â Â dll_global_dtors ();
>
> ÂThat's bad.

Ouch. That violates the C++98 standard as well, which requires that
atexit handlers and static destructors are invoked in the opposite
order that the atexit handlers were registered and the corresponding
static constructor were invoked, whereby: "A function registered with
atexit before an object obj1 of static storage duration is initialized
will not be called until obj1's destruction has completed. A function
registered with atexit after an object obj2 of static storage duration
is initialized will be called before obj2's destruction starts."

In other words: there should be a single stack for both atexit
handlers and static destructors, with atexit handlers getting pushed
by atexit(), and static destructors getting pushed at entry to their
corresponding constructors. No idea whether that's actually feasible
in connection with DLLs.

Andy


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