<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <p>Hi,<br>
      thanks for the feedback. The decision of whether to lump or split
      various kind of arenas has defintively been on our mind while
      designing the API.</p>
    <p>First, let me point out that I find your connection to integrity
      by default a bit excessive. You can already have memory leaks in
      Java in all sort of ways -- without the need to resort to off-heap
      memory. A program with a memory leak doesn't run into undefined
      behavior, nor does it undermine memory safety. It is just a badly
      written (and possibly very inefficient) program. (Note that Rust
      has a similar stance [1] -- preventing memory leaks is *not* among
      the guarantees provided by the Rust type system).<br>
    </p>
    <p>More to your point, our experience with trying to provide an API
      that more clearly separated between managed and unmanaged lifetime
      always led to the feeling that, while more formally correct, more
      splitty APIs weren't really improving the situation much -- and,
      at the same time, would make discoverability of the API worse, by
      scattering arena factories across the FFM API.</p>
    <p>Let's say, for argument sake, that we had two Arena types --
      Arena and ManagedArena. ManagedArena would have a close method,
      while Arena would not. Let's say that, for ease of use, one would
      start from a plain Arena:</p>
    <p>```<br>
      Arena arena = Arena.ofAuto();<br>
      arena.allocate(...)<br>
      foo(arena);<br>
      ```</p>
    <p>But later on, as you point out, one realizes that more control
      over deallocation is required, and a confined arena is needed.
      Let's say we change only the first factory call:</p>
    <div style="background-color:#ffffff;color:#000000">
      <p>```<br>
        Arena arena = ManagedArena.ofConfined();<br>
        arena.allocate(...)<br>
        foo(arena);<br>
        ```</p>
      <p>The rest of the code would still compile perfectly. So, having
        a more splitty hierarchy here doesn't really buy a lot of extra
        safety, as you can still refactor your code in ways that leaves
        deallocation behind. That is, unless you go completely overboard
        and make Arena and ManagedArena incompatible, which would be
        very bad, as now developers would have to keep two unrelated
        abstractions in their minds at all times (meaning the split
        would also affect client code) -- we tried this in Java 20 and
        it didn't really work out. E.g. an important use case such as
        Arena extensibility was badly hurt by the split move, as users
        could define custom managed arenas, but they could not define
        custom automatic arenas with more efficient allocation policies
        (which resulted in tension/friction between Arena and
        SegmentAllocator). </p>
      <p>Instead of lump vs. split, I think the crucial observation is
        this:</p>
      <p> </p>
      <blockquote type="cite">Because these implementations throw an
        exception on <code>close()</code>, developers are actively
        incentivized (and practically forced) to avoid the <code>try-with-resources</code>
        pattern.</blockquote>
      <br>
      This has also been the subject of many discussions while designing
      the API. There's basically two questions here:
      <p>* should close() be idempotent? E.g. should closing an already
        closed arena succeed?<br>
        * should close() tolerate cases where the method is called on a
        managed arena (with the subtext that, in such cases, close() is,
        again, a no-op)</p>
      <p>When designing the API, we always erred on the side of catching
        user bugs -- out of a desire to make a low-level API such as FFM
        as deterministic and predictable as possible (e.g. close really
        means close). In this light, closing an already closed arena
        seems like a bug (double free?) so, throwing an exception seemed
        reasonable. And, similarly, closing an arena externally managed
        also seemed suspicious, as no real "free" occurs there. Although
        I concede that argument for the former is less clear-cut than
        the latter -- e.g. even if close were to be idempotent, the
        invariant that all segments associated with a closed arena are
        no longer accessible would still be valid. On the other hand,
        allowing to close a managed arena might create an expectation
        that its segments are no longer accessible, whereas that's not
        the case.<br>
      </p>
      <p>There's no "free lunches" here -- either we make the API more
        easily usable with try-with-resources _or_ we detect suspicious
        activity in user code (such as a double close), but we can't do
        both.<br>
      </p>
      Perhaps, to mitigate some of the concerns you have, some other
      avenues could be explored:<br>
      <br>
      * an IDE analysis could e.g. detect cases where a confined/shared
      arena is used w/o a corresponding try-with-resource block<br>
      * some JFR event could be triggered when an confined/shared Arena
      becomes unrechable and its scope is still alive (Netty's ByteBuf
      API has something similar to this [2])<br>
    </div>
    <div style="background-color:#ffffff;color:#000000"><br>
    </div>
    <div style="background-color:#ffffff;color:#000000">Summing up,
      after staring at this problem for a very long time, I don't think
      there's a "perfect" Arena API that is rid of all the
      aforementioned issues -- it's mostly a "pick your poison"
      situation.<br>
    </div>
    <p>Cheers<br>
      Maurizio<br>
      <br>
      [1] - <a class="moz-txt-link-freetext" href="https://doc.rust-lang.org/book/ch15-06-reference-cycles.html">https://doc.rust-lang.org/book/ch15-06-reference-cycles.html</a><br>
      [2] -
<a class="moz-txt-link-freetext" href="https://netty.io/wiki/reference-counted-objects.html#troubleshooting-buffer-leaks">https://netty.io/wiki/reference-counted-objects.html#troubleshooting-buffer-leaks</a><br>
    </p>
    <p><br>
    </p>
    <div class="moz-cite-prefix">On 16/01/2026 23:49, Abraham Tehrani
      wrote:<br>
    </div>
    <blockquote type="cite" cite="mid:CALHZk=nv6ZTXu=-nsY5nUr4eUCesQY_D3keyW04_hWUEAZupog@mail.gmail.com">
      <div dir="ltr">
        <p>Dear Panama Dev Team,</p>
        <p>I would like to offer feedback on the current design of the <code>Arena</code>
          interface, specifically the decision to have <code>global()</code>
          and <code>ofAuto()</code> throw <code>UnsupportedOperationException</code>
          on <code>close()</code>.</p>
        <p>While I understand the desire for a unified interface, I
          believe this design creates a significant "Refactoring Hazard"
          that undermines the goal of <b>Integrity by Default</b>.</p>
        <p><b>The Path to a Leak:</b> Most developers will naturally
          start with <code>global()</code> or <code>ofAuto()</code>
          for ease of use. Because these implementations throw an
          exception on <code>close()</code>, developers are actively
          incentivized (and practically forced) to avoid the <code>try-with-resources</code>
          pattern.</p>
        <p>Later, when the developer needs to optimize and switches to <code>ofConfined()</code>
          or <code>ofShared()</code>, they will perform a "drop-in"
          replacement of the implementation. Because <code>Arena</code>
          is a unified interface, the code will compile perfectly.
          However, the developer, now conditioned to treat Arenas as
          managed, will likely fail to wrap the new implementation in a
          <code>try-with-resources</code> block.</p>
        <p><b>The Result:</b> A catastrophic off-heap memory leak.</p>
        <p>In almost any other Java API, refactoring toward more manual
          control is guarded by the compiler or the type system. Here,
          the "ease of use" of a single interface effectively masks a
          fundamental change in lifecycle responsibility.</p>
        <p>When dealing with <i>native memory</i>, <b>correctness and
            explicit lifecycle management must take precedence over
            interface uniformity.</b> We are trading a slightly "split"
          API for a very real "split" in runtime safety.</p>
        <p>The easiest way to use an API should also be the safest way.</p>
        <p>Respectfully and Humbly,<br>
          Abraham Tehrani</p>
      </div>
    </blockquote>
  </body>
</html>