mirror of
https://github.com/termux/termux-packages.git
synced 2025-05-11 20:13:22 +00:00
Patched `rts` heap reservation logic on Android:
* Need for patch
- There seems to be a bug on Android. Sometimes simple commands like `ghc --help` usage 90% - 100% cpu and hangs.
- It doesn't happen every time, but ~25% of the times (at least in my
testing).
* Cause of the bug
- The function `osReserveHeapMemory` tries to allocate virtual space starting
from `0x4200000000`. The allocated space has to start at an address >= this address.
- If the kernel doesn't allocate inside this space, it keeps on repeating the
`mmap` call with increasing starting point.
- Now, on Android the kernel sometimes return an address above (as in counting)
this `hint` address. It repeatedly returns the same address for subsequent calls.
- Thus, an infinite loop occurs.
References:
- 383be28ffd/rts/posix/OSMem.c (L461)
- https://github.com/termux/termux-packages/pull/22991#issuecomment-2759137291
* Solution (proposed by Robert Kirkman):
- It introduces a new helper function `osTryReserveHeapMemoryRecursive`.
This transforms the heap reservation logic into a recursive one.
- `osTryReserveHeapMemory()` is run multiple times without unmapping the
undesired addresses. Thus, forcing the kernel to map subsequent calls of
`mmap` to a new, unique address until an address above the `0x4200000000`
mark is obtained.
- After which each recursive call unmaps its undesired address before returning
the desired address (in order from last mapped to first mapped).
References:
- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14164
- https://github.com/termux/termux-packages/pull/22991#issuecomment-2761325484
Co-authored-by: Robert Kirkman <rkirkman@termux.dev>
Signed-off-by: Aditya Alok <alok@termux.dev>
133 lines
5.5 KiB
Diff
133 lines
5.5 KiB
Diff
From fdd0c67a38ef8435b9aa46c0d3c9d4460191cdcf Mon Sep 17 00:00:00 2001
|
|
From: Robert Kirkman <rkirkman@termux.dev>
|
|
Date: Fri, 28 Mar 2025 16:53:16 -0500
|
|
Subject: [PATCH] rts: reattempt heap reservation recursively before unmapping
|
|
addresses below the 8GB mark
|
|
|
|
This patch works around Android's mmap() occasionally repeatedly mapping the exact
|
|
same block of memory at an address below the 8GB mark that was just
|
|
unmapped, which would cause the 'ghc --help' command to fall into an infinite loop while
|
|
osTryReserveHeapMemory() repeatedly returned the same unwanted address. This
|
|
moves the heap reservation attempt logic into a recursive function that
|
|
runs osTryReserveHeapMemory() multiple times without unmapping the
|
|
undesired addresses, to force the mapping of new, unique addresses
|
|
until an address above the 8GB mark is obtained,
|
|
after which each recursive call unmaps its undesired address before
|
|
returning the desired address, in order from last mapped to first mapped.
|
|
|
|
First discussed here: https://github.com/termux/termux-packages/pull/22991
|
|
---
|
|
rts/posix/OSMem.c | 88 +++++++++++++++++++++++++----------------------
|
|
1 file changed, 46 insertions(+), 42 deletions(-)
|
|
|
|
diff --git a/rts/posix/OSMem.c b/rts/posix/OSMem.c
|
|
index 94c60f441ac9..2f1638bb5123 100644
|
|
--- a/rts/posix/OSMem.c
|
|
+++ b/rts/posix/OSMem.c
|
|
@@ -493,11 +493,53 @@ osTryReserveHeapMemory (W_ len, void *hint)
|
|
return start;
|
|
}
|
|
|
|
-void *osReserveHeapMemory(void *startAddressPtr, W_ *len)
|
|
+static void *
|
|
+osTryReserveHeapMemoryRecursive(W_ minimumAddress, W_ startAddress, int attempt, W_ *len)
|
|
{
|
|
- int attempt;
|
|
- void *at;
|
|
+ *len &= ~MBLOCK_MASK;
|
|
+
|
|
+ if (*len < MBLOCK_SIZE) {
|
|
+ // Give up if the system won't even give us 16 blocks worth of heap
|
|
+ barf("osReserveHeapMemory: Failed to allocate heap storage");
|
|
+ }
|
|
+
|
|
+ void *hint = (void*)(startAddress + attempt * BLOCK_SIZE);
|
|
+ void *at = osTryReserveHeapMemory(*len, hint);
|
|
+ attempt++;
|
|
+
|
|
+ if (at == NULL) {
|
|
+ // This means that mmap failed which we take to mean that we asked
|
|
+ // for too much memory. This can happen due to POSIX resource
|
|
+ // limits. In this case we reduce our allocation request by a
|
|
+ // fraction of the current size and try again.
|
|
+ //
|
|
+ // Note that the previously would instead decrease the request size
|
|
+ // by a factor of two; however, this meant that significant amounts
|
|
+ // of memory will be wasted (e.g. imagine a machine with 512GB of
|
|
+ // physical memory but a 511GB ulimit). See #14492.
|
|
+ *len -= *len / 8;
|
|
+ // debugBelch("Limit hit, reduced len: %zu\n", *len);
|
|
+ return osTryReserveHeapMemoryRecursive(minimumAddress, startAddress, attempt, len);
|
|
+ } else if ((W_)at >= minimumAddress) {
|
|
+ // Success! We were given a block of memory starting above the 8 GB
|
|
+ // mark, which is what we were looking for.
|
|
+
|
|
+ return at;
|
|
+ } else {
|
|
+ // We got addressing space but it wasn't above the 8GB mark.
|
|
+ // Try again recursively first, unmap after, because on aarch64 Android,
|
|
+ // sometimes mmap() will continuously map the same address regardless of
|
|
+ // the hint changing, if that address has already been unmapped.
|
|
+ void *next_at = osTryReserveHeapMemoryRecursive(minimumAddress, startAddress, attempt, len);
|
|
+ if (munmap(at, *len) < 0) {
|
|
+ sysErrorBelch("unable to release reserved heap");
|
|
+ }
|
|
+ return next_at;
|
|
+ }
|
|
+}
|
|
|
|
+void *osReserveHeapMemory(void *startAddressPtr, W_ *len)
|
|
+{
|
|
/* We want to ensure the heap starts at least 8 GB inside the address space,
|
|
since we want to reserve the address space below that address for code.
|
|
Specifically, we need to make sure that any dynamically loaded code will
|
|
@@ -585,45 +627,7 @@ void *osReserveHeapMemory(void *startAddressPtr, W_ *len)
|
|
}
|
|
#endif
|
|
|
|
- attempt = 0;
|
|
- while (1) {
|
|
- *len &= ~MBLOCK_MASK;
|
|
-
|
|
- if (*len < MBLOCK_SIZE) {
|
|
- // Give up if the system won't even give us 16 blocks worth of heap
|
|
- barf("osReserveHeapMemory: Failed to allocate heap storage");
|
|
- }
|
|
-
|
|
- void *hint = (void*)(startAddress + attempt * BLOCK_SIZE);
|
|
- at = osTryReserveHeapMemory(*len, hint);
|
|
- if (at == NULL) {
|
|
- // This means that mmap failed which we take to mean that we asked
|
|
- // for too much memory. This can happen due to POSIX resource
|
|
- // limits. In this case we reduce our allocation request by a
|
|
- // fraction of the current size and try again.
|
|
- //
|
|
- // Note that the previously would instead decrease the request size
|
|
- // by a factor of two; however, this meant that significant amounts
|
|
- // of memory will be wasted (e.g. imagine a machine with 512GB of
|
|
- // physical memory but a 511GB ulimit). See #14492.
|
|
- *len -= *len / 8;
|
|
- // debugBelch("Limit hit, reduced len: %zu\n", *len);
|
|
- } else if ((W_)at >= minimumAddress) {
|
|
- // Success! We were given a block of memory starting above the 8 GB
|
|
- // mark, which is what we were looking for.
|
|
-
|
|
- break;
|
|
- } else {
|
|
- // We got addressing space but it wasn't above the 8GB mark.
|
|
- // Try again.
|
|
- if (munmap(at, *len) < 0) {
|
|
- sysErrorBelch("unable to release reserved heap");
|
|
- }
|
|
- }
|
|
- attempt++;
|
|
- }
|
|
-
|
|
- return at;
|
|
+ return osTryReserveHeapMemoryRecursive(minimumAddress, startAddress, 0, len);
|
|
}
|
|
|
|
void osCommitMemory(void *at, W_ size)
|
|
--
|
|
GitLab
|