httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
Subject Re: A solution to the Win32 console problem
Date Sat, 17 Jun 2000 19:18:23 GMT

> William Rowe writes...

>  > Kevin Kiley wrote...
>  >
>  > 
>  > The /K option might cause some problems with watchdog style scripts
>  > or batch files that are designed to re-start the server. If people
>  > don't add specific 'exit' commands to their scripts to close the
>  > console then you could end up with a box in the closet that
>  > re-generates so many 'kept' console windows that the available memory
>  > goes away and it will reach a point where the Server won't re-start
>  > anymore and will probably have to be re-booted to get rid of all
>  > the consoles.
>  If you use those, you should be using services, and the parent Apache
>  process is our watchdog (right?)  

Under 2.0 it's supposed to be... but I thought you were looking for 
something that retrofits 1.3.x as well and I've never seen anyone
running 1.3.x Apache in a production environment WITHOUT some 
kind of external 'watchdog' deal going on.

I think, also, that many will choose to run 2.0 as a non-service 
so the 'no exit' /K flag problem may carry over into 2.0.

Many people are 'afraid' of the whole 'Services' thing due to some
bug reports and just the general 'obtuseness' of it all.

It just doesn't meet their idea of 'running a program' under Win32.
Not being familiar with UNIX 'daemons' it just crosses the line for
them and makes it look like some kind of 'device driver' or something.

They will still be seeking to run the new multi-threaded version
but they will want to run it 'the same way we always have' as a
simple 3rd party application complete with whatever re-start scripts
they had in place with 1.3.x.

> I agree... I've seen the horror, but a warning in the batch file should 
> be sufficient, nay?  

Hmm... not sure... check this logic...

If someone is stupid enough not to know that aborted non /K
Win32 console modules disappear on exit() and it is not an 'error'
( as has been reported by many ) and they also haven't read
anything far enough to know they can (should) always check
the error logs... Do you think they will be off reading .BAT
files before sending useless PR reports? I don't think so.

Most of the PR reports in this area are always the 'Hey I just
ran your windows program and I don't get a window and some
black box flashes on my screen what's wrong with your software'
kinda folk.

> These batch files are -ONLY- for window shortcut users 
> who don't know any better.

See above. Those very same folk are the ones who will
NEVER acutually want to run is as a 'service' because
they really don't even know what that means.
>  > In fear and trepidation I offer the following alternative...
>  > 
>  > ( NOTE: This does NOT provide a solution for GP Faults but
>  > then neither does /K. Even a Win32 console that was started
>  > with the /K 'keep' option will usually disappear in a puff
>  > of smoke if the problem is a GP fault. The suggestion is
>  > only for for tracking unexpected calls to 'exit()' ).
>  Agreed... earlier comments about SET probably should be evaluated
>  in terms of the 2.0 server, but not 1.3.x - and clean exit paths
>  in the 2.0 overhaul will make this whole task a breeze.

Exactly. The only real answer is to clean up the exit points once
and for all and add whatever 'stop here' logic is deemed necessary.
Anything else is a kludge but I thought that's what you were 
looking for since the 'exit()' overhaul always seems to remain a
'we'll get around to it someday but not this year' kinda thing.
Why not skip the /K kludge itself and go for the real solution?

Just say that no consoles are going to be 'held open' until
the code that does it is the new 'official' ap_exit() routine which
MUST replace all existing 'exit()' calls ASAP.

Any kind of /K or macro or start-up code kludge just continues
to allow the real solution to be postponed even longer.

>  > Whenever I unpack a new copy of Apache source I simply add
>  > some macros to it that will 'catch' any calls to 'exit()'
>  > and let me read any console messages and/or choose to let
>  > the console go away. It does it on a TIMER if I am not there
>  > so that it still functions 'normally' and the console will
>  > be destroyed so watchdog scripts can get the right error
>  > code and safely 're-start' without resources disappearing.
>  > 
>  > As an example... the macro below is about as simple as it
>  > gets... but still retains the problem of 'holding up' the show
>  > and scripts or batch files designed to handle error recovery won't
>  > ever be able to kick in until someone presses a key...
>  > 
>  > #undef exit
>  > #define exit(code) \
>  >   printf("Press any key to perform exit(%d)...\n", code ); \
>  >   while(!kbhit()); \
>  >   exit(code);
>  It's simple, buck ugly, and I suggest you wrap it in a {} block...
>  or set this up as a little function.  But this -could- work.

It DOES work... all the time... every time.

