random-state.net

Nikodemus Siivola

<< next | top | previous >>

Life After Heap Exhaustion #
hacking, June 4th 2006

SBCL has historically had only one way of dealing with heap exhaustion: instant death.

This is about to change:

(defvar *list* nil)
(defun cram (n) 
  (push (make-array n :element-type '(unsigned-byte 8)) *list*))

;;;; Allocate 10Mb chunks in a loop, and see what happens...
* (loop (cram (* 10 1024 1024))) 
Heap exhausted during allocation: 8589312 bytes available, 10485768 requested.

debugger invoked on a SB-KERNEL::HEAP-EXHAUSTED-ERROR in thread
#<THREAD "initial thread" {A68D5B9}>:
  Heap exhausted: 8589312 bytes available, 10485768 requested. PROCEED WITH CAUTION!

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL::HEAP-EXHAUSTED-ERROR 8589312 10485768)

;;;; Kazonk! An allocation failed. Let's see what the backtrace looks like...

0] ba 10

0: (SB-KERNEL::HEAP-EXHAUSTED-ERROR 8589312 10485768)
1: ("foreign function: call_into_lisp")
2: ("foreign function: funcall2")
3: ("foreign function: gc_find_freeish_pages")
4: ("foreign function: gc_alloc_large")
5: ("foreign function: alloc")
6: ("foreign function: alloc_overflow_ecx")
7: (CRAM 10485760)
8: (NIL)
9: (SB-INT:EVAL-IN-LEXENV (LOOP (CRAM (* 10 1024 1024))) #)

;;;; Presto! Selecting the ABORT restart gives up the failing allocation 
;;;; — and are still alive and well.
0] 0

* (length *list*)
48

;;;; Let's try with smaller chunks
* (loop (cram (* 1024 1024)))
Heap exhausted during allocation: 167936 bytes available, 1048584 requested.

debugger invoked on a SB-KERNEL::HEAP-EXHAUSTED-ERROR in thread
#<THREAD "initial thread" {A68D5B9}>:
  Heap exhausted: 167936 bytes available, 1048584 requested. PROCEED WITH CAUTION!

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL::HEAP-EXHAUSTED-ERROR 167936 1048584)
0] 0

;;;; We managed to allocate a few more, as expected.
* (length *list*)
56

;;;; ...let's try smallish chunks instead of these megabyte monsters...
* (loop  (cram (* 8 1024)))
Heap exhausted during allocation: 8120 bytes available, 8200 requested.

debugger invoked on a SB-KERNEL::HEAP-EXHAUSTED-ERROR in thread
#<THREAD "initial thread" {A68D5B9}>:
  Heap exhausted: 8120 bytes available, 8200 requested. PROCEED WITH CAUTION!

Heap exhausted during allocation: 112 bytes available, 528 requested.
Heap exhausted during allocation: 0 bytes available, 8 requested.
 Gen StaPg UbSta LaSta LUbSt Boxed Unboxed LB   LUB  !move  Alloc  Waste   Trig    WP  GCs Mem-age
   0: 131071     0 131063     0   201     0  2089  7683  2563 40785336 64072 33464640    0   1  0.0000
   1:     0     0     0     0     3     0     0 46098  2563 188751280 78416 190751280    1   1  0.0000
   2:  5863  5853     0     0    75     3     0 69147  2605 283429280 116320  2000000   29   0  0.0000
   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  5771     0     0     0     0 23638016     0  2000000 5670   0  0.0000
   Total bytes allocated=536603912
fatal error encountered in SBCL pid 3613(tid 3085402240):
Heap exhausted, game over.
LDB monitor

