get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/1.2/patches/2225592/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 2225592,
    "url": "http://patchwork.ozlabs.org/api/1.2/patches/2225592/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/glibc/patch/b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com/",
    "project": {
        "id": 41,
        "url": "http://patchwork.ozlabs.org/api/1.2/projects/41/?format=api",
        "name": "GNU C Library",
        "link_name": "glibc",
        "list_id": "libc-alpha.sourceware.org",
        "list_email": "libc-alpha@sourceware.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": "",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com>",
    "list_archive_url": null,
    "date": "2026-04-21T08:39:12",
    "name": "[RFC,1/1] malloc: madvise interior free chunks above a threshold",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "02c4e6160e99c2e2864e44812331037afcbcc439",
    "submitter": {
        "id": 93202,
        "url": "http://patchwork.ozlabs.org/api/1.2/people/93202/?format=api",
        "name": "Xavier Roche",
        "email": "xavier.roche@algolia.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/glibc/patch/b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com/mbox/",
    "series": [
        {
            "id": 500760,
            "url": "http://patchwork.ozlabs.org/api/1.2/series/500760/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/glibc/list/?series=500760",
            "date": "2026-04-21T08:39:12",
            "name": "malloc: madvise interior free chunks above a threshold",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/500760/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/2225592/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/2225592/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "libc-alpha@sourceware.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@legolas.ozlabs.org",
            "libc-alpha@sourceware.org"
        ],
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=algolia.com header.i=@algolia.com header.a=rsa-sha256\n header.s=google header.b=K1XRfuO0;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)",
            "sourceware.org;\n\tdkim=pass (1024-bit key,\n unprotected) header.d=algolia.com header.i=@algolia.com header.a=rsa-sha256\n header.s=google header.b=K1XRfuO0",
            "sourceware.org;\n dmarc=pass (p=reject dis=none) header.from=algolia.com",
            "sourceware.org; spf=pass smtp.mailfrom=algolia.com",
            "server2.sourceware.org;\n arc=none smtp.remote-ip=2a00:1450:4864:20::330"
        ],
        "Received": [
            "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g0G4f3m7bz1yCv\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 18:39:46 +1000 (AEST)",
            "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id AEE3D4BA9020\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 08:39:44 +0000 (GMT)",
            "from mail-wm1-x330.google.com (mail-wm1-x330.google.com\n [IPv6:2a00:1450:4864:20::330])\n by sourceware.org (Postfix) with ESMTPS id 4F9204BA901A\n for <libc-alpha@sourceware.org>; Tue, 21 Apr 2026 08:39:16 +0000 (GMT)",
            "by mail-wm1-x330.google.com with SMTP id\n 5b1f17b1804b1-4891b02a0acso2588965e9.3\n for <libc-alpha@sourceware.org>; Tue, 21 Apr 2026 01:39:16 -0700 (PDT)",
            "from xavier-thinkpad.. ([2a01:e0a:1048:49a0:2de4:14b:d3be:34fd])\n by smtp.gmail.com with ESMTPSA id\n 5b1f17b1804b1-488fc17f642sm322503005e9.5.2026.04.21.01.39.13\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 21 Apr 2026 01:39:14 -0700 (PDT)"
        ],
        "DKIM-Filter": [
            "OpenDKIM Filter v2.11.0 sourceware.org AEE3D4BA9020",
            "OpenDKIM Filter v2.11.0 sourceware.org 4F9204BA901A"
        ],
        "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org 4F9204BA901A",
        "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org 4F9204BA901A",
        "ARC-Seal": "i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776760756; cv=none;\n b=Ey+4sdgpho7ZrCvwesMhWZVsu8hhZi15e4BwZIQL6HnTihPEUAts090ICI58cRxE+xq1jO/dk1KmWObmqDfbCq6NwYdjHWApVr4oM6Z38T+/dPXaewqvsRUKKs5fHACgI9d3HHchiop3dexO9qtmTWxmlJwVzGcw2G1ItlkSwek=",
        "ARC-Message-Signature": "i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776760756; c=relaxed/simple;\n bh=3k4W6ey+nA0vELAvxA3MFaYO98jMPKhbdw6ODPBAf3U=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=UsX6t0p+PLLuzG/+uC3hTIH3nFBo1OyMQlgX/JF+WhJsnHpix49ZuPOv20Oz2Clzm9k2aDGlYDSlBXHs443uGOPws7Ao1ZB6+oo5ebjlzRfgmq9KXG3C36pAYU124mqiG77tnsjPsh1I7CdLIly6htjZdQ+bi06eptWJALRp+Ck=",
        "ARC-Authentication-Results": "i=1; server2.sourceware.org",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=algolia.com; s=google; t=1776760755; x=1777365555; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=KBzkjlCZ6F7UM3zxSSnv4swwVMAHvO4G1orT1kQ9vII=;\n b=K1XRfuO0NK/jp6ZZX0XK/CZdypfl2hUED10ZxkxNSlAZKrpHjUOyUwplePS4ZYeBnB\n wn52mB9vCpsZ09mxwaWju01qbzssAF9hMM8VS7WdxxCtm1S/7gPBJxKnZ0pojBcJ0Jms\n 1N8Cqg1Jox4vyL2EmM+MPJYjyd8nAeJBaA66Q=",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776760755; x=1777365555;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=KBzkjlCZ6F7UM3zxSSnv4swwVMAHvO4G1orT1kQ9vII=;\n b=KgxH2yqenwufeSTSc/TS6Ibal8wMFRU5TFczULYtFRcJPeIgu3zGqc2UJq0LzKhWpe\n PAdqYfjheqZfiX8n78wDQbFUvRtCj02N3xSKtsEhaIJc/Lb0UHS67/G/d0Rz3mrDPdmF\n US+TS/oG8a7aJHa/iIIKqIFODQRbZZuGexOgkGSd8sfdbvJxnyj9HFxs4+h6AgVC+nLK\n QXq0HYDvF1rhSNMuBNqGpf2haRx2LOdgF2baUupQoXcniIlM6VxSBnlaR0Z7flTSokEH\n ff9tM59rXfgyWQk2H38f3U/gsQ9ry5iYKh9is5Mq7YzvXQC9O7xhKJYK99TtqUs/P0eQ\n btxA==",
        "X-Gm-Message-State": "AOJu0YzRvxqK9xwDlp5rDlxLhdz0mRoIlKhwFWwqpOYHpEa0KfH7kKXn\n gug8cid6BYfsR7pp0OmfG3k8Y0qNWOo60fjXhxA65HbVcft9++7nLx4iIO0A8z4j8AOamdAeFgw\n 7iz5/",
        "X-Gm-Gg": "AeBDietq5xudFOkXpv4ECb4ZyoI5R2O3bNbOmteZ30VGqiyf1g75aqKmRzgps3w2Apb\n Ana3MkZEBOpiC45LdQessMLO0fVYZ4dejrpa+bfTXt7VDaKLzDb8Wpjss3FCE4JJHLZmagDvNXS\n 8mIhobkpr3mQB2kDm1W6aT/UQwcg4PjzEBUqy9DsYBPkH8nYJt46nBbAFrrGbY07oWzJ8WOp5J+\n RCro1dMxkAOn+ObEHJvM1lL7///T8bzyhUssQkS8/Rvb9g9tbM622sO7kylcyCtia2J3ArL/Upa\n J2tPhUp7bqhLeWnTjdnz9lftjlu8Fr2mDhOGBSeHzyC2ojmyZI/sjMOqjgo9IDRgVTo3mWZj9QQ\n /jHqMKyn2aPHc4+xwiclwy8nvecJ768Pwoy8qzuziBhQOXKGCn3ewtmnAK2tLmQO8yxv9bhTmd1\n Zp9qgWqXPNlnHdCpna7ULUhUdY6qdtBWP6eONaxIDmAl/KiWVpkw==",
        "X-Received": "by 2002:a05:600c:4247:b0:489:1dc6:d6e with SMTP id\n 5b1f17b1804b1-4891dc60e71mr27937035e9.1.1776760754649;\n Tue, 21 Apr 2026 01:39:14 -0700 (PDT)",
        "From": "Xavier Roche <xavier.roche@algolia.com>",
        "To": "libc-alpha@sourceware.org",
        "Cc": "wilco.dijkstra@arm.com,\n\tadhemerval.zanella@linaro.org",
        "Subject": "[RFC PATCH 1/1] malloc: madvise interior free chunks above a\n threshold",
        "Date": "Tue, 21 Apr 2026 10:39:12 +0200",
        "Message-ID": "\n <b8462f215aa1e4f7ad5adbba9337d57cc3ee4a7b.1776760573.git.xavier.roche@algolia.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<cover.1776760573.git.xavier.roche@algolia.com>",
        "References": "<cover.1776760573.git.xavier.roche@algolia.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "libc-alpha@sourceware.org",
        "X-Mailman-Version": "2.1.30",
        "Precedence": "list",
        "List-Id": "Libc-alpha mailing list <libc-alpha.sourceware.org>",
        "List-Unsubscribe": "<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>",
        "List-Archive": "<https://sourceware.org/pipermail/libc-alpha/>",
        "List-Post": "<mailto:libc-alpha@sourceware.org>",
        "List-Help": "<mailto:libc-alpha-request@sourceware.org?subject=help>",
        "List-Subscribe": "<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>",
        "Errors-To": "libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"
    },
    "content": "Since glibc 2.26 introduced tcache, free() no longer returns\nphysical pages to the kernel for chunks that sit in the interior\nof an arena heap.  malloc_trim(0) still recovers the memory, but\nfree() itself cannot.  A bisect between 2.25 and 2.26 shows a 4x\nRSS regression on a reproducer that interleaves long-lived index\ndata with short-lived filler allocations.\n\nExtend _int_free_maybe_trim to madvise the page-aligned interior\nof consolidated chunks >= ATTEMPT_TRIMMING_THRESHOLD (64 KB),\nusing the same page-alignment logic as mtrim.  To avoid a flood\nof madvise calls when many small frees merge into one chunk (the\nconcern raised by Wilco on BZ #33886 comment 10), gate the call\non the caller's pre-consolidation size: direct madvise when that\nsize already covers a full page, accumulator-batched madvise\n(fires every MADVISE_PURGE_THRESHOLD = 256 KB of sub-page frees)\notherwise.\n\nThe per-arena accumulator is read and written only under the\narena mutex, which is held by both call sites\n(_int_free_merge_chunk and _int_memalign).\n\nAdvice type is MADV_FREE for moderate chunks and MADV_DONTNEED\nfor chunks >= 2 * ATTEMPT_TRIMMING_THRESHOLD.  MADV_DONTNEED\nmatches the existing mtrim behavior and gives operators the\npredictable RSS drop they expect; MADV_FREE amortises the\nper-page cost for moderate chunks that are likely to be reused.\n\nReproducer (tst-madvise-threshold): 16 threads, 256 MB live\ndata, 10 GB short-lived churn.\n\n  RSS after free, before:  1247 MB\n  RSS after free, after:    296 MB\n  Runtime overhead:        +0.16 s on a tight malloc/free loop.\n\nRelated: BZ #15321, #18910, #27976, #33886.\n\nSigned-off-by: Xavier Roche <xavier.roche@algolia.com>\n---\n malloc/Makefile                |   1 +\n malloc/malloc.c                |  61 +++++++++++++---\n malloc/tst-madvise-threshold.c | 128 +++++++++++++++++++++++++++++++++\n 3 files changed, 182 insertions(+), 8 deletions(-)\n create mode 100644 malloc/tst-madvise-threshold.c",
    "diff": "diff --git a/malloc/Makefile b/malloc/Makefile\nindex fef5021298..d663454e57 100644\n--- a/malloc/Makefile\n+++ b/malloc/Makefile\n@@ -39,6 +39,7 @@ tests := \\\n   tst-free-sized-trace \\\n   tst-interpose-nothread \\\n   tst-interpose-thread \\\n+  tst-madvise-threshold \\\n   tst-mallinfo2 \\\n   tst-malloc \\\n   tst-malloc-alternate-path \\\ndiff --git a/malloc/malloc.c b/malloc/malloc.c\nindex 57b58382b1..d20e22a463 100644\n--- a/malloc/malloc.c\n+++ b/malloc/malloc.c\n@@ -1029,7 +1029,8 @@ static void _int_free_merge_chunk (mstate, mchunkptr, INTERNAL_SIZE_T);\n static INTERNAL_SIZE_T _int_free_create_chunk (mstate,\n \t\t\t\t\t       mchunkptr, INTERNAL_SIZE_T,\n \t\t\t\t\t       mchunkptr, INTERNAL_SIZE_T);\n-static void _int_free_maybe_trim (mstate, INTERNAL_SIZE_T);\n+static void _int_free_maybe_trim (mstate, mchunkptr, INTERNAL_SIZE_T,\n+\t\t\t\t  INTERNAL_SIZE_T);\n static void*  _int_realloc(mstate, mchunkptr, INTERNAL_SIZE_T,\n \t\t\t   INTERNAL_SIZE_T);\n static void*  _int_memalign(mstate, size_t, size_t);\n@@ -1691,6 +1692,18 @@ unlink_chunk (mstate av, mchunkptr p)\n \n #define ATTEMPT_TRIMMING_THRESHOLD  (65536UL)\n \n+/* Cumulative bytes freed per arena before triggering madvise for\n+   sub-page frees that individually skip the page-size gate.  */\n+\n+#define MADVISE_PURGE_THRESHOLD  (4 * ATTEMPT_TRIMMING_THRESHOLD)\n+\n+/* Consolidated chunks above this size use MADV_DONTNEED (immediate\n+   page release) instead of MADV_FREE (lazy release).  Large chunks\n+   are unlikely to be reused at the same size, and the immediate RSS\n+   reduction is worth the higher per-call cost.  */\n+\n+#define MADVISE_DONTNEED_THRESHOLD  (2 * ATTEMPT_TRIMMING_THRESHOLD)\n+\n /*\n    NONCONTIGUOUS_BIT indicates that MORECORE does not return contiguous\n    regions.  Otherwise, contiguity is exploited in merging together,\n@@ -1747,6 +1760,9 @@ struct malloc_state\n   /* Memory allocated from the system in this arena.  */\n   INTERNAL_SIZE_T system_mem;\n   INTERNAL_SIZE_T max_system_mem;\n+\n+  /* Cumulative sub-page bytes freed since the last madvise.  */\n+  INTERNAL_SIZE_T madvise_accumulator;\n };\n \n struct malloc_par\n@@ -4315,6 +4331,7 @@ _int_free_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size, int have_lock)\n static void\n _int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)\n {\n+  INTERNAL_SIZE_T orig_size = size;\n   mchunkptr nextchunk = chunk_at_offset(p, size);\n \n   check_inuse_chunk (av, p);\n@@ -4352,7 +4369,7 @@ _int_free_merge_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size)\n \n   /* Write the chunk header, maybe after merging with the following chunk.  */\n   size = _int_free_create_chunk (av, p, size, nextchunk, nextsize);\n-  _int_free_maybe_trim (av, size);\n+  _int_free_maybe_trim (av, p, orig_size, size);\n }\n \n /* Create a chunk at P of SIZE bytes, with SIZE potentially increased\n@@ -4432,14 +4449,41 @@ _int_free_create_chunk (mstate av, mchunkptr p, INTERNAL_SIZE_T size,\n }\n \n /* If the total unused topmost memory exceeds trim threshold, ask malloc_trim\n-   to reduce top.  */\n+   to reduce top.  Also release physical pages from interior free chunks.  */\n static void\n-_int_free_maybe_trim (mstate av, INTERNAL_SIZE_T size)\n+_int_free_maybe_trim (mstate av, mchunkptr p,\n+\t\t      INTERNAL_SIZE_T orig_size, INTERNAL_SIZE_T size)\n {\n-  /* We don't want to trim on each free.  As a compromise, trimming is attempted\n-     if ATTEMPT_TRIMMING_THRESHOLD is reached.  */\n   if (size >= ATTEMPT_TRIMMING_THRESHOLD)\n     {\n+      /* Release interior pages of the consolidated chunk.  MADV_FREE\n+\t for moderate chunks (pages kept until kernel pressure, no\n+\t re-fault on quick reuse).  MADV_DONTNEED for large chunks\n+\t (immediate RSS reduction, worth the cost at this size).\n+\t Sub-page frees accumulate until MADVISE_PURGE_THRESHOLD.  */\n+      size_t ps = GLRO (dl_pagesize);\n+      bool do_madvise\n+\t= (orig_size >= ps + sizeof (struct malloc_chunk));\n+      if (!do_madvise)\n+\t{\n+\t  av->madvise_accumulator += orig_size;\n+\t  if (av->madvise_accumulator >= MADVISE_PURGE_THRESHOLD)\n+\t    do_madvise = true;\n+\t}\n+      if (do_madvise)\n+\t{\n+\t  char *paligned = PTR_ALIGN_UP ((char *) p\n+\t\t\t\t\t + sizeof (struct malloc_chunk), ps);\n+\t  char *pend = PTR_ALIGN_DOWN ((char *) p + size, ps);\n+\t  if (pend > paligned)\n+\t    {\n+\t      int advice = (size >= MADVISE_DONTNEED_THRESHOLD)\n+\t\t\t   ? MADV_DONTNEED : MADV_FREE;\n+\t      __madvise (paligned, pend - paligned, advice);\n+\t      av->madvise_accumulator = 0;\n+\t    }\n+\t}\n+\n       if (av == &main_arena)\n \t{\n #ifndef MORECORE_CANNOT_TRIM\n@@ -4646,9 +4690,10 @@ _int_memalign (mstate av, size_t alignment, size_t bytes)\n       mchunkptr nextchunk = chunk_at_offset (p, size);\n       mchunkptr remainder = chunk_at_offset (p, nb);\n       set_head_size (p, nb);\n-      size = _int_free_create_chunk (av, remainder, size - nb, nextchunk,\n+      INTERNAL_SIZE_T remainder_size = size - nb;\n+      size = _int_free_create_chunk (av, remainder, remainder_size, nextchunk,\n \t\t\t\t     chunksize (nextchunk));\n-      _int_free_maybe_trim (av, size);\n+      _int_free_maybe_trim (av, remainder, remainder_size, size);\n     }\n \n   check_inuse_chunk (av, p);\ndiff --git a/malloc/tst-madvise-threshold.c b/malloc/tst-madvise-threshold.c\nnew file mode 100644\nindex 0000000000..964ec0ba30\n--- /dev/null\n+++ b/malloc/tst-madvise-threshold.c\n@@ -0,0 +1,128 @@\n+/* Test for the glibc.malloc.madvise_threshold tunable.\n+\n+   Verify that when the tunable is set, free() returns physical memory\n+   to the OS for interior free chunks (not just the top chunk).\n+\n+   Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <malloc.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <string.h>\n+#include <unistd.h>\n+#include <support/check.h>\n+\n+/* Read RSS from /proc/self/statm in bytes.  */\n+static long\n+get_rss (void)\n+{\n+  FILE *f = fopen (\"/proc/self/statm\", \"r\");\n+  if (f == NULL)\n+    FAIL_UNSUPPORTED (\"/proc/self/statm not available\");\n+\n+  long pages;\n+  if (fscanf (f, \"%*d %ld\", &pages) != 1)\n+    {\n+      fclose (f);\n+      FAIL_UNSUPPORTED (\"cannot parse /proc/self/statm\");\n+    }\n+  fclose (f);\n+  return pages * sysconf (_SC_PAGESIZE);\n+}\n+\n+/* Number of pinning (index) allocations.  These stay alive and\n+   prevent heap segments from being unmapped.  */\n+#define N_INDEX 200\n+\n+/* Number of filler (query) allocations per round.  These are freed\n+   and should have their physical pages returned when the tunable\n+   is set.  */\n+#define N_FILLER 2000\n+\n+/* Size of each filler allocation.  Must be below mmap threshold\n+   so allocations go through arenas.  */\n+#define FILLER_SIZE (64 * 1024)\n+\n+/* Size of each index allocation.  Small enough to fit between\n+   filler chunks.  */\n+#define INDEX_SIZE 1024\n+\n+static int\n+do_test (void)\n+{\n+  void *index_ptrs[N_INDEX];\n+  void *filler_ptrs[N_FILLER];\n+\n+  /* Phase 1: Allocate index and filler data interleaved.\n+     This creates the fragmentation pattern: index chunks scattered\n+     among filler chunks in the arena heaps.  */\n+  int idx = 0;\n+  for (int i = 0; i < N_FILLER; i++)\n+    {\n+      /* Every N_FILLER/N_INDEX filler allocs, insert an index alloc.  */\n+      if (idx < N_INDEX && i % (N_FILLER / N_INDEX) == 0)\n+\t{\n+\t  index_ptrs[idx] = malloc (INDEX_SIZE);\n+\t  TEST_VERIFY_EXIT (index_ptrs[idx] != NULL);\n+\t  memset (index_ptrs[idx], 0xAA, INDEX_SIZE);\n+\t  idx++;\n+\t}\n+\n+      filler_ptrs[i] = malloc (FILLER_SIZE);\n+      TEST_VERIFY_EXIT (filler_ptrs[i] != NULL);\n+      memset (filler_ptrs[i], 0xBB, FILLER_SIZE);\n+    }\n+\n+  long rss_peak = get_rss ();\n+  printf (\"RSS after allocation: %ld MB\\n\", rss_peak / (1024 * 1024));\n+\n+  /* Phase 2: Free all filler data.  Index data stays alive and\n+     pins the heap segments, so the freed space is interior.  */\n+  for (int i = 0; i < N_FILLER; i++)\n+    free (filler_ptrs[i]);\n+\n+  long rss_after_free = get_rss ();\n+  printf (\"RSS after free:       %ld MB\\n\", rss_after_free / (1024 * 1024));\n+\n+  /* Phase 3: Check that RSS dropped.\n+     With madvise_threshold set (via GLIBC_TUNABLES in the test\n+     environment), free() calls madvise(MADV_DONTNEED) on the\n+     interior of the freed chunks, so RSS should drop.\n+\n+     Without the tunable, RSS stays near the peak because the freed\n+     memory is interior to the heap, not at the top.\n+\n+     We expect at least 50% of the filler memory to be returned.  */\n+  long filler_bytes = (long) N_FILLER * FILLER_SIZE;\n+  long recovered = rss_peak - rss_after_free;\n+\n+  printf (\"Filler data:          %ld MB\\n\", filler_bytes / (1024 * 1024));\n+  printf (\"Recovered by free():  %ld MB\\n\", recovered / (1024 * 1024));\n+\n+  /* The threshold is set via the test environment.  If it's working,\n+     we should recover at least half the filler memory.  */\n+  TEST_VERIFY (recovered > filler_bytes / 2);\n+\n+  /* Cleanup.  */\n+  for (int i = 0; i < N_INDEX; i++)\n+    free (index_ptrs[i]);\n+\n+  return 0;\n+}\n+\n+#include <support/test-driver.c>\n",
    "prefixes": [
        "RFC",
        "1/1"
    ]
}