Bug 688817

Summary: Endless 'interrupt in interrupt' with GSAPI
Product: Ghostscript Reporter: SaGS <sags5495>
Component: PS InterpreterAssignee: leonardo <leonardo>
Status: NOTIFIED FIXED    
Severity: normal    
Priority: P2    
Version: master   
Hardware: PC   
OS: Windows XP   
Customer: Word Size: ---
Attachments: Suggested fix.

Description SaGS 2006-07-29 07:18:05 UTC
When a GSAPI client tries to stop GS by returning a negative value 
from the polling callback function, GS enters an infinite loop 
handling cascading "interrupt" exceptions (assuming the normal 
user_errors == 0 parameter to gsapi_run_string_*()).

The problem exists, but (given the default error handler) is not 
apparent, in TRUNK rev 6909 and earlier. It surfaced approx. at TRUNK 
rev 6914, after the recent changes in stdio handling. Because of it, 
GSView is hardly usable with current GS (after opening a file, one 
needs a Ctrl+Alt+Del to close it).

To reproduce the error, open a PS/EPS file in GSView and close the 
GSView window by clicking the "X" button. With many files, GSView 
waits forever for the PostScript interpreter to quit. GSView is 
otherwise responsive; typing "M" shows GS's output, which looks like:

   "GSview 4.6 2004-01-11
    AFPL Ghostscript SVN PRE-RELEASE 8.55 (2006-05-20)
    Copyright (C) 2006 artofcode LLC, Benicia, CA.  All rights reserved.
    This software comes with NO WARRANTY: see the file PUBLIC for details.
    Displaying non DSC file " ... "/gs/lib/showpage.ps
    Unrecoverable error: interrupt in interrupt
    Operand stack:
        --nostringval--  false  Unrecoverable error: interrupt in interrupt
    Operand stack:
        --nostringval--  false  Unrecoverable error: interrupt in interrupt
    Operand stack:
        --nostringval--  false  Unrecoverable error: interrupt in interrupt
    ..."

(At some point, "interrupt" alternates with "execstackoverflow".)

This error happens with most files in examples\, and even with 
lib\showpage.ps.

The error cannot be reproduced with GS alone, even with "-B", because 
it is strictly connected to stopping the interpreter via the polling 
callback.

Details ---

- The interrupt is detected in interp.c::interp() (rev 6912) 
  line #1647 "set_code_on_interrupt(imemory, &code);".
- interp() returns code == e_interrupt to interp.c::gs_call_interp() 
  line #485.
- Given the normal user_errors == 0, gs_call_interp() reflects 
  the error back at PostScript level, and the normal PS error 
  handler starts.
- interp() is called again, and the error handler attempts to 
  print an error message, dump the stacks and then quit.
- The error handler is long enough for ticks_left to reach 0.
- When this happens, the polling callback is called and returns < 0 
  again (the GSAPI client still wants the interpreter to stop!).
- A new interrupt is detected (while handling the old one), 
  so the interrupt handler is reentered.
- This whole process is repeated forever.

Why this didn't happen in rev 6909 and earlier ---

- Each time the error handler was writing something to stdout, 
  interp() was exited with code == e_NeedStdout, then entered again.
- When interp() reenters, ticks_left is reinitialized to 100. This 
  is a step that cannot happen in rev 6914 and later.
- Between 2 consecutive e_NeedStdout requests, the default error 
  handler was executing too little operations for ticks_left to 
  reach 0, so the error handler was essentially executed without 
  checking for interrupts. This allowed it to finish normally 
  and quit.

  A different error handler may make this problem happen even 
  with GS revisions before 6909.

Fix and workaround ---

I don't think processing e_interrupt the same way as normal PS 
errors (obey user_errors, etc.) is a good idea; afterall, the GSAPI 
client requests the interpreter to abort, and the error handler may 
do anything, even attempt to continue the PS program indefinitely.

The simplest fix is to change interp.c::gs_call_interp() to return 
when it gets an e_interrupt, and this is what the suggested patch 
does. Disadvantage: the GSAPI client actually sees an e_interrupt 
(<0, but not among the special values previously listed in API.htm),
but I don't think a GSAPI client is interested in printing error 
messages/ etc. after it requested an abort. I also thought to return 
as if an e_Quit happened, but then what to put into *perror_object?

Of course, a workaround is for the GSAPI client to return < 0 only 
once, then return 0 until gsapi_run_string_*() returns. However, 
I don't think this is what API.htm intends to say.
Comment 1 SaGS 2006-07-29 07:19:06 UTC
Created attachment 2390 [details]
Suggested fix.

Return e_interrupt as soon as an interrupt is detected.

An attempt to mention the e_interrupt in the docs is included.
(There were some "<b></b>" missing in those paragraphs, too.)
Comment 2 Ralph Giles 2006-08-02 09:44:39 UTC
assigning to ray for review. Looks ok to me.
Comment 3 leonardo 2006-10-04 06:21:44 UTC
I'll commit the patch soon because Ray is too busy.
Comment 4 leonardo 2007-01-21 03:03:47 UTC
Bumping priority for problems with a suggested patch.
Comment 5 leonardo 2007-01-22 02:32:12 UTC
Patch to HEAD :
http://ghostscript.com/pipermail/gs-cvs/2007-January/007210.html