Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:129189 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 B39021A00BC for ; Mon, 10 Nov 2025 13:25:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1762781114; bh=jucPDVm7Drgu1U/SSdHF+mtT1Ib4XIpsRBh2NBS/TKg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=DEwAJiVOth3wl8DKGX1mA5iWacNMhFn6rdxZl9pd+gX4q/1Uw67zYwSP57u/nGcL4 gmLa5R25tq0Ref2p6cBHhpd6DMgpeIY7lRUqFY9hjsE744FNgxIUEeyTSkZk6pS6g3 sCFTjFFu9jXqWp3teb8cs7OIsp2s1SekbUxPwwHpyv4BCK4bpp1W06NdiYjCSVrDCt o5CY76hoi4583Qawlw7/Zlbbk/YiMZ06fMSOlN/nM4mSg1+o037cLTFIVirxy565DH mP8tJF3W9Hk68Bet+1I9T2ZciyFeHfJMSQb5Kpkh/EJrayoCiu++Y/3MQUEE+OCoAA xZc1FUEqhNjXQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 5770218003E for ; Mon, 10 Nov 2025 13:25:13 +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,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: No X-Envelope-From: Received: from mail-ej1-f49.google.com (mail-ej1-f49.google.com [209.85.218.49]) (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 ; Mon, 10 Nov 2025 13:25:12 +0000 (UTC) Received: by mail-ej1-f49.google.com with SMTP id a640c23a62f3a-b725ead5800so393497966b.1 for ; Mon, 10 Nov 2025 05:25:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1762781107; x=1763385907; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=phhPmkl2YeEWbNQNVH39VOVQZR4qfSNppOUh+YVvdrg=; b=k25eujppR93tdW4fdcilyc+EQt1OvGBDaY0FddM5FnCxKfIwSPQIa+ct+YwTy+cVz9 sR8vAizvGLQnAtiKsl+fOLXjWaV42S6D+LqHCfvzYirlGbrHTz+SL2eeWj+EESlNBwBj roMM22AhC9Hoi01PgUKlFIa3givTI8gxLZyAbys8je45mrV0u3tk0RuFLGBrZylPwVe0 xukI08AXD5ExgcsChY6IPcdzwyxRSBJL8vHH10iLhy/kSvZQZPY+e7YY5oEzCtsiSGtk EDOvGWlwOVWkpG8zYfrEpBwuf7vdZgVcLLFCAHqhklQi4AnnJovWYQr7YGcEac3lQTaT 6ZEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1762781107; x=1763385907; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=phhPmkl2YeEWbNQNVH39VOVQZR4qfSNppOUh+YVvdrg=; b=fU6it9aT7pmvjId4c4qqg+p+rr9O6B6HtKkzEq2lsmZSI0IVZIi9dmnu2zGGrEXGEV 72I/+oRJIPmYQq277SXC9ykVFYtoan9BqMEDPqu+7n5Wq5CzpZymZhl604lpzecykJpr 8MHcqcY3RTaHro1pFlXibu0P4UUEG0nuV0zRpvzfTNbIHZ2X6cAhZ3f0WH+/0nIuiCJv MUHlkOKJ6Ts8ERKbAKt++/j7R8uJW3C1w3PoIF6rDba9rgKesxhoF1/ZqMhYzjUQbpqK 2Q7NLTUayzuY0/CserCaRHGPI84+gwbr61bV7Nvim4bf+KBvv6h7szRGfsbmL+X53Hcw qKKQ== X-Forwarded-Encrypted: i=1; AJvYcCVQqt7kxxoyTc5TZzk7Il4ESe5btrneG6etVZr7saUpjIUbwt2TW7i/biNydxlzx1JQ8GjGdnsMVbQ=@lists.php.net X-Gm-Message-State: AOJu0Ywp+cdLEmmg5d+dV/OgEN4VXW3TZ/YlmeMTx6W1F1cjQCqTLTFa PY05s96WM4O3x8JY52m8m0DcdXBQObsEUI284hMc1Y0XKucK8KJ2hybuBqTXlL/uy0nKmO7rgPq JIrSzEf7Rh9lGC7uuR7r1LP1yqXlZbpxPg+z7 X-Gm-Gg: ASbGncvWxd2Vdqay5hSY7C+bG2KCbgNQ5gmDxmfBZcU0UWnpJTnTcpryWxgen2zZD1Y Kzi2gMrQ/+ehba1YWhs3OL5WkN1l1UyfBiwVsltf6mFr18ZgMDgHUGmfKgmX7xcL310zYyppu2L QHve2fhun8hcaS69XjFRbSvKCkFcMiTb/dhTKyNpNu39+t3aZCq4HLOoADND115VlYzauzCJjJy xxMkbMqeMKtgzgjKH3+TF20VfFHlFQqzgEimJy2rFuJ6jbcvIq57RhtSgWZ X-Google-Smtp-Source: AGHT+IFWzUNnjOol/tWLc46oI0SlYjAQt5UlLGGugLbQuVO2V69ztPaZfjivkB/oIno+J5uEtuh/oXJhbp6ymghu8+Y= X-Received: by 2002:a17:907:7b96:b0:b72:b495:827a with SMTP id a640c23a62f3a-b72e041274amr880289666b.38.1762781106421; Mon, 10 Nov 2025 05:25:06 -0800 (PST) Precedence: list list-help: list-unsubscribe: list-post: List-Id: x-ms-reactions: disallow MIME-Version: 1.0 References: <4bf2e8023b934e73e0832c8dc3ddeb3c@bastelstu.be> In-Reply-To: Date: Mon, 10 Nov 2025 14:24:54 +0100 X-Gm-Features: AWmQ_blT_Uuijcm-tbQhBtKefCMzzwXKg_ltml2pfe1oqrRkD6Spu8gza16qv4A Message-ID: Subject: Re: [PHP-DEV] [RFC][Discussion] use construct (Block Scoping) To: =?UTF-8?Q?Tim_D=C3=BCsterhus?= Cc: Seifeddine Gmati , internals@lists.php.net Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable From: arnaud.lb@gmail.com (Arnaud Le Blanc) Hi, On Sun, Nov 9, 2025 at 9:08=E2=80=AFPM Tim D=C3=BCsterhus wrote: > 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 =E2=80=9Cunreliable= =E2=80=9D > 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.rs= t#throwables) > I can meaningfully handle a =E2=80=9CDiskFullException=E2=80=9D when atte= mpting to write > into a file. But I handling a =E2=80=9CFileHandleBrokenError=E2=80=9D is = not meaningful, > particularly when it's something like calling `fstat(2)` which is > explicitly acting on a file descriptor you are already holding. As I'm seeing it, a File object that was explicitly closed would throw an exception like "FileIsClosedError". It would indicate a lifetime bug that needs to be fixed, not something that should be handled by the program. This is reasonable, as closing is a clear intent that the resource should not be used anymore. This is not an exception that needs to be handled/checked. Under these intentions, leaving the resource open (for the reason it's still referenced) and allowing writes to it would be much worse. BTW, has the idea of removing close() methods on resources been tried successfully in other languages? > > 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 =E2=80=9Cescapes=E2=80=9D are always= unintentional, > which I do not believe is true (as mentioned in the next quoted section). This diverges considerably from the features that the RFC claims to be designed after. Other languages with these features forcibly close the resource, while languages favoring RAII idioms make it very obvious when variables have non-local lifetimes. I feel that the RFC is taking a risk, and doesn't build on proven features, as it states. IMHO it is encouraging an idiom that comes with many pitfalls in PHP. The exception/backtrace issue demonstrated by Ed and Claude is not easily fixable with attributes or weak references, as the resource can be referenced indirectly: https://3v4l.org/lsicN Exceptions/backtraces are not the only way to capture a variable. It can happen in explicit ways: ``` using ($fd =3D fopen("file", "r")) { $buffer =3D new LineBuffered($fd); } // $fd not closed ``` Of course we can can do this instead, but it's easy to forget, so it's a pitfall: ``` using ($fd =3D fopen("file", "r"), $buffer =3D new LineBuffered($fd)) { } ``` And now, all precautions that one should take for resources (do not create cycles, do not extend lifetime) should also be taken for anything the resource is passed to. Here we capture $this by declaring a closure: ``` class CharsetConversion { function __construct(private mixed $fd, private string $from, private string $to) { if (function_exists("iconv")) { $this->convert =3D fn($input) =3D> iconv($this->from, $this->to,= $input); } else { $this->convert =3D fn($input) =3D> mb_convert_encoding($input, $this->to, $this->from); } } function readLine() { return ($this->convert)(fgets($this->fd)); } } using ($fd =3D new File("file", "r"), $conversion =3D new CharsetConversion($fd, "iso-8859-1", "utf-8")) { } // $fd not closed ``` Async frameworks are likely to hold onto resources, by design: ``` using ($fd =3D ..) { await(processFile($fd)); } // $fd not closed ``` In this case a proper exitContext() / dispose() would likely request the framework to stop watching $fd. > Managing lifetimes properly is already something that folks need to do. > You mention =E2=80=9Cfile descriptor leak=E2=80=9D, but this is no differ= ent from a > =E2=80=9Cmemory leak=E2=80=9D that causes the program to to exceed the `m= emory_limit`, > because some large structure was accidentally still referenced somewhere. There are a few differences between memory and other resources: * Cycles can retain both memory and external resources, but the GC is governed only by memory-related metrics. So, cycles are usually cleared before they become a problem WRT memory usage, but not WRT other limits. I believe this is the reason why other languages chose to not rely on finalizers to release non-memory resources. * Memory is usually less scarce than other resources (files, db connection= s) * Releasing memory is usually less time-sensitive than other resources (locks, transactions) * Memory usage can be observed in an easier way * Resources can be released explicitly, but not memory > > 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. I would represent this as a LockedFile object, and make it an error to access the object if it has been closed/discarded, so it's not needed to check its state explicitly before every access. This is under the assumption that if I closed or unlocked the file, I don't intend it to be used anymore. If it's still accessed, letting the access go through seems worse than terminating the program. > >> This is true, but equally affects =E2=80=9Cnot closing=E2=80=9D and = =E2=80=9Cforcibly closing=E2=80=9D > >> 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=3D4. > - The file object is passed to your IO polling mechanism. > - The file object is forcibly closed, releasing FD=3D4. > - FD=3D4 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=3D4= . > - The IO polling mechanism works on the wrong FD until it realizes that > the file object is dead. > > Am I misunderstanding you? I'm not sure. I would expect an I/O polling mechanism to remove the FD as soon as it's closed. > > 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. I disagree. I gave one counter example above. > And as outlined above, > making a variable escape the local scope needs some deliberate action. I also disagree. It's true in C++ or Rust, as extending the lifetime of a local var would be undefined behavior or a compile time error, but not in PHP. Doing anything useful with a resource will likely involve passing it to other functions, which increases the chances of it happening. See also the examples above. Best Regards, Arnaud