Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129179 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id AECC51A00BC for ; Sun, 9 Nov 2025 20:08:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1762718922; bh=MA/zD8Aa4LGAqnNynkeQfOEx1qqgFzaGGTl1dVxXlRs=; h=Date:From:Subject:To:Cc:References:In-Reply-To:From; b=h8LEYMONWTYgiFBxIjz2L3wbWw9aDFGr/w1JtDWC2fspm/ul6uqJrCAIs+ZqR28h+ nG0HfxpF6yxfGXsCVKJHn6HarzOX9PyMo+bSYtxgZAD/5ZnWrkbktf/csHNpiYqc0H ZGjjAIXtw//0zl/A0rD1cJtojWf8Va7nxN0vv4N8g1RevBuCHTb3ixggUp1Ewm3kJ9 d6s643gpdTTlVgeckS/s/hcX0/sMmLHCvqMCnJzEUhFSVXL0rj1jbiYsIUx9zPIRZX uwfDZ9YaksYbogaOpKriZekjS+u4CECuJxpumgZLwPrPP/N9WMNwqrVmn1VIWOFgcv dJM6KzcFyN85w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 778F3180050 for ; Sun, 9 Nov 2025 20:08:41 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=0.6 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from chrono.xqk7.com (chrono.xqk7.com [176.9.45.72]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Sun, 9 Nov 2025 20:08:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bastelstu.be; s=mail20171119; t=1762718915; bh=sKdUWjuMOWJ4aJuofGdIKuPkXMKV86kr1AOXwtJutMk=; h=Message-ID:Date:MIME-Version:From:Subject:To:Cc:References: In-Reply-To:Content-Type:from:to:cc:subject:message-id; b=ENf0pgxEnp37WkE7w7WxTYbNbenHSPzgWMBV4abSbO015IQ2rdKzsn/txbVsuHLvb bCuGjVgt7AwDfl8Zv/efo9g8k9FWavVsJYDsY0SgWh69NXLct+DuCOFsIj5Qj9NoOA oW1SHh80D08eKX2kEd1Zx3BDyQUs7f053J1Brh6hBGrkh/Hb1Takf1BO2OavweqliO bmcFathMyE9I4HaSXrK9b1PuG34z7VzTOfO7o1tNDYCW0jCSuamyuhiAOy75CQMmMU k5/7NhiYvNq9+fn8yrqURwp+WIxU/mC/DCnFgkeBP+vpviZaBe97NKe7hyk+yU5DBc Nhe3QllXvdPDQ== Message-ID: Date: Sun, 9 Nov 2025 21:08:34 +0100 Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 Subject: Re: [PHP-DEV] [RFC][Discussion] use construct (Block Scoping) To: Arnaud Le Blanc Cc: Seifeddine Gmati , internals@lists.php.net References: <4bf2e8023b934e73e0832c8dc3ddeb3c@bastelstu.be> Content-Language: en-US In-Reply-To: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit From: tim@bastelstu.be (=?UTF-8?Q?Tim_D=C3=BCsterhus?=) Hi On 11/5/25 17:17, Arnaud Le Blanc wrote: > But I don't think this is achievable or desirable for objects that > represent external resources like files or connection to servers, > which is what with() and similar mechanisms target. These resources > can become invalid or operations on them can fail for reasons that are > external to the program state. Removing close() methods will not > achieve the goal of ensuring that these resources are always valid. That is correct, but I don't think that this is an argument in favor of increasing the number of these situations. Even for “unreliable” external resources, introspection functionality generally is effectively infallible (i.e. it only fails in situation where the entire system is in a bad state). Following our Throwable policy (https://github.com/php/policies/blob/main/coding-standards-and-naming.rst#throwables) I can meaningfully handle a “DiskFullException” when attempting to write into a file. But I handling a “FileHandleBrokenError” is not meaningful, particularly when it's something like calling `fstat(2)` which is explicitly acting on a file descriptor you are already holding. >> If $fd escapes and is nevertheless closed at the end of the block, this >> may affect the program's behavior in various ways: >> - Suddenly any operation on the file descriptor fails. > > This will also happen due to external factors, for example if the disk > becomes full. Having a File object that can not be closed doesn't > ensure that operations on it will not throw. See above. > Regarding `use()`, there are two alternatives, with different outcomes: > > 1. use() doesn't forcibly close resources: If a resource escapes > despite the intent of the programmer, the program may appear to work > normally for a while until the leak causes it to fail > 2. use() forcibly closes resources: If a resource escapes despite the > intent of the programmer, the program may fail faster if it attempts > to use the resource again > > The second alternative seems better to me: > > * If a mistake was made, the program will stop earlier and will not > successfully interact with a resource that was supposed to be closed > (which could have unwanted results) > * Troubleshooting will be easier than chasing a resource leak This is based on the assumption that “escapes” are always unintentional, which I do not believe is true (as mentioned in the next quoted section). Managing lifetimes properly is already something that folks need to do. You mention “file descriptor leak”, but this is no different from a “memory leak” that causes the program to to exceed the `memory_limit`, because some large structure was accidentally still referenced somewhere. The problem and solution is the same for both cases and my understanding is that there is already tooling to assist with verifying that e.g. a PHPUnit test does not leak. >> Being able to let resource objects escape is a feature, since this >> allows to reliably pass locks around without the resource suddenly >> getting unlocked. > > Would you utilize `use()` to lock a file in cases where the lock is > supposed to outlive the `use()` block? Yes. The `use()` is there to make sure that I properly clean up after myself. If I pass my resource to another function, then I'm still responsible to clean up after myself - and the called function is responsible to clean up after itself. As an example use case, consider a function that takes a lock as a proof that some resource is locked and then either processes it immediately or stores the resource (incl. the lock) for later processing and then unlock it when it is done with the processing. The `use()` construct in the caller then ensures that for the “immediate” use case the lock is not held for longer than necessary. In any case, the developer is in full control. Passing the lock to the other function and creating a function that takes a lock as proof are intentional acts. In simple cases, the resource object will be a regular local variable that will not escape. The PSL Lock example from the RFC is such an example, it was specifically designed to be held in the local scope and users of PSL are already successfully using that pattern. The PSL library is developed and maintained by Seifeddine and he specifically co-authored the RFC to improve the use cases that users (of PSL) are already successfully applying in practice. > Making objects invalid to detect bugs can also be a feature: We could > make a LockedFile object invalid once it's unlocked, therefore > preventing accidental access to the file while it's unlocked. To make this same, the state of the lock object would need to be checked before every access, which I believe is impractical and error prone. If you forget this check, then the file might already be unlocked, since every function call could possibly have unlocked the file by calling `->unlock()`. By tying the lock to the lifetime of an object it's easy to reason about and to review: If the object is alive, which is easily guaranteed by looking if the corresponding variable is in scope, the lock is locked. >> This is true, but equally affects “not closing” and “forcibly closing” >> the resource. In case of forcibly closing, your I/O polling mechanism >> might suddenly see a dead file descriptor (or worse: a reassigned one) - > > The reassigned case can not happen in PHP as we don't use raw file > descriptor numbers. I was thinking about the following situation: - A file object is created that internally stores FD=4. - The file object is passed to your IO polling mechanism. - The file object is forcibly closed, releasing FD=4. - FD=4 still remains registered in the IO polling mechanism, since the IO polling mechanism is unaware that the file object was forcibly closed. - A new file object is created that internally gets the reassigned FD=4. - The IO polling mechanism works on the wrong FD until it realizes that the file object is dead. Am I misunderstanding you? >> and static analysis tools need to report every single method call as >> “might possibly throw an Exception”. > > This is the case even if we removed every possible way to close a file > descriptor See the top of this email. > The fact we had to introduce a cycle collector, and that most projects > don't disable it, shows that cycles exist in practice. The fact that > they exist or can be introduced is enough that thinking of PHP's GC > mechanism as something closer to a tracing GC is easier and safer, in > general. A resource doesn't have to be part of a cycle, it only needs > to be referenced by one. The data structures that tend to end up circular, are not the data structures that tend to store resource objects. And as outlined above, making a variable escape the local scope needs some deliberate action. I expect it to be something done by more experienced PHP developers, which I'd claim are also the group of developers that carefully rely on the semantics of the language to keep their code safe. Best regards Tim Düsterhus