source: trunk/abcl/src/org/armedbear/lisp/Extensions.java

Last change on this file was 15584, checked in by Mark Evenson, 2 years ago

This commit makes interrupt-thread react quickly.

What happens on interrupt


I'm assuming there's both a worker thread(w) and a control thread(c) running.

Running in thread(c)

Call (interrupt-thread thread(w) function args)
thread(c).interruptThread unpacks the arguments into a list
It then calls thread(w).interrupt(function, args)
thread(w).interrupt pushes args and fun on thread(w) thread local variable pending
it then call java's interrupt() method.

If things are left alone, the java interrupt mechanism will at some
point throw an InterruptedException? when running inside thread(w) various
lispThread functions catch the InterruptedException? and call
processThreadInterrupts()

processThreadInterrupts() then runs the functions in the thread local
variable pending.

There's also one check that uses the java interrupt mechanism, a call to
thread.isInterrupted(). So if thread(w) notices that it's been
interrupted in this way it will also call processThreadInterrupts().

BUT

Java will only throw the exception when running functions like sleep or
wait, so the exception won't be handled until one of those methods is
called within thread(w). The call to isInterrupted() only happens once
per function call. So if a function is in a loop, it could be a long
time until the interrupt is handled.

Meanwhile:

Thread(w) is running. The compiler has thoughtfully inserted a check on
a static variable Lisp.interrupted inside each iteration of a loop. If a
global Lisp.interrupted get sets to true the compiler inserted code
calls Lisp.handleInterupts().

BUT

Lisp.interrupted is never set in the normal course of events. There's a
function to set it, interruptLisp() but no one calls it. interruptLisp
calls Lisp.setInterrupted() which sets Lisp.interrupted to true.

The change:

We add another static Lisp.threadToInterrupt.

We change setInterrupted() take the boolean but also a thread that you
want interrupted. We change handleInterrupts(), which used to call
break() to check whether the current thread is equal to threadTointerrupt.
When the current thread is thread(w), it call thread(w).processThreadInterrupts().

Then, we change interruptLisp() to call Lisp.setInterrupted() with the
thread to be interrupted.

We don't want to have to call interruptLisp separately. So we
call setInterrupted() directly in thread(c).interruptLisp().

And we win.

