Memory overflow

From the docs:

exn:fail:out-of-memory

Raised for an error due to insufficient memory, in cases where sufficient memory is at least available for raising the exception.

How can sufficient memory be available when the memory limit is exceeded?

Another question: When running DrRacket, I see from time to time the amount of memory displayed left below exceeding the memory limit as put under tab 'limit memory ...' without DrRacket complaining about insufficient memory.

How come?

Hi, @joskoot.

So, how I would interpret this, is that, the computer doesn't have enough memory to keep doing the thing you asked, but it does have enough memory to tell you that.* I could be wrong, though.

*In the context of the restriction imposed by Racket, I forget to say.

The memory usage displayed at the bottom, I have always assumed to be the sum total used by DrRacket and all its children, because as you say, it does "overshoot" what you might otherwise expect.

1 Like

My guess is that you try (vector 1000000) it can raise a out of memory exception, but the memory for the exception is only like 20 or 40 bytes.

Here's a rather straightforward example (using with-handlers to "prove" that we get the right exception; the normal REPL display is probably more useful).

(require racket/sandbox]
(define e
;; 1 MB limit
(parameterize ([sandbox-memory-limit 1])
(make-evaluator 'racket/base)))

(with-handlers ([exn:fail:out-of-memory? values])
(void (e '(make-vector 1000000))))
;; => (exn:fail:out-of-memory "out of memory" #<continuation-mark-set>)
3 Likes

@joskoot - it's actually pretty simple ... limits such as the "memory limit" in settings, or sandbox sizes, etc., really are only suggestions.

The way it works is that Racket looks at a VM's memory usage if and when that VM performs GC. The exception is thrown if the GC discovers that the VM's total usage exceeded the suggested limit. But note that Racket will use whatever it needs (up to the maximum available) and will only at the GC will it discover that your limit was passed.

Also note that if your program uses places and creates multiple VMs, any soft limits set by the program will be per-VM ... not global.

To really set enforceable memory limits you need to involve the OS. You can use ulimit on Unix or Linux, or job controls on Windows (if you can figure out how). Racket will then actually run out of usable RAM, and getting an exception will mean the program doesn't have enough space to continue running even after performing GC.

The difficulty here is that Racket's JIT compiled code counts against the process heap from the point of view of the OS. Racket itself considers code separate from the VM's data heap. You need to take code into account when you set your OS limits, else your program may run very slowly or not at all.

Aside: several times over many years (actually going back to PLT Scheme which preceded Racket) I have suggested that there should be an option to strictly limit the size of the VM heap. It never materialized.

Hope this helps.

2 Likes

In addition to checking after GC, there are checks at “certain large allocations”, as @benknoble's example illustrates.

The fundamental unit of memory accounting is the custodian, via custodian-limit-memory. Per the docs:

New memory allocation will be accounted to the running thread’s managing custodian. In other words, a custodian’s limit applies only to the allocation made by the threads that it manages. See also call-in-nested-thread for a simpler setup.

I can see that there is some interaction of places and memory limits, but neither the docs nor my own experiments leave me entirely clear how it works.

No, Racket does include code in memory counting. (Also, by default, Racket uses a fully ahead-of-time compiler with no JIT compilation.) However, for custodian-limit-memory to restrict code, you have to take care for the code to be attributable to the limited custodian in accounting.

2 Likes