Module Memprof_limits

Global memory limits, allocation limits, and cancellation of CPU-bound threads.

All the functions in this module are thread-safe.

Result of a task

type 'a result = ('a, exn) Stdlib.Result.t

Denotes the result of a task: a computation running in a thread under one of the limits described below. This result is either the return value produced from the successful completion of the task, given by Ok, or, in case the task has been interrupted due to a limit being reached, some exception given by Error.

This exception can be a (private) exception raised by Memprof-limits, or another exception (bug, uncaught exception, or another interrupt) that arose after the private exception was raised and before the result is produced. (If the task follows the guidelines for safely recovering from interrupts, this exception can be discarded.)

Once the task is interrupted, the private exception will keep arising regularly inside the task until a result is produced.

It is guaranteed that Error is produced if and only if an interrupt arose due to the corresponding limit being exceeded by the task. Any exception raised otherwise is unaffected and arises normally. Reaching a limit in some other thread, including threads created by the task, will not produce an Error.

Initialisation

val start_memprof_limits : unit -> unit

Turn on the memprof-limits engine until the program terminates, which should be done once. Simply doing so should have no impact on performance. All the functions below producing a result raise Stdlib.Failure if the engine is not started.

Raises Stdlib.Failure if the Memprof engine is already started.

Global memory limits

val limit_global_memory : (unit -> 'a) -> 'a result

Executes the task given by the argument under the threat of being interrupted if the memory use of the program exceeds the limit set through set_global_memory_limit. The memory use is given by the total memory allocated for the major heap.

If interrupted, the program is likely in a state where further checks of memory limits will result in further interrupts being raised in concurrent and subsequent tasks running under a global memory limit. You can either let other tasks be interrupted too until the situation is resolved by the program shutting down gracefully, or you can call Gc.compact () to attempt to free up space. The chance for a task to be interrupted relative to others is proportional to its allocation rate.

val set_global_memory_limit : int -> unit

Set the limit in kiB for the major heap word count over which one or more tasks executed with limit_global_memory will be interrupted. A negative value disables the limit.

Default: -1.

Allocation limits

val limit_allocations : limit:Stdlib.Int64.t -> (unit -> 'a) -> ('a * Stdlib.Int64.t) result

Executes the task given by the second argument under the threat of being interrupted if it allocates more than the limit. It counts allocations but not deallocations; as such it is a measure of work done. This measure is more portable and can be more suitable than time elapsed. The ~limit is expressed in thousands of words (kw) allocated.

Upon successful completion, the result is made of a pair of the return value of task, and the statistical approximation of the allocation count, expressed in kw allocated. The latter can be used to calibrate the chosen ~limit. Refer to the documentation for the accuracy of the counting method and for advice about good margin for ~limit.

If two calls to limit_allocations are nested, then allocations are accounted for both limits. In other words, a call to limit_allocations cannot be used to go beyond a limit already set in the current thread.

val max_allocation_limit : Stdlib.Int64.t

The maximal value for ~limit, which is equal to 9'223'372'036'854'775 kw.

Token limits

module Token : sig ... end

A flag that can be set atomically, and never unset.

val limit_with_token : token:Token.t -> (unit -> 'a) -> 'a result

Executes the task given by the second argument under the threat of being interrupted if the Token given as an argument is set by anyone.

This can be used to interrupt an allocating task at a distance, from a monitor thread or from a signal handler, in a way which is safe for Memprof-limits and for resources defined using Masking.with_resource. Note that a task that does not allocate (for instance blocking on IO) will not be interrupted.

Resource management

module Masking : sig ... end

Manage the masking of interrupts (asynchronous exceptions) arising from memprof callbacks.

module Resource_bind : sig ... end

Open Memprof_limits.Resource_bind to enable the let& binder for resources.

val is_interrupted : unit -> bool

Returns true if the current thread is being interrupted. That is, this returns true between the moment an interrupt is raised and the moment the corresponding task terminates, even if the exception is discarded by the task (in which case it is likely to arise again soon). Returns false otherwise.

This has two purposes:

  • inside the when clause of a catch-all exception handler, to avoid catching and discarding an interrupt,
  • during resource release, to implement state poisoning or transaction rollback upon failure, so as to ensure that inconsistent state does not leak from the interrupted task.

Profiling

module Memprof : sig ... end

Use this reimplementation of the Memprof interface if you need to profile a program that uses Memprof-limits.