httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "William A. Rowe, Jr." <wr...@lnd.com>
Subject RE: A solution to the Win32 console problem
Date Sun, 18 Jun 2000 15:34:12 GMT
> From: TOKILEY@aol.com [mailto:TOKILEY@aol.com]
> Sent: Saturday, June 17, 2000 6:18 PM
> 
> > William Rowe writes...
> 
> >  > Kevin Kiley wrote...
> >  >
> >  > * KEEPING THE WIN32 CONSOLE ON EXIT...
> >  > 
> >  > 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.

That's fine.  Watchdog Apache.exe - we simply document; "Never, Never
attempt to control RunApache.bat through any watchdog program.  Period.
Run Apache.exe from your watchdog program."

> 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.

No, again... run it as a console, under a win32 wrapper, whatever you
wish to do, but never friggin run the batch file that way.  It's for
*SHORTCUTS ONLY*.

> 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.

So it is, and yes, it's effectively another 'driver'.  That's fine.

> 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.

That's exactly what I'm saying:  Keep running Apache.exe - but if you
like shortcuts, here they are :)

> > 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.

Ya know what, if they are going to be dinking with watchdogs, rewriting
shortcuts, and playing obtuse games, then they will get a 'RTFM' message
back on the newsgroup.  Otherwise, if they are learning, this will
protect them till they know enough to be dangerous :)

> 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.

And this solves it (80/20)

> > 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.

And see above, we will never please everybody, so how do we chop
out 80% of the bug reports in one whack without hurting any one
interest too terribly?

> >  > 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.

No, Ryan already began.  We are correcting this in 2.0 as we speak,
and if you want some assurance, see the STATUS file.

> 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.

I agree with a good number of folk here, large changes that touch a big
body of the code are badness, and must be avoided for 1.3.13.  If you
want to work up the patch, I'll bite and think it through, but I want
this to roll in the next two weeks - max - and I'm just holding on
for a better install script and the stat() cleanup listed in STATUS.

> >  > 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.

I recognize that, which is why I like it :)

> >  > 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
> >  > 
> >  > // EXIT CAPTURE MACRO
> >  > 
> >  > // 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.

It's quite simple, but will be instantiated about two dozen times in the
code... I don't think it would be a problem to call out to a function
WRAPPED in a macro, such as 

#define exit(x) ap_pause_for_error_and_exit(x)

which is all I'm suggesting, the code looks (on a quick glance) just
fine for what we want to do (with the caviat that it will interact
with the console_control_handler()).
 
> Ever looked in the very headers ( both UNIX and Win32 ) that you 
> are including in every current Apache compile?

No, I've written 100+ line macros, and monthly write several 20+ line 
macros for my customizations to the MS ATL.  I don't hate macros, I
just avoid abusing them for what _can_ be a function.  What I code
as a macro simply can't be.

> 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.

Simple in Devstudio... settings - source file - custom - -i
I need that all the time for my run-time RCHAR and RCHARSTR string
classes with overloads on the entire Win32 environment.  Source is
forthcoming once I write the autogen script that allows anyone to
butcher their own copies of all the Platform SDK drivel.

> 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.

Can't, and maybe I see where we are confused.  Only the parent failure
will be caught.  Any other situation (child faulting) will be logged,
but that won't cause invocation of this handler.

If it's hooked, we are a console.  If it's not, we don't care (we were
something else.)  That simple, really.  But a process, the parent, has
a console, and you can alloc one per process (so I understand.)

> 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.

I'll break it...

  if (retval != ERROR_SUCCESS)
      exit(1);
  do_something_useful();

In your simple example... expanded to

  if (retval != ERROR_SUCCESS)
      printf("Press any key to perform exit(%d)...\n", 1 ); 
  while(!kbhit()); 
  exit(code);
  do_something_useful();

Code indented for clarity.  Now if we already protect exit() blocks,
that's well and good, but as a vanilla macro, it's a bit dangerous.
 
> 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 ).

Perhaps we don't care, at this point... as I said a bit earlier, I'm
only focused on "Apache started and blinked away"... I'm not worried
about the process that started 10 minutes ago.  Is the suggestion good?
Yes - I like it, we can even add impersonation logic to throw message
boxes to the console.  But I'm trying to get the 80% solved early next week :)
 
> 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.

I like this too.  Perhaps an ap_log_error_and_terminate() is the best
solution, since it can dispatch the log, close up the logs cleanly,
call other cleanups that need to be run under 2.0, and tell the MPM
we've died.

> 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.
> 
> * STARTUP CODE MODIFICATIONS.
> 
> 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.
> 
> Example:
> 
> 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.
> 
> IFDEF __DPMI16__
> ;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
> ELSE
> ;Set up and call WinMain for Windows application
>         push    __hInstance
>         push    __hPrev
>         push    __psp
>         push    word ptr __pszCmdline
>         push    __cmdShow
> __WINMAINCALL:  call    WINMAIN
> ENDIF
>         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
>         ret
> __cleanup       ENDP
> 
> ;       Check for null pointers before exit.  NO-OP on Windows.
> 
> __checknull     PROC    DIST
>         PUBLIC  __checknull
>         ret
> __checknull     ENDP
> 
> ;       Restore grabbed interrupt vectors.  NO-OP on Windows.
> 
> __restorezero     PROC    DIST
>         PUBLIC  __restorezero
>         ret
> __restorezero     ENDP
> 
> ;       Exit to DOS
> 
> __terminate     PROC    DIST
>         PUBLIC  __terminate
> 
> IF LDATA EQ false
>         mov     ax,-1
>         push    ax
>         call    UNLOCKSEGMENT
> ENDIF
>         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
>         endp
> 
> ;       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 ).

Believe it or not, I've already been contemplating this type
of solution.  It is library specific, but for this _specific_
feature that would only apply to 1.3.13+, for a problem that
noone else shares, and that is not manditory (it's not really
-necessary- to hold the console open, just desireable)...
this is an alternative.

I crown this solution #3 (#1: batch file, #2, your macro,
perhaps functionalized.)  It doesn't bastardize the code base,
only adds an exit extension.  The best solution, simply modify
it to export the exit value so that we can 'see' the exit in
an atexit() handler, and say 'ok, looks good, close'; or 'ugh,
not good, let's hold out for close button or the esc key'.
 
> >  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.

Actually, I'm at 33.6 this month (over ISDN conditioned lines (don't
ask) but that doesn't compare to effective 19.8 over poor copper.)
I'm speaking of those on their own frac-T1 and up.  Yes, ViewCVS can
print pretty much raw text, no extra wasteful html codes :)

Bill


Mime
View raw message