[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37. Asynchronous Events; Quit Checking


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.1 Signal Handling


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.2 Control-G (Quit) Checking

Note: The code to handle QUIT is divided between ‘lisp.h’ and ‘signal.c’. There is also some special-case code in the async timer code in ‘event-stream.c’ to notice when the poll-for-quit (and poll-for-sigchld) timers have gone off.

Here’s an overview of how this convoluted stuff works:

  1. Scattered throughout the XEmacs core code are calls to the macro QUIT; This macro checks to see whether a C-g has recently been pressed and not yet handled, and if so, it handles the C-g by calling signal_quit(), which invokes the standard Fsignal() code, with the error being Qquit. Lisp code can establish handlers for this (using condition-case), but normally there is no handler, and so execution is thrown back to the innermost enclosing event loop. (One of the things that happens when entering an event loop is that a condition-case is established that catches all calls to signal, including this one.)
  2. How does the QUIT macro check to see whether C-g has been pressed; obviously this needs to be extremely fast. Now for some history. In early Lemacs as inherited from the FSF going back 15 years or more, there was a great fondness for using SIGIO (which is sent whenever there is I/O available on a given socket, tty, etc.). In fact, in GNU Emacs, perhaps even today, all reading of events from the X server occurs inside the SIGIO handler! This is crazy, but not completely relevant. What is relevant is that similar stuff happened inside the SIGIO handler for C-g: it searched through all the pending (i.e. not yet delivered to XEmacs yet) X events for one that matched C-g. When it saw a match, it set Vquit_flag to Qt. On TTY’s, C-g is actually mapped to be the interrupt character (i.e. it generates SIGINT), and XEmacs’s handler for this signal sets Vquit_flag to Qt. Then, sometime later after the signal handlers finished and a QUIT macro was called, the macro noticed the setting of Vquit_flag and used this as an indication to call signal_quit(). What signal_quit() actually does is set Vquit_flag to Qnil (so that we won’t get repeated interruptions from a single C-g press) and then calls the equivalent of (signal ’quit nil).
  3. Another complication is introduced in that Vquit_flag is actually exported to Lisp as quit-flag. This allows users some level of control over whether and when C-g is processed as quit, esp. in combination with inhibit-quit. This is another Lisp variable, and if set to non-nil, it inhibits signal_quit() from getting called, meaning that the C-g gets essentially ignored. But not completely: Because the resetting of quit-flag happens only in signal_quit(), which isn’t getting called, the C-g press is still noticed, and as soon as inhibit-quit is set back to nil, a quit will be signalled at the next QUIT macro. Thus, what inhibit-quit really does is defer quits until after the quit- inhibitted period.
  4. Another consideration, introduced by XEmacs, is critical quitting. If you press Control-Shift-G instead of just C-g, quit-flag is set to critical instead of to t. When QUIT processes this value, it ignores the value of inhibit-quit. This allows you to quit even out of a quit-inhibitted section of code! Furthermore, when signal_quit() notices that it was invoked as a result of a critical quit, it automatically invokes the debugger (which otherwise would only happen when debug-on-quit is set to t).
  5. Well, I explained above about how quit-flag gets set correctly, but I began with a disclaimer stating that this was the old way of doing things. What’s done now? Well, first of all, the SIGIO handler (which formerly checked all pending events to see if there’s a C-g) now does nothing but set a flag – or actually two flags, something_happened and quit_check_signal_happened. There are two flags because the QUIT macro is now used for more than just handling QUIT; it’s also used for running asynchronous timeout handlers that have recently expired, and perhaps other things. The idea here is that the QUIT macros occur extremely often in the code, but only occur at places that are relatively safe – in particular, if an error occurs, nothing will get completely trashed.
  6. Now, let’s look at QUIT again.
  7. UNFINISHED. Note, however, that as of the point when this comment got committed to CVS (mid-2001), the interaction between reading C-g as an event and processing it as QUIT was overhauled to (for the first time) be understandable and actually work correctly. Now, the way things work is that if C-g is pressed while XEmacs is blocking at the top level, waiting for a user event, it will be read as an event; otherwise, it will cause QUIT. (This includes times when XEmacs is blocking, but not waiting for a user event, e.g. accept-process-output and wait_delaying_user_events().) Formerly, this was supposed to happen, but didn’t always due to a bizarre and broken scheme, documented in next_event_internal like this:

    If we read a C-g, then set quit-flag but do not discard the C-g. The callers of next_event_internal() will do one of two things:

    1. set Vquit_flag to Qnil. (next-event does this.) This will cause the ^G to be treated as a normal keystroke.
    2. not change Vquit_flag but attempt to enqueue the ^G, at which point it will be discarded. The next time QUIT is called, it will notice that Vquit_flag was set.

    This required weirdness in enqueue_command_event_1 like this:

    put the event on the typeahead queue, unless the event is the quit char, in which case the QUIT which will occur on the next trip through this loop is all the processing we should do - leaving it on the queue would cause the quit to be processed twice.

    And further weirdness elsewhere, none of which made any sense, and didn’t work, because (e.g.) it required that QUIT never happen anywhere inside next_event_internal() or any callers when C-g should be read as a user event, which was impossible to implement in practice.

    Now what we do is fairly simple. Callers of next_event_internal() that want C-g read as a user event call begin_dont_check_for_quit(). next_event_internal(), when it gets a C-g, simply sets Vquit_flag (just as when a C-g is detected during the operation of QUIT or QUITP), and then tries to QUIT. This will fail if blocked by the previous call, at which point next_event_internal() will return the C-g as an event. To unblock things, first set Vquit_flag to nil (it was set to t when the C-g was read, and if we don’t reset it, the next call to QUIT will quit), and then unbind_to() the depth returned by begin_dont_check_for_quit(). It makes no difference is QUIT is called a zillion times in next_event_internal() or anywhere else, because it’s blocked and will never signal.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.2.1 Reentrancy Problems due to QUIT Checking

Checking for QUIT can do quite a long of things – since it pumps the event loop, this may cause arbitrary code to get executed, garbage collection to happen. etc. (In fact, garbage collection cannot happen because it is inhibited.) This has led to crashes when functions get called reentrantly when not expecting it. Example:

Crash – reentrant re_match_2()

 
  /* dont_check_for_quit is set in three circumstances:

     (1) when we are in the process of changing the window
     configuration.  The frame might be in an inconsistent state,
     which will cause assertion failures if we check for QUIT.

     (2) when we are reading events, and want to read the C-g
     as an event.  The normal check for quit will discard the C-g,
     which would be bad.

     (3) when we're going down with a fatal error.  we're most likely
     in an inconsistent state, and we definitely don't want to be
     interrupted. */

  /* We should *not* conditionalize on Vinhibit_quit, or
     critical-quit (Control-Shift-G) won't work right. */

  /* WARNING: Even calling check_quit(), without actually dispatching
     a quit signal, can result in arbitrary Lisp code getting executed
     -- at least under Windows. (Not to mention obvious Lisp
     invocations like asynchronous timer callbacks.) Here's a sample
     stack trace to demonstrate:

 NTDLL! DbgBreakPoint@0 address 0x77f9eea9
assert_failed(const char * 0x012d036c, int 4596, const char * 0x012d0354) line 3478
re_match_2_internal(re_pattern_buffer * 0x012d6780, const unsigned char * 0x00000000, int 0, const unsigned char * 0x022f9328, int 34, int 0, re_registers * 0x012d53d0 search_regs, int 34) line 4596 + 41 bytes
re_search_2(re_pattern_buffer * 0x012d6780, const char * 0x00000000, int 0, const char * 0x022f9328, int 34, int 0, int 34, re_registers * 0x012d53d0 search_regs, int 34) line 4269 + 37 bytes
re_search(re_pattern_buffer * 0x012d6780, const char * 0x022f9328, int 34, int 0, int 34, re_registers * 0x012d53d0 search_regs) line 4031 + 37 bytes
string_match_1(long 31222628, long 30282164, long 28377092, buffer * 0x022fde00, int 0) line 413 + 69 bytes
Fstring_match(long 31222628, long 30282164, long 28377092, long 28377092) line 436 + 34 bytes
Ffuncall(int 3, long * 0x008297f8) line 3488 + 168 bytes
execute_optimized_program(const unsigned char * 0x020ddc50, int 6, long * 0x020ddf50) line 744 + 16 bytes
funcall_compiled_function(long 34407748, int 1, long * 0x00829aec) line 516 + 53 bytes
Ffuncall(int 2, long * 0x00829ae8) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x020ddc90, int 4, long * 0x020ddf90) line 744 + 16 bytes
funcall_compiled_function(long 34407720, int 1, long * 0x00829e28) line 516 + 53 bytes
Ffuncall(int 2, long * 0x00829e24) line 3523 + 17 bytes
mapcar1(long 15, long * 0x00829e48, long 34447820, long 34187868) line 2929 + 11 bytes
Fmapcar(long 34447820, long 34187868) line 3035 + 21 bytes
Ffuncall(int 3, long * 0x00829f20) line 3488 + 93 bytes
execute_optimized_program(const unsigned char * 0x020c2b70, int 7, long * 0x020dd010) line 744 + 16 bytes
funcall_compiled_function(long 34407580, int 2, long * 0x0082a210) line 516 + 53 bytes
Ffuncall(int 3, long * 0x0082a20c) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x020cf810, int 6, long * 0x020cfb10) line 744 + 16 bytes
funcall_compiled_function(long 34407524, int 0, long * 0x0082a580) line 516 + 53 bytes
Ffuncall(int 1, long * 0x0082a57c) line 3523 + 17 bytes
run_hook_with_args_in_buffer(buffer * 0x022fde00, int 1, long * 0x0082a57c, int 0) line 3980 + 13 bytes
run_hook_with_args(int 1, long * 0x0082a57c, int 0) line 3993 + 23 bytes
Frun_hooks(int 1, long * 0x0082a57c) line 3847 + 19 bytes
run_hook(long 34447484) line 4094 + 11 bytes
unsafe_handle_wm_initmenu_1(frame * 0x01dbb000) line 736 + 11 bytes
unsafe_handle_wm_initmenu(long 28377092) line 807 + 11 bytes
condition_case_1(long 28377116, long (long)* 0x0101c827 unsafe_handle_wm_initmenu(long), long 28377092, long (long, long)* 0x01005fa4 mswindows_modal_loop_error_handler(long, long), long 28377092) line 1692 + 7 bytes
mswindows_protect_modal_loop(long (long)* 0x0101c827 unsafe_handle_wm_initmenu(long), long 28377092) line 1194 + 32 bytes
mswindows_handle_wm_initmenu(HMENU__ * 0x00010199, frame * 0x01dbb000) line 826 + 17 bytes
mswindows_wnd_proc(HWND__ * 0x000501da, unsigned int 278, unsigned int 65945, long 0) line 3089 + 31 bytes
USER32! UserCallWinProc@20 + 24 bytes
USER32! DispatchClientMessage@20 + 47 bytes
USER32! __fnDWORD@4 + 34 bytes
NTDLL! KiUserCallbackDispatcher@12 + 19 bytes
USER32! DispatchClientMessage@20 address 0x77e163cc
USER32! DefWindowProcW@16 + 34 bytes
qxeDefWindowProc(HWND__ * 0x000501da, unsigned int 274, unsigned int 61696, long 98) line 1188 + 22 bytes
mswindows_wnd_proc(HWND__ * 0x000501da, unsigned int 274, unsigned int 61696, long 98) line 3362 + 21 bytes
USER32! UserCallWinProc@20 + 24 bytes
USER32! DispatchClientMessage@20 + 47 bytes
USER32! __fnDWORD@4 + 34 bytes
NTDLL! KiUserCallbackDispatcher@12 + 19 bytes
USER32! DispatchClientMessage@20 address 0x77e163cc
USER32! DefWindowProcW@16 + 34 bytes
qxeDefWindowProc(HWND__ * 0x000501da, unsigned int 262, unsigned int 98, long 540016641) line 1188 + 22 bytes
mswindows_wnd_proc(HWND__ * 0x000501da, unsigned int 262, unsigned int 98, long 540016641) line 3362 + 21 bytes
USER32! UserCallWinProc@20 + 24 bytes
USER32! DispatchMessageWorker@8 + 244 bytes
USER32! DispatchMessageW@4 + 11 bytes
qxeDispatchMessage(const tagMSG * 0x0082c684 {msg=0x00000106 wp=0x00000062 lp=0x20300001}) line 989 + 10 bytes
mswindows_drain_windows_queue() line 1345 + 9 bytes
emacs_mswindows_quit_p() line 3947
event_stream_quit_p() line 666
check_quit() line 686
check_what_happened() line 437
re_match_2_internal(re_pattern_buffer * 0x012d5a18, const unsigned char * 0x00000000, int 0, const unsigned char * 0x02235000, int 23486, int 14645, re_registers * 0x012d53d0 search_regs, int 23486) line 4717 + 14 bytes
re_search_2(re_pattern_buffer * 0x012d5a18, const char * 0x02235000, int 23486, const char * 0x0223b38e, int 0, int 14645, int 8841, re_registers * 0x012d53d0 search_regs, int 23486) line 4269 + 37 bytes
search_buffer(buffer * 0x022fde00, long 29077572, long 13789, long 23487, long 1, int 1, long 28377092, long 28377092, int 0) line 1224 + 89 bytes
search_command(long 29077572, long 46975, long 28377116, long 28377092, long 28377092, int 1, int 1, int 0) line 1054 + 151 bytes
Fre_search_forward(long 29077572, long 46975, long 28377116, long 28377092, long 28377092) line 2147 + 31 bytes
Ffuncall(int 4, long * 0x0082ceb0) line 3488 + 216 bytes
execute_optimized_program(const unsigned char * 0x02047810, int 13, long * 0x02080c10) line 744 + 16 bytes
funcall_compiled_function(long 34187208, int 3, long * 0x0082d1b8) line 516 + 53 bytes
Ffuncall(int 4, long * 0x0082d1b4) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x01e96a10, int 6, long * 0x020ae510) line 744 + 16 bytes
funcall_compiled_function(long 34186676, int 3, long * 0x0082d4a0) line 516 + 53 bytes
Ffuncall(int 4, long * 0x0082d49c) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x02156b50, int 4, long * 0x020c2db0) line 744 + 16 bytes
funcall_compiled_function(long 34186564, int 2, long * 0x0082d780) line 516 + 53 bytes
Ffuncall(int 3, long * 0x0082d77c) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x0082d964, int 3, long * 0x020c2d70) line 744 + 16 bytes
Fbyte_code(long 29405156, long 34352480, long 7) line 2392 + 38 bytes
Feval(long 34354440) line 3290 + 187 bytes
condition_case_1(long 34354572, long (long)* 0x01087232 Feval(long), long 34354440, long (long, long)* 0x01084764 run_condition_case_handlers(long, long), long 28377092) line 1692 + 7 bytes
condition_case_3(long 34354440, long 28377092, long 34354572) line 1779 + 27 bytes
execute_rare_opcode(long * 0x0082dc7c, const unsigned char * 0x01b090af, int 143) line 1269 + 19 bytes
execute_optimized_program(const unsigned char * 0x01b09090, int 6, long * 0x020ae590) line 654 + 17 bytes
funcall_compiled_function(long 34186620, int 0, long * 0x0082df68) line 516 + 53 bytes
Ffuncall(int 1, long * 0x0082df64) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x02195470, int 1, long * 0x020c2df0) line 744 + 16 bytes
funcall_compiled_function(long 34186508, int 0, long * 0x0082e23c) line 516 + 53 bytes
Ffuncall(int 1, long * 0x0082e238) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x01e5d410, int 6, long * 0x0207d410) line 744 + 16 bytes
funcall_compiled_function(long 34186312, int 1, long * 0x0082e524) line 516 + 53 bytes
Ffuncall(int 2, long * 0x0082e520) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x02108fb0, int 2, long * 0x020c2e30) line 744 + 16 bytes
funcall_compiled_function(long 34186340, int 0, long * 0x0082e7fc) line 516 + 53 bytes
Ffuncall(int 1, long * 0x0082e7f8) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x020fe150, int 2, long * 0x01e6f510) line 744 + 16 bytes
funcall_compiled_function(long 31008124, int 0, long * 0x0082ebd8) line 516 + 53 bytes
Ffuncall(int 1, long * 0x0082ebd4) line 3523 + 17 bytes
run_hook_with_args_in_buffer(buffer * 0x022fde00, int 1, long * 0x0082ebd4, int 0) line 3980 + 13 bytes
run_hook_with_args(int 1, long * 0x0082ebd4, int 0) line 3993 + 23 bytes
Frun_hooks(int 1, long * 0x0082ebd4) line 3847 + 19 bytes
Ffuncall(int 2, long * 0x0082ebd0) line 3509 + 14 bytes
execute_optimized_program(const unsigned char * 0x01ef2210, int 5, long * 0x01da8e10) line 744 + 16 bytes
funcall_compiled_function(long 31020440, int 2, long * 0x0082eeb8) line 516 + 53 bytes
Ffuncall(int 3, long * 0x0082eeb4) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x0082f09c, int 3, long * 0x01d89390) line 744 + 16 bytes
Fbyte_code(long 31102388, long 30970752, long 7) line 2392 + 38 bytes
Feval(long 31087568) line 3290 + 187 bytes
condition_case_1(long 30961240, long (long)* 0x01087232 Feval(long), long 31087568, long (long, long)* 0x01084764 run_condition_case_handlers(long, long), long 28510180) line 1692 + 7 bytes
condition_case_3(long 31087568, long 28510180, long 30961240) line 1779 + 27 bytes
execute_rare_opcode(long * 0x0082f450, const unsigned char * 0x01ef23ec, int 143) line 1269 + 19 bytes
execute_optimized_program(const unsigned char * 0x01ef2310, int 6, long * 0x01da8f10) line 654 + 17 bytes
funcall_compiled_function(long 31020412, int 1, long * 0x0082f740) line 516 + 53 bytes
Ffuncall(int 2, long * 0x0082f73c) line 3523 + 17 bytes
execute_optimized_program(const unsigned char * 0x020fe650, int 3, long * 0x01d8c490) line 744 + 16 bytes
funcall_compiled_function(long 31020020, int 2, long * 0x0082fa14) line 516 + 53 bytes
Ffuncall(int 3, long * 0x0082fa10) line 3523 + 17 bytes
Fcall_interactively(long 29685180, long 28377092, long 28377092) line 1008 + 22 bytes
Fcommand_execute(long 29685180, long 28377092, long 28377092) line 2929 + 17 bytes
execute_command_event(command_builder * 0x01be1900, long 36626492) line 4048 + 25 bytes
Fdispatch_event(long 36626492) line 4341 + 70 bytes
Fcommand_loop_1() line 582 + 9 bytes
command_loop_1(long 28377092) line 495
condition_case_1(long 28377188, long (long)* 0x01064fb9 command_loop_1(long), long 28377092, long (long, long)* 0x010649d0 cmd_error(long, long), long 28377092) line 1692 + 7 bytes
command_loop_3() line 256 + 35 bytes
command_loop_2(long 28377092) line 269
internal_catch(long 28457612, long (long)* 0x01064b20 command_loop_2(long), long 28377092, int * volatile 0x00000000) line 1317 + 7 bytes
initial_command_loop(long 28377092) line 305 + 25 bytes
STACK_TRACE_EYE_CATCHER(int 1, char * * 0x01b63ff0, char * * 0x01ca5300, int 0) line 2501
main(int 1, char * * 0x01b63ff0, char * * 0x01ca5300) line 2938
XEMACS! mainCRTStartup + 180 bytes
_start() line 171
KERNEL32! BaseProcessStart@4 + 115547 bytes

[explain dont_check_for_quit() et al]


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.3 Profiling

We implement our own profiling scheme so that we can determine things like which Lisp functions are occupying the most time. Any standard OS-provided profiling works on C functions, which is not always that useful – and inconvenient, since it requires compiling with profile info and can’t be retrieved dynamically, as XEmacs is running.

The basic idea is simple. We set a profiling timer using setitimer (ITIMER_PROF), which generates a SIGPROF every so often. (This runs not in real time but rather when the process is executing or the system is running on behalf of the process – at least, that is the case under Unix. Under MS Windows and Cygwin, there is no setitimer(), so we simulate it using multimedia timers, which run in real time. To make the results a bit more realistic, we ignore ticks that go off while blocking on an event wait. Note that Cygwin does provide a simulation of setitimer(), but it’s in real time anyway, since Windows doesn’t provide a way to have process-time timers, and furthermore, it’s broken, so we don’t use it.) When the signal goes off, we see what we’re in, and add 1 to the count associated with that function.

It would be nice to use the Lisp allocation mechanism etc. to keep track of the profiling information (i.e. to use Lisp hash tables), but we can’t because that’s not safe – updating the timing information happens inside of a signal handler, so we can’t rely on not being in the middle of Lisp allocation, garbage collection, malloc(), etc. Trying to make it work would be much more work than it’s worth. Instead we use a basic (non-Lisp) hash table, which will not conflict with garbage collection or anything else as long as it doesn’t try to resize itself. Resizing itself, however (which happens as a result of a puthash()), could be deadly. To avoid this, we make sure, at points where it’s safe (e.g. profile_record_about_to_call() – recording the entry into a function call), that the table always has some breathing room in it so that no resizes will occur until at least that many items are added. This is safe because any new item to be added in the sigprof would likely have the profile_record_about_to_call() called just before it, and the breathing room is checked.

In general: any entry that the sigprof handler puts into the table comes from a backtrace frame (except "Processing Events at Top Level", and there’s only one of those). Either that backtrace frame was added when profiling was on (in which case profile_record_about_to_call() was called and the breathing space updated), or when it was off – and in this case, no such frames can have been added since the last time start-profile was called, so when start-profile is called we make sure there is sufficient breathing room to account for all entries currently on the stack.

Jan 1998: In addition to timing info, I have added code to remember call counts of Lisp funcalls. The profile_increase_call_count() function is called from Ffuncall(), and serves to add data to Vcall_count_profile_table. This mechanism is much simpler and independent of the SIGPROF-driven one. It uses the Lisp allocation mechanism normally, since it is not called from a handler. It may even be useful to provide a way to turn on only one profiling mechanism, but I haven’t done so yet. –hniksic

Dec 2002: Total overhaul of the interface, making it sane and easier to use. –ben

Feb 2003: Lots of rewriting of the internal code. Add GC-consing-usage, total GC usage, and total timing to the information tracked. Track profiling overhead and allow the ability to have internal sections (e.g. internal-external conversion, byte-char conversion) that are treated like Lisp functions for the purpose of profiling. –ben

BEWARE: If you are modifying this file, be very careful. Correctly implementing the "total" values is very tricky due to the possibility of recursion and of functions already on the stack when starting to profile/still on the stack when stopping.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.4 Asynchronous Timeouts


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

37.5 Exiting

Ben’s capsule summary about expected and unexpected exits from XEmacs.

Expected exits occur when the user directs XEmacs to exit, for example by pressing the close button on the only frame in XEmacs, or by typing C-x C-c. This runs save-buffers-kill-emacs, which saves any necessary buffers, and then exits using the primitive kill-emacs.

However, unexpected exits occur in a few different ways:

Currently, both unexpected exit scenarios described above set preparing_for_armageddon to indicate that nonessential and possibly dangerous things should not be done, specifically:

(Also, all places that set preparing_for_armageddon also set dont_check_for_quit. This happens separately because it’s also necessary to set other variables to make absolutely sure no quitting happens.)

In the first scenario above (the access violation), we also set fatal_error_in_progress. This causes more things to not happen:


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Aidan Kehoe on December 27, 2016 using texi2html 1.82.