;;;; OOOPS! What happened? We ran totally out of memory — and died — while 
;;;; printing out the debugger message, it seems. Note the nested calls to 
;;;; HEAP-EXHAUSTED-ERROR in the backtrace. The possibility of this happening is the
;;;; reason for the apparently redundant error messages printed to STDOUT that you
;;;; see before the debugger messages ("Heap exhausted during allocation: ...").
ldb> ba 35
Backtrace:
   0: Foreign function (null), fp = 0xb7a7e1b8, ra = 0x80557ab
   1: Foreign function ldb_monitor, fp = 0xb7a7e288, ra = 0x805531e
   2: Foreign function lose, fp = 0xb7a7e2a8, ra = 0x80523ee
   3: Foreign function gc_find_freeish_pages, fp = 0xb7a7e2d8, ra = 0x805e21b
   4: Foreign function (null), fp = 0xb7a7e308, ra = 0x806064d
   5: Foreign function alloc, fp = 0xb7a7e338, ra = 0x805f8db
   6: Foreign function alloc_overflow_edx, fp = 0xb7a7e3b4, ra = 0x806283f
   7: (SB-C::TL-XEP COMMON-LISP::INVOKE-DEBUGGER)
   8: (SB-C::TL-XEP COMMON-LISP::ERROR)
   9: (SB-C::TL-XEP SB-KERNEL::HEAP-EXHAUSTED-ERROR)
  10: Foreign function call_into_lisp, fp = 0xb7a7e490, ra = 0x8062651
  11: Foreign function funcall2, fp = 0xb7a7e4b0, ra = 0x805cf1d
  12: Foreign function gc_find_freeish_pages, fp = 0xb7a7e4e0, ra = 0x805e251
  13: Foreign function (null), fp = 0xb7a7e510, ra = 0x806064d
  14: Foreign function alloc, fp = 0xb7a7e540, ra = 0x805f8db
  15: Foreign fp = 0xb7a7e578, ra = 0x28ffff29
  16: (SB-C::TL-XEP SB-FORMAT::INTERPRET-FORMAT-LOGICAL-BLOCK)
  17: (SB-C::TL-XEP SB-FORMAT::<-FORMAT-DIRECTIVE-INTERPRETER)
  18: (SB-C::TL-XEP SB-FORMAT::INTERPRET-DIRECTIVE-LIST)
  19: (SB-C::HAIRY-ARG-PROCESSOR SB-FORMAT::%FORMAT)
  20: (SB-C::TL-XEP COMMON-LISP::FORMAT)
  21: (SB-C::TL-XEP SB-DEBUG::%INVOKE-DEBUGGER)
  22: (COMMON-LISP::LAMBDA ())
  23: (SB-C::TL-XEP SB-IMPL::CALL-WITH-SANE-IO-SYNTAX)
  24: (SB-C::TL-XEP SB-IMPL::%WITH-STANDARD-IO-SYNTAX)
  25: (SB-C::TL-XEP COMMON-LISP::INVOKE-DEBUGGER)
  26: (SB-C::TL-XEP COMMON-LISP::ERROR)
  27: (SB-C::TL-XEP SB-KERNEL::HEAP-EXHAUSTED-ERROR)
  28: Foreign function call_into_lisp, fp = 0xb7a7e9d4, ra = 0x8062651
  29: Foreign function funcall2, fp = 0xb7a7e9f4, ra = 0x805cf1d
  30: Foreign function gc_find_freeish_pages, fp = 0xb7a7ea24, ra = 0x805e251
  31: Foreign function (null), fp = 0xb7a7ea54, ra = 0x806064d
  32: Foreign function alloc, fp = 0xb7a7ea84, ra = 0x805f8db
  33: Foreign function alloc_overflow_ecx, fp = 0xb7a7eb04, ra = 0x8062827
  34: COMMON-LISP-USER::CRAM

So obviously this is by no means a full solution, but a damn sight better then nothing. If you allocate big objects this should work perfectly well, as there will still be plenty of memory left when the first allocation fails. However, if you manage to fill the heap close enough to the limit, then you are simply out of luck. Running out of heap during GC is also not dealt with.

I hope that this will be of use despite the limitations.

Coming soon to a CVS near you.