Re: [RFC] Context Managers
| From: | Larry Garfield | Date: | Wed, 15 Apr 2026 14:52:17 +0000 |
| Subject: | Re: [RFC] Context Managers | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-130639@lists.php.net to get a copy of this message | ||
On Tue, Apr 14, 2026, at 9:27 AM, Rob Landers wrote:
> On Tue, Apr 14, 2026, at 16:18, Rob Landers wrote:
>>
>>
>> On Tue, Nov 4, 2025, at 21:13, Larry Garfield wrote:
>>> Arnaud and I would like to present another RFC for consideration: Context Managers.
>>>
>>> https://wiki.php.net/rfc/context-managers
>>>
>>> You'll probably note that is very similar to the recent proposal from Tim and
>>> Seifeddine. Both proposals grew out of casual discussion several months ago; I don't believe
>>> either team was aware that the other was also actively working on such a proposal, so we now have
>>> two. C'est la vie. :-)
>>>
>>> Naturally, Arnaud and I feel that our approach is the better one. In particular, as
>>> Arnaud noted in an earlier reply, __destruct() is unreliable if timing matters. It also does not
>>> allow differentiating between a success or failure exit condition, which for many use cases is
>>> absolutely mandatory (as shown in the examples in the context manager RFC).
>>>
>>> The Context Manager proposal is a near direct port of Python's approach, which is
>>> generally very well thought-out. However, there are a few open questions as listed in the RFC that
>>> we are seeking feedback on.
>>>
>>> Discuss. :-)
>>>
>>> --
>>> Larry Garfield
>>> larry@garfieldtech.com
>>>
>>
>> Hi Larry/Arnaud,
>>
>> This is a pretty exciting thread and fascinating proposal. That being said, I have a couple
>> of subtle questions that don't seem to be answered in the (very long) thread or the RFC itself
>> -- If I missed it, please let me know:
>> 1. What happens if a Fiber is suspended in the using block and never resumed? When is the
>> using block released to clean up the context?
Since it decomposes to a try-catch-finally, it will exit whenever the finally block would have run
if you'd just typed out try-catch-finally yourself. Arnaud checked, and confirmed that when
the fiber is destroyed the using block will exit in a success case (ie, exitContext(null)).
>> 2. There's still no mention of how this should affect debugging, will we see the
>> "desugared" or "sugared" version? Is that even a concern for the RFC?
Error messages would see the original code, so "error on line X" would be based on the
original
using block. That's the same as any other desugaring we already do.
(PIpes, PFA, constructor promotion, etc.) Debuggers will see the materialized opcodes, again, the
same other desugaring cases.
>> 3. I will say it is weird to have exitContext return an exception; but what happens if an
>> exception is thrown during exitContext? Why not just have it return void and throw if you need to
>> throw instead of having two paths to the same thing?
There's a subtle but important difference here: An exception passed through exitContext() is
the original exception from lower in the call stack, and its backtrace will be the original location
of the error. An exception thrown from within exitContext() itself indicates a failure that the
Context Manager is responsible for, usually an error in the exitContext() logic itself.
Technically a Context Manager can wrap-and-rethrow the exception if it wants, but then it is
"claiming ownership" over it, just like in any other case of wrap-and-rethrow.
Our expectation is that 90% of the time, "let the exception propagate up unimpeded" is the
desired behavior. This approach makes "return $e" the right thing to do almost-always,
which is nice and simple to remember.
See the "return values and exception handling" section for a discussion of this in more
detail. As I said in a previous reply, our constraints are different than Python's so we end
up with a different solution. If you have a suggestion for an alternate approach to the problem,
we're happy to listen.
>> 4. Looking at the desugared form ... I'm a bit confused: if exitContext is called
>> during the finally path and returns an exception, it is just swallowed? But if it is thrown, it
>> won't be?
The finally path is only reached in case of a successful exit. Therefore there is no exception to
pass in, and thus returning an exception is meaningless. If exitContext() throws a new exception of
its own (which would indicate an error in its own logic), that will just bubble up past the
using block entirely, which is what we want.
>> 5. That being said, I don't think the RFC shares with us when we should return an
>> exception vs. throw an exception.
See the "return values and exception handling" section. If something there isn't
clear, let me know and I will try to clarify further.
>> — Rob
>
> Maybe the desugared version should look more like this?
>
> } catch (\Throwable $e) {
> try {
> $__mgr->exitContext($e);
> } catch (\Throwable $cleanupException) {
> throw new ContextManagerException(
> $cleanupException->getMessage(),
> previous: $e
> );
> }
> throw $e;
> }
>
I'm not sure I see a reason to force any new exceptions to be only of the
ContextManagerException type. If there's a TypeError inside exitContext() or something,
I'd expect that to be propagated as a TypeError.
--Larry Garfield