overflowcommon-lispheap-memorysbclland-of-lisp

Wumpus game's make-city-edges function causes heap overflow


Going through the Land of Lisp book, I managed to get to the Grand Theft Wumpus game, that has me define a make-city-edges function. When I try to run it however, SBCL hangs for a while before giving me a very nasty error saying

    Heap exhausted during garbage collection: 0 bytes available, 16 requested.
 Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB   LUB  !move  Alloc  Waste   Trig    WP  GCs Mem-age
   0:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   1:     0     0     0     0     0     0     0     0     0        0     0 10737418    0   0  0.0000
   2: 27757     0     0     0 19204    70     0    10    54 631392704 505408  2000000    0   0  0.9800
   3:     0     0     0     0     0     0     0     0     0        0     0  2000000    0   0  0.0000
   4:     0     0     0     0     0     0     0     0     0        0     0  2000000    0   0  0.0000
   5:     0     0     0     0     0     0     0     0     0        0     0  2000000    0   0  0.0000
   6:     0     0     0     0  1638   251     0     0     0 61898752     0  2000000 1523   0  0.0000
   Total bytes allocated    = 1073069936
   Dynamic-space-size bytes = 1073741824
GC control variables:
   *GC-INHIBIT* = true
   *GC-PENDING* = true
   *STOP-FOR-GC-PENDING* = false
fatal error encountered in SBCL pid 85448(tid 140735276667664):
Heap exhausted, game over.

Error opening /dev/tty: Device not configured
Welcome to LDB, a low-level debugger for the Lisp runtime environment.
ldb> 

I've triple-checked to see if I made any mistake, but I couldn't find any.

Here's the function causing the problem:

(defun make-city-edges ()
  (let* ((nodes (loop for i from 1 to *node-num*
                      collect i))
         (edge-list (connect-all-islands nodes (make-edge-list)))
         (cops (remove-if-not (lambda (x)
                                (zerop (random *cop-odds*)))
                              edge-list)))
    (add-cops (edges-to-alist edge-list) cops)))

[here] is the rest of the code if you want to have a look at the other functions, I added it to a GitHub Gist page since it would take up too much space in the question.

What can I do to resolve this? I'm using Emacs 24.4 (9.0) on OSX 10.9 with SLIME and SBCL 1.2.10 for the project.


Solution

  • In the linked code,

    (defun find-islands (nodes edge-list)
      "returns a list of nodes that aren't interconnected"
      (let ((islands nil))
        (labels ((find-island (nodes)
               (let* ((connected (get-connected (car nodes) edge-list))
                  (unconnected (set-difference nodes connected)))
             (push connected islands)
             (when connected
               (find-island unconnected)))))
          (find-island nodes))
        islands))
    

    (when connected should be (when unconnected.

    A few tips for debugging heap exhaustion:

    1. Check that your loops and recursions actually terminate. (That's what led us to this solution -- get-connected never returns nil, so find-island would recurse forever.)
    2. CL's trace can be useful, as well as the traditional adding of print statements.
    3. C-c C-c in SLIME after the program has run for a bit but before heap exhaustion might provide a useful backtrace.

    E.g. of the backtrace:

      0: ((:INTERNAL TRAVERSE GET-CONNECTED) NIL)
          Locals:
            NODE = NIL
            #:G11908 = ((2 . 21) (20 . 22) (22 . 20) (9 . 28) (28 . 9) (2 . 7) ...)
            EDGE-LIST = ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...)
            VISITED = (NIL)
      1: (GET-CONNECTED NIL ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...))
          Locals:
            NODE = NIL
            EDGE-LIST = ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...)
            VISITED = (NIL)
      2: ((:INTERNAL FIND-ISLAND FIND-ISLANDS) NIL)
          Locals:
            NODES = NIL
            ISLANDS = ((NIL) (NIL) (NIL) (NIL) (NIL) (NIL) ...)
            EDGE-LIST = ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...)
      3: (FIND-ISLANDS (1 2 3 4 5 6 ...) ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...))
          Locals:
            NODES = (1 2 3 4 5 6 ...)
            EDGE-LIST = ((8 . 3) (3 . 8) (18 . 7) (7 . 18) (26 . 23) (23 . 26) ...)
            ISLANDS = ((NIL) (NIL) (NIL) (NIL) (NIL) (NIL) ...)
    

    That might lead us to say "I didn't think a node would ever be nil, and islands being ((nil) (nil) (nil) ...) seems broken."