Revisit the blocking behavior of `malloc` and `calloc`. We have recently changed the blocking behavior of `malloc` and `calloc` in 3ff0dda87b07973e588cd7efa522c7de6e7fe2ca from a fully blocking behavior to an entirely non-blocking behavior. This change, although it addressed some problems, created new ones: we now have situations where malloc is returning `nullptr` because the quarantine is holding back memory which could be used for the allocation. In such cases, the allocation could be satistified with a small wait. This creates issues in the network stack. This indicates that neither fully blocking nor fully non-blocking is the right approach here. This commit proposes to immediately return `nullptr` if the heap or the quota is full (do not block), but block for a short timeout if the quarantine is holding back memory which could be used for the allocation. The short timeout can be configured through a macro, here set at 30 ticks which seems to be a reasonable time for the revoker to process the quarantine. Modify `MVM_CONTEXTUAL_MALLOC` to exhibit a similar behavior. Signed-off-by: Hugo Lefeuvre <hugo.lefeuvre@scisemi.com>
diff --git a/sdk/include/microvium/microvium_port.h b/sdk/include/microvium/microvium_port.h index 1b2e4b1..c18188a 100644 --- a/sdk/include/microvium/microvium_port.h +++ b/sdk/include/microvium/microvium_port.h
@@ -295,11 +295,15 @@ * * The `context` passed to these macros is whatever value that the host passes * to `mvm_restore`. It can be any value that fits in a pointer. + * + * Similarly to `malloc` and `calloc`, this will only ever + * block to wait for the quarantine to be processed. */ #define MVM_CONTEXTUAL_MALLOC(size, context) \ ({ \ - Timeout t = {0, 0}; \ - void *ret = heap_allocate(&t, context, size); \ + Timeout t = {0, MALLOC_WAIT_TICKS}; \ + void *ret = \ + heap_allocate(&t, context, size, AllocateWaitRevocationNeeded); \ if (!__builtin_cheri_tag_get(ret)) \ { \ ret = NULL; \
diff --git a/sdk/include/stdlib.h b/sdk/include/stdlib.h index de007dd..6955d23 100644 --- a/sdk/include/stdlib.h +++ b/sdk/include/stdlib.h
@@ -85,6 +85,17 @@ */ #define MALLOC_CAPABILITY STATIC_SEALED_VALUE(__default_malloc_capability) +#ifndef MALLOC_WAIT_TICKS +/** + * Define how long a call to `malloc` and `calloc` can block to fulfil an + * allocation. Regardless of this value, `malloc` and `calloc` will only ever + * block to wait for the quarantine to be processed. This means that, even with + * a non-zero value of `MALLOC_WAIT_TICKS`, `malloc` would immediately return + * if the heap or the quota is exhausted. + */ +# define MALLOC_WAIT_TICKS 30 +#endif + __BEGIN_DECLS static inline void __dead2 panic() { @@ -288,8 +299,9 @@ #ifndef CHERIOT_NO_AMBIENT_MALLOC static inline void *malloc(size_t size) { - Timeout t = {0, 0}; - void *ptr = heap_allocate(&t, MALLOC_CAPABILITY, size, AllocateWaitNone); + Timeout t = {0, MALLOC_WAIT_TICKS}; + void *ptr = + heap_allocate(&t, MALLOC_CAPABILITY, size, AllocateWaitRevocationNeeded); if (!__builtin_cheri_tag_get(ptr)) { ptr = NULL; @@ -298,9 +310,9 @@ } static inline void *calloc(size_t nmemb, size_t size) { - Timeout t = {0, 0}; - void *ptr = - heap_allocate_array(&t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitNone); + Timeout t = {0, MALLOC_WAIT_TICKS}; + void *ptr = heap_allocate_array( + &t, MALLOC_CAPABILITY, nmemb, size, AllocateWaitRevocationNeeded); if (!__builtin_cheri_tag_get(ptr)) { ptr = NULL;