>  > The next macro is 'the real deal'...
>  > 
>  > It accounts for multi-threading and uses a timer so that
>  > there is plenty of time to read the error message(s) but will
>  > still 'continue normally' if no one is around and the console
>  > still 'goes away'.
>  > 
>  > The best part about it is that it tells you EXACTLY where
>  > the 'exit()' is happening includng source file name and
>  > exact line number.
>  > 
>  > #ifdef WIN32
>  > 
>  > 
>  > // Redefine the 'exit()' function so we can intercept all calls
>  > // and have the chance to read the error message(s) before the
>  > // console closes since it might not have been launched with
>  > // the /K 'keep' flag...
>  > 
>  > CRITICAL_SECTION g_exit_interceptor_critical_section1;
>  > long g_exit_interceptor_startup_time;
>  > long g_exit_interceptor_current_time;
>  > long g_exit_interceptor_elapsed_time;
>  > 
>  > // GetCurrentTime() works but Microsoft says it is now obsolete
>  > // and remains only for backward compatibility with 16 bit
>  > // programs. It would only break about a million 16 and 32 bit
>  > // programs if they dropped it but go ahead and use GetTickCount()
>  > // instead of GetCurrentTime() since it works just as well.
>  > //
>  > // MSVC and Borland will allow comments inside the macro but
>  > // both compilers have the same problem when it comes to
>  > // using the 'double-slash' single line comments. Both compilers
>  > // will screw up the macro unless it uses standard ANSI 'C' comment
>  > // style for each separate comment line with the 'line continuation'
>  > // marker at the end.
>  > 
>  > // Once this new 'exit()' macro has been defined and a thread
>  > // goes to issue an 'exit(99)' command ( or some other exit code )
>  > // this is what will appear on the console...
>  > 
>  > // ****> exit(99)
>  > // File: .\src\RCTPDW.C
>  > // Line: 197
>  > // Inside exit(99) critical section now...
>  > // The program will terminate in 30 seconds...
>  > // You may press any key during that time and
>  > // the wait loop will terminate early.
>  > //
>  > // Performing real exit(99) now...
>  > //
>  > // V:\RCI\SERVERS\RCTPDW >  <-- Actual exit to OS with error code 99
>  > 
>  > #undef exit
>  > #define exit(code) \
>  >   InitializeCriticalSection( 
>  > &g_exit_interceptor_critical_section1 ); \
>  >   EnterCriticalSection( &g_exit_interceptor_critical_section1 ); \
>  >   printf( "****> exit(%d)\n", code ); \
>  >   printf( "File: %s\n",  __FILE__ ); \
>  >   printf( "Line: %ld\n", (long) __LINE__ ); \
>  >   printf( "Inside exit(%d) critical section now...\n", code ); \
>  >   printf( "The program will terminate in 30 seconds...\n" ); \
>  >   printf( "You may press any key during that time and\n" ); \
>  >   printf( "the wait loop will terminate early.\n" ); \
>  >   g_exit_interceptor_startup_time = GetTickCount(); \
>  >   for ( ;; ) \
>  >      { \
>  >       if (kbhit()) break; \
>  >       g_exit_interceptor_current_time = GetTickCount(); \
>  >       g_exit_interceptor_elapsed_time = \
>  >       g_exit_interceptor_current_time - \
>  >       g_exit_interceptor_startup_time; \
>  >       /* Remember that all the times are in milliseconds so */ \
>  >       /* multiply the wait time in seconds by 1000... */ \
>  >       if ( g_exit_interceptor_elapsed_time > (30*1000) ) break; \
>  >      } \
>  >   printf( "\nPerforming real exit(%d) now...\n", code ); \
>  >   /* It isn't even necessary to release the critical section */ \
>  >   /* and, in fact, might be harmful to do so since other threads */ \
>  >   /* will get a few more CPU cycles when the critsec comes off. */ \
>  >   LeaveCriticalSection( &g_exit_interceptor_critical_section1 ); \
>  >   exit( code ); // Return original exit code to OS
>  > 
>  > #endif // WIN32
>  Ugh.  Again, a {} block is probably required.  And I'd really like
>  to see this delegated to a function.  We also have to watch how it
>  would interact with the new console_control_handler function...
>  and if a service (if the console_control_handler isn't registered)
>  only then run with it (that code could be a macro) but if you want 
>  to put this in the form of a 'functionalized' patch, I'd certainly 
>  consider it.  It's simply too dirty to be inlined for my tastes :-)

That's why I always hate to submit macros to people.
Some people love them, some people cop an attitude about them.

I don't know what is 'dirty' about it... looks pretty simple to me.

Ever looked in the very headers ( both UNIX and Win32 ) that you 
are including in every current Apache compile?

Some of the those system header macros that are included
at all times make the simple example above look like the cliff notes 
for War and Peace.

I use macros all the time... they don't ever scare me... but I can
understand the way some people feel about them. The only time
you get to 'really see' all the code that is generated is to look
at the pre-compiler output and this is just too much of a waste
of time for most folk.

If the 'size' issue is the balk point from all the inlining then
I submit that once an executable is over about 200k it doesn't
really matter much... it's already big enough where another
8-10k won't hurt anything. 'In for a penny in for a pound' as they say.

With regards to the console control handler... it really shouldn't
be an issue. If you want you could simply allocate ANOTHER
console in the exit macro itself and 'pause' while the user
reads the error message. Any existing console won't even
know what's happening. You could also allocate a 'new' 
console buffer on the same console handle and then just
'switch screens' with the 'SetActiveConsoleBuffer()' thing
to the other buffer for as long as it takes for the user to read 
the message.

That's getting pretty whacky but jumping through these kinds
of hoops just for people who don't know how to go read a simple
ASCII error log is already pretty whacky unto itself.

BTW: The {} block is most assuredly NOT required. Maybe for
gcc but never for MSVC or Borland. The macro works perfectly 
fine at all times just as it is. I have never found an occasion in
the Apache code where it doesn't work.

As a matter of fact... You can simply add the following to it
as well and it will handle all cases where there isn't even a 
visible console...

sprintf( g_exit_interceptor_scratch_buffer, "exit(%d) in %s @ %ld",
code, __FILE__, (long) __LINE__ );
MessageBox(0, g_exit_interceptor_scratch_buffer, "Apache exit()", MB_OK );

Even an invisible console window can still put a MessageBox() on the 
screen at any time as long as you use ZERO or GetDesktopWindow()
as the target window handle ( Desktop ).

It is also not required to be a function but it could be but now the
macro itself will have to PASS the __FILE__ and __LINE__ info
to the function so the chance to see where the 'exit()' is happening
is not lost... which is really the best part about it.

Wouldn't it be great to get PR reports that ( finally ) give module
and line numbers for crap-outs instead of ones that just say
'I went to access this page and your Server died' ?

Knowing exactly WHICH 'exit()' in the code cause the PR 
report would really save some time, methinks.


If the macro is just too scary or messy to consider then there is still 
another 'simple' way to gracefully handle the 'exit()' calls... but somehow
I think this might be considered even 'scarier' if you didn't like the macro.

You could simply add a few simple lines of code to your
own 'C' language STARTUP code that is used at compile time
and create a more flexible and/or sophisticated 'program exit'.

For a Win32 console mode program this is usually one of
the C0X??.ASM modules depending on the memory model.


The following is what 'actually' calls 'main()' or 'WinMain()'...

The same startup code is used for both console programs and
regular Windows programs. The only thing that is different
about a Windows program is that WINMAIN is called instead
of _main.

They both 'fall-through' and call the same '_exit' routine
for a 'normal' exit or '__exit' for an 'abnormal' exit.

The only time the abnormal '__exit' is called is if the
secret global __abend has been set somwhere before the
return from the mainline code.

;Set up and call _main for DPMI16 application

        push    word ptr [__C0environ+2]
        push    word ptr [__C0environ]
        push    word ptr [__C0argv+2]
        push    word ptr [__C0argv]
        push    [__C0argc]
        call    _main
;Set up and call WinMain for Windows application
        push    __hInstance
        push    __hPrev
        push    __psp
        push    word ptr __pszCmdline
        push    __cmdShow
        push    ax      ; Push return value
        cmp     __abend, 0
        jne     @@abnormalexit
        call    _exit   ; Normal exit
@@abnormalexit: call    __exit  ; Abnormal exit, don't call destructors etc.

So what does that give you? A lot, actually, once you realize
that even though '_exit'  and '__exit' are somewhere off in the LIB code 
and are declared 'extern' they are both GOING to 'call back' into your code
looking for certain 'cleanup' routines to run.

Once that 'callback' takes place you can be sure that an
'exit' is in progress and then do whatever you want.

A little farther down in your startup code you will find
the very 'callbacks' in your code space that will be 'called' by
'_exit' and '__exit'...

You will find a section that looks like this...

; _cleanup()      call all #pragma exit cleanup routines.
; _checknull()    check for null pointer zapping copyright message
; _terminate(int) exit program with error code
; _restorezero()  restore interrupt vectors
; These functions are called by exit(), _exit(), _cexit(),
; and _c_exit().

;       Call cleanup routines

__cleanup       PROC    DIST
        PUBLIC  __cleanup

        mov     ax,ds
        mov     es,ax
        push    si
        push    di
        mov     si,offset DGROUP:ExitStart
        mov     di,offset DGROUP:ExitEnd
        call    Cleanup
        pop     di
        pop     si
__cleanup       ENDP

;       Check for null pointers before exit.  NO-OP on Windows.

__checknull     PROC    DIST
        PUBLIC  __checknull
__checknull     ENDP

;       Restore grabbed interrupt vectors.  NO-OP on Windows.

__restorezero     PROC    DIST
        PUBLIC  __restorezero
__restorezero     ENDP

;       Exit to DOS

__terminate     PROC    DIST
        PUBLIC  __terminate

        mov     ax,-1
        push    ax
        call    UNLOCKSEGMENT
        mov     bp,sp
        mov     al,[bp+cPtrSize]
        mov     ah,4ch                  ;exit
        int     21h
__terminate     ENDP

@@Fail:         mov     al,0ffh
        push    ax
        call    _exit

        mov     ah,4ch                  ;exit
        int     21h

;       Return default data segment in AX

At any one ( or all ) of those points you could simply
'hold the phone' and do whatever you want before the
final 'Goodnight'. You could simply pop error codes off
the stack and display them on a console and wait for
a key or run a 'countdown to exit' timer... or whatever.

'terminate' is really the 'Good Night, Gracie' spot ( and
isn't it fascinating to see that even under Win32 it still
happens with a good old INT 21 DOS call!  Albeit...
revectored and faked for Win32 but still very much there ).

You could even just add 1 line somewhere that calls
a 'C' function back in the source code to handle the
'countdown to exit' or whatever needs to be done.

Just a thought ( Probably way out of line but it really
is no big deal... this code is as much a part of your
compilation as any source module and you are free
to do what you like with it at any time... the compiler
and the linker won't care ).

>  > * VIEWCVS
>  > 
>  > ( To William Rowe )...
>  > 
>  > Thanks for providing the VIEWCVS links!
>  > 
>  > I didn't even know there was such a thing. Pretty neat!
>  That's my job... and thank Greg for its current form :-)

It's really a neat piece of kit but obviously written by someone
who has a high-speed Internet connection and assumes everyone
else does too.
>  > There is a way to receive all that HTML into your browser
>  > compressed upwards of 85 percent in real time. You don't
>  > need any sort of 'client' piece to do it... just any version
>  > of MSIE or Netscape or Opera that supports standard IETF
>  > Content-Encoding.
>  > 
>  > Proxy your browser to port 5010
>  > and ask for the following Apache VIEWCVS document...
>  > 
>  >
>  > http_main.c.diff?r1=1.491&r2=1.503
>  > 
>  > The Online Internet Compression Server at
>  > obtains the page from Apache at T3 speeds, compresses it
>  > dynamically upwards of 85 percent and then sends the highly
>  > reduced version back to your browser over the modem connection.
>  Fun... yes, I regularly zip up ebcdic print images of 100000+
>  pages, and yes, 95% compression is typical.

Ah... but isn't it better when you don't want to stop and have to do
that ( or can't because Whoops!... it's dynamically generated like
VIEWCVS and Online Search pages. ).

The Online Compression Server does it all automatically all the
time. No questions asked.
>  > Going through the Online Internet Compression Server
>  > at is the only thing that makes the
>  > VIEWCVS tolerable here in dial-up land.
>  No doubt, but next time I'll post a simplified diff link instead
>  for those who aren't so lucky.

Incomplete sentence?

'Aren't so lucky' as whom... you? I assume that means you have
some fast connection and you don't really know what I'm talking
about with regards to VIEWCVS access being excruciating.

Being lucky shouldn't have to be the case. We ( poor unfortunate
dial-up users ) shouldn't be short-changed. We should still be
able to see 'The Full Monty' and see it ASAP because, as
they say, 'We have the technology'.

If you do post a simplified 'diff' link can you still post that one
GZIPPED? With .gz on the end?

That would work fine and wouldn't require the Proxy as long as the 
Server is capable of and set up to handle IETF content negotiation 
for compressed files.

If I knew I could count on compressed files 'being there' I probably wouldn't 
even bother to use the Online Compression Server myself ( except for
all dynamic HTML which will never fit into the IETF scheme ) but since 
the static .gz files are pretty much always guaranteed to NOT be there 
and I KNOW my browsers are capable of receiving compressed data at 
all times it only made sense to write the compression server which handles
both static and dynamic content equally well.


Kevin Kiley
CTO, Remote Communications, Inc. - Online Internet Content Compression Server.

View raw message