public Integer handleError(ESXX esxx, Context cx, Throwable ex) {
    String title = "ESXX Server Error";
    int code = 500;

    if (ex instanceof ESXXException) {
      code = ((ESXXException) ex).getStatus();
    }

    StringWriter sw = new StringWriter();
    PrintWriter out = new PrintWriter(sw);

    out.println(esxx.getHTMLHeader());
    out.println("<h2>" + title + "</h2>");
    out.println("<h3>Unhandled exception: " + ex.getClass().getSimpleName() + "</h3>");
    if (ex instanceof ESXXException
        || ex instanceof javax.xml.stream.XMLStreamException
        || ex instanceof javax.xml.transform.TransformerException) {
      out.println("<p><tt>" + encodeXMLContent(ex.getMessage()) + "</tt></p>");
    } else if (ex instanceof RhinoException) {
      out.println("<pre>");
      out.println(ex.getClass().getSimpleName() + ": " + encodeXMLContent(ex.getMessage()));
      out.println(((RhinoException) ex).getScriptStackTrace(new ESXX.JSFilenameFilter()));
      out.println("</pre>");
    } else {
      out.println("<pre>");
      ex.printStackTrace(out);
      out.println("</pre>");
    }
    out.println(esxx.getHTMLFooter());
    out.close();

    try {
      return handleResponse(
          esxx, cx, new Response(code, "text/html; charset=UTF-8", sw.toString(), null));
    } catch (Exception ex2) {
      // Hmm
      return 20;
    }
  }
  /** Interrupts script execution. */
  private void interrupted(Context cx, final StackFrame frame, Throwable scriptException) {
    ContextData contextData = frame.contextData();
    boolean eventThreadFlag = callback.isGuiEventThread();
    contextData.eventThreadFlag = eventThreadFlag;

    boolean recursiveEventThreadCall = false;

    interruptedCheck:
    synchronized (eventThreadMonitor) {
      if (eventThreadFlag) {
        if (interruptedContextData != null) {
          recursiveEventThreadCall = true;
          break interruptedCheck;
        }
      } else {
        while (interruptedContextData != null) {
          try {
            eventThreadMonitor.wait();
          } catch (InterruptedException exc) {
            return;
          }
        }
      }
      interruptedContextData = contextData;
    }

    if (recursiveEventThreadCall) {
      // XXX: For now the following is commented out as on Linux
      // too deep recursion of dispatchNextGuiEvent causes GUI lockout.
      // Note: it can make GUI unresponsive if long-running script
      // will be called on GUI thread while processing another interrupt
      if (false) {
        // Run event dispatch until gui sets a flag to exit the initial
        // call to interrupted.
        while (this.returnValue == -1) {
          try {
            callback.dispatchNextGuiEvent();
          } catch (InterruptedException exc) {
          }
        }
      }
      return;
    }

    if (interruptedContextData == null) Kit.codeBug();

    try {
      do {
        int frameCount = contextData.frameCount();
        this.frameIndex = frameCount - 1;

        final String threadTitle = Thread.currentThread().toString();
        final String alertMessage;
        if (scriptException == null) {
          alertMessage = null;
        } else {
          alertMessage = scriptException.toString();
        }

        int returnValue = -1;
        if (!eventThreadFlag) {
          synchronized (monitor) {
            if (insideInterruptLoop) Kit.codeBug();
            this.insideInterruptLoop = true;
            this.evalRequest = null;
            this.returnValue = -1;
            callback.enterInterrupt(frame, threadTitle, alertMessage);
            try {
              for (; ; ) {
                try {
                  monitor.wait();
                } catch (InterruptedException exc) {
                  Thread.currentThread().interrupt();
                  break;
                }
                if (evalRequest != null) {
                  this.evalResult = null;
                  try {
                    evalResult = do_eval(cx, evalFrame, evalRequest);
                  } finally {
                    evalRequest = null;
                    evalFrame = null;
                    monitor.notify();
                  }
                  continue;
                }
                if (this.returnValue != -1) {
                  returnValue = this.returnValue;
                  break;
                }
              }
            } finally {
              insideInterruptLoop = false;
            }
          }
        } else {
          this.returnValue = -1;
          callback.enterInterrupt(frame, threadTitle, alertMessage);
          while (this.returnValue == -1) {
            try {
              callback.dispatchNextGuiEvent();
            } catch (InterruptedException exc) {
            }
          }
          returnValue = this.returnValue;
        }
        switch (returnValue) {
          case STEP_OVER:
            contextData.breakNextLine = true;
            contextData.stopAtFrameDepth = contextData.frameCount();
            break;
          case STEP_INTO:
            contextData.breakNextLine = true;
            contextData.stopAtFrameDepth = -1;
            break;
          case STEP_OUT:
            if (contextData.frameCount() > 1) {
              contextData.breakNextLine = true;
              contextData.stopAtFrameDepth = contextData.frameCount() - 1;
            }
            break;
        }
      } while (false);
    } finally {
      synchronized (eventThreadMonitor) {
        interruptedContextData = null;
        eventThreadMonitor.notifyAll();
      }
    }
  }