In slime, when you hit Control-c, it calls interrupt-thread with the
function invoking the debugger. With the change, interrupt-thread gets
handled promptly and the debugger is called, even if we are in an
infinite loop.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.9 KB
Line 
1/*
2 * Extensions.java
3 *
4 * Copyright (C) 2002-2007 Peter Graves
5 * $Id: Extensions.java 15584 2022-05-23 06:23:42Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33
34package org.armedbear.lisp;
35
36import static org.armedbear.lisp.Lisp.*;
37
38import java.io.File;
39import java.io.IOException;
40import java.util.*;
41
42public final class Extensions
43{
44  // ### *ed-functions*
45  public static final Symbol _ED_FUNCTIONS_ =
46    exportSpecial("*ED-FUNCTIONS*", PACKAGE_EXT,
47                  list(intern("DEFAULT-ED-FUNCTION", PACKAGE_SYS)));
48
49  // ### truly-the value-type form => result*
50  private static final SpecialOperator TRULY_THE = new truly_the();
51  private static class truly_the extends SpecialOperator {
52    truly_the() {
53      super("truly-the", PACKAGE_EXT, true, "type value");
54    }
55    @Override
56    public LispObject execute(LispObject args, Environment env)
57    {
58      if (args.length() != 2)
59        return error(new WrongNumberOfArgumentsException(this, 2));
60      return eval(args.cadr(), env, LispThread.currentThread());
61    }
62  }
63
64  // ### neq
65  private static final Primitive NEQ = new neq();
66  private static class neq extends Primitive
67  {
68    neq() 
69    {
70      super(Symbol.NEQ, "obj1 obj2");
71    }
72    @Override
73    public LispObject execute(LispObject first, LispObject second)
74    {
75        return first != second ? T : NIL;
76    }
77  }
78
79  // ### memq item list => tail
80  private static final Primitive MEMQ = new memq();
81  private static class memq extends Primitive
82  {
83    memq() 
84    {
85      super(Symbol.MEMQ, "item list");
86    }
87    @Override
88    public LispObject execute(LispObject item, LispObject list)
89    {
90      while (list instanceof Cons)
91        {
92          if (item == ((Cons)list).car)
93            return list;
94          list = ((Cons)list).cdr;
95        }
96      if (list != NIL)
97        type_error(list, Symbol.LIST);
98      return NIL;
99    }
100  }
101
102  // ### memql item list => tail
103  private static final Primitive MEMQL = new memql();
104  private static class memql extends Primitive
105  {
106    memql() {
107      super(Symbol.MEMQL, "item list");
108    }
109    @Override
110    public LispObject execute(LispObject item, LispObject list)
111    {
112      while (list instanceof Cons)
113        {
114          if (item.eql(((Cons)list).car))
115            return list;
116          list = ((Cons)list).cdr;
117        }
118      if (list != NIL)
119        type_error(list, Symbol.LIST);
120      return NIL;
121    }
122  }
123
124  // ### adjoin-eql item list => new-list
125  private static final Primitive ADJOIN_EQL = new adjoin_eql();
126  private static class adjoin_eql extends Primitive {
127    adjoin_eql() {
128      super(Symbol.ADJOIN_EQL, "item list");
129    }
130    @Override
131    public LispObject execute(LispObject item, LispObject list)
132    {
133      return memql(item, list) ? list : new Cons(item, list);
134    }
135  }
136
137  // ### special-variable-p
138  private static final Primitive SPECIAL_VARIABLE_P = new special_variable_p();
139  private static class special_variable_p extends Primitive {
140    special_variable_p() {
141      super("special-variable-p", PACKAGE_EXT, true);
142    }
143    @Override
144    public LispObject execute(LispObject arg)
145    {
146      return arg.isSpecialVariable() ? T : NIL;
147    }
148  }
149
150  // ### source symbol
151  private static final Primitive SOURCE = new source();
152  private static class source extends Primitive {
153    source() {
154      super("source", PACKAGE_EXT, true);
155    }
156    @Override
157    public LispObject execute(LispObject arg)
158    {
159      return get(arg, Symbol._SOURCE, NIL);
160    }
161  }
162
163  // ### source-file-position symbol
164  private static final Primitive SOURCE_FILE_POSITION = new source_file_position();
165  private static class source_file_position extends Primitive {
166    source_file_position() {
167      super("source-file-position", PACKAGE_EXT, true);
168    }
169    @Override
170    public LispObject execute(LispObject arg)
171    {
172      LispObject obj = get(arg, Symbol._SOURCE, NIL);
173      if (obj instanceof Cons)
174        return obj.cdr();
175      return NIL;
176    }
177  }
178
179  // XXX rename to something else as it doesn't always refer to a pathname.
180  public static final Primitive SOURCE_PATHNAME = new pf_source_pathname();
181  @DocString(
182    name="source-pathname",
183    args="symbol",
184    doc="Returns either the pathname corresponding to the file from which this symbol was compiled,"
185    + "or the keyword :TOP-LEVEL."
186  )
187  private static class pf_source_pathname extends Primitive {
188    pf_source_pathname() {
189      super("source-pathname", PACKAGE_EXT, true);
190    }
191    @Override
192    public LispObject execute(LispObject arg)
193    {
194      LispObject obj = get(arg, Symbol._SOURCE, NIL);
195      if (obj instanceof Cons)
196        return obj.car();
197      return obj;
198    }
199  }
200
201  // ### exit
202  private static final Primitive EXIT = new exit();
203  private static class exit extends Primitive {
204    exit() {
205      super("exit", PACKAGE_EXT, true, "&key status");
206    }
207    @Override
208    public LispObject execute()
209    {
210      throw new ProcessingTerminated();
211    }
212    @Override
213    public LispObject execute(LispObject first, LispObject second)
214     
215    {
216      int status = 0;
217      if (first == Keyword.STATUS)
218        {
219          if (second instanceof Fixnum)
220            status = ((Fixnum)second).value;
221        }
222      throw new ProcessingTerminated(status);
223    }
224  }
225
226  // ### quit
227  private static final Primitive QUIT = new quit();
228  private static class quit extends Primitive {
229    quit() {
230      super("quit", PACKAGE_EXT, true, "&key status");
231    }
232    @Override
233    public LispObject execute()
234    {
235      ((Stream)Symbol.STANDARD_OUTPUT.getSymbolValue())._finishOutput();
236      ((Stream)Symbol.ERROR_OUTPUT.getSymbolValue())._finishOutput();
237      throw new ProcessingTerminated();
238    }
239    @Override
240    public LispObject execute(LispObject first, LispObject second)
241    {
242      int status = 0;
243      if (first == Keyword.STATUS)
244        {
245          if (second instanceof Fixnum)
246            status = ((Fixnum)second).value;
247        }
248      throw new ProcessingTerminated(status);
249    }
250  }
251
252  // ### dump-java-stack
253  private static final Primitive DUMP_JAVA_STACK = new dump_java_stack();
254  private static class dump_java_stack extends Primitive {
255    dump_java_stack() {
256      super("dump-java-stack", PACKAGE_EXT, true);
257    }
258    @Override
259    public LispObject execute()
260    {
261      Thread.dumpStack();
262      return LispThread.currentThread().nothing();
263    }
264  }
265
266  public static final Primitive MAKE_TEMP_FILE = new make_temp_file();
267  @DocString(name="make-temp-file",
268             doc="Create and return the pathname of a previously non-existent file.",
269             args="&key prefix suffix")
270  private static class make_temp_file extends Primitive { 
271    make_temp_file() {
272      super("make-temp-file", PACKAGE_EXT, true, "&key prefix suffix");
273    }
274    @Override
275    public LispObject execute(LispObject ... args)
276    {
277      String prefix = "abcl";
278      String suffix = null; 
279      if ( args.length % 2 != 0) {
280        error(new WrongNumberOfArgumentsException("Expecting an even number of arguments including keywords."));
281      }
282
283      for (int i = 0; i < args.length; i++ ) {
284        if (args[i].SYMBOLP() != NIL) {
285          if (args[i].equals(Keyword.PREFIX)) {
286            String specifiedPrefix = args[i + 1].getStringValue();
287            if (specifiedPrefix != null) {
288              if (specifiedPrefix.equals(NIL.getStringValue())) {
289                error (new TypeError("Cannot create temporary file with NIL prefix."));
290              }
291              prefix = specifiedPrefix;
292              i += 1;
293            }
294          } else if (args[i].equals(Keyword.SUFFIX)) {
295            String specifiedSuffix = args[i + 1].getStringValue();
296            if (specifiedSuffix != null) {
297              if (specifiedSuffix.equals(NIL.getStringValue())) {
298                suffix =null;
299              } else {
300                suffix = specifiedSuffix;
301              }
302              i += 1;
303            }
304          }
305        } else {
306          error(new TypeError("Expected matching keyword argument.", args[i], Keyword.PREFIX.classOf()));
307        }
308      }
309      return createTempFile(prefix, suffix);
310    }
311
312    @Override
313    public LispObject execute() {
314      return createTempFile("abcl", null);
315    }
316
317    private LispObject createTempFile(String prefix, String suffix) {
318      try {
319        File file = File.createTempFile(prefix, suffix, null);
320        if (file != null)
321          return Pathname.create(file.getPath());
322      } catch (IllegalArgumentException e) {
323        // "Failed to create temporary file due to argument problems."
324        error(new JavaException(e));
325      } catch (SecurityException e) {
326        //"Failed to create problem due to problems with JVM SecurityManager."
327        error(new JavaException(e));
328      } catch (IOException e) {
329        // "Failed to create temporary file."
330        error(new JavaException(e));
331      }
332      return NIL;
333    }
334  }
335
336  public static final Primitive MAKE_TEMP_DIRECTORY = new make_temp_directory();
337  @DocString(name="make-temp-directory",
338             doc="Create and return the pathname of a previously non-existent directory.")
339  private static class make_temp_directory extends Primitive { 
340    make_temp_directory() {
341      super("make-temp-directory", PACKAGE_EXT, true, "");
342    }
343    @Override
344    public LispObject execute()
345    {
346      try {
347        File dir = File.createTempFile("abcl", null);
348        dir.delete();
349        if (dir.mkdirs()) {
350          return Pathname.create(dir + "/");
351        }
352      } catch (Throwable t) {
353        Debug.trace(t);
354      }
355      return NIL;
356    }
357  }
358
359  // ### interrupt-lisp
360  private static final Primitive INTERRUPT_LISP = new interrupt_lisp();
361  private static class interrupt_lisp extends Primitive {
362    interrupt_lisp() {
363      super("interrupt-lisp", PACKAGE_EXT, true, "");
364    }
365    @Override
366    public LispObject execute(LispObject[] args)
367    {
368      if (args.length < 1)
369        return error(new WrongNumberOfArgumentsException(this, 1, -1));
370      final LispThread thread;
371      if (args[0] instanceof LispThread) {
372        thread = (LispThread) args[0];
373      }
374      else {
375        return type_error(args[0], Symbol.THREAD);
376      }
377      setInterrupted(thread,true); // engage the compiler-insert check Lisp.interrupted/Lisp.handleInterrupts mechanism
378      return T;
379    }
380  }
381
382  // ### getenv variable => string
383  private static final Primitive GETENV = new getenv();
384  private static class getenv extends Primitive
385  {
386    getenv() 
387    {
388      super("getenv", PACKAGE_EXT, true, "variable",
389             "Return the value of the environment VARIABLE if it exists, otherwise return NIL.");
390    }
391    @Override
392    public LispObject execute(LispObject arg)
393    {
394      AbstractString string;
395      if (arg instanceof AbstractString) {
396        string = (AbstractString) arg;
397      } else
398        return type_error(arg, Symbol.STRING);
399      String result = System.getenv(string.getStringValue());
400      if (result != null)
401        return new SimpleString(result);
402      else
403        return NIL;
404    }
405  }
406
407  // ### getenv-all variable => string
408  private static final Primitive GETENV_ALL = new getenv_all();
409  private static class getenv_all extends Primitive
410  {
411    getenv_all() 
412    {
413      super("getenv-all", PACKAGE_EXT, true, "variable",
414             "Returns all environment variables as an alist containing (name . value)");
415    }
416    @Override
417    public LispObject execute()
418    {
419      Cons result = new Cons(NIL);
420      Map<String, String> env = System.getenv();
421      for (Map.Entry<String, String> entry : env.entrySet()) {
422          Cons entryPair = new Cons(new SimpleString(entry.getKey()), 
423                                    new SimpleString(entry.getValue()));
424          result = new Cons(entryPair, result);
425      }
426      return result;
427    }
428  }
429}
Note: See TracBrowser for help on using the repository browser.