Slim 4 - Why doesn't ResponseEmitter close the body stream when finished reading?

I have questions regarding Slim’s handling of the body stream. In ResponseEmitter, the body stream is read until $body->eof() is true but the stream is never closed.

My main concern is during file downloads, file handles created via fopen and used to construct a stream are not being closed. However, I have never proven this to be true. I assume PHP automatically cleans up open file handles before the script exits. Is that assumption correct?

I’m sure there’s a reason the stream isn’t closed after emitting the response. Would someone be able to explain to me why?

Edit: According to this StackOverflow post, file handles are automatically cleaned upon script termination. But they do point out that there are some security concerns with leaving the handles open.

Thank you!

Is that assumption correct?

Yes, thanks to the Shared Nothing Architecture. As far as I know there are no security issues when using shared nothing, because the memory will be completely wiped, unless there are some bugs in PHP that cause memory leaks.

If you use “async” frameworks such as Swoole, the handlers may not be closed automatically, which could cause memory and security issues.

1 Like

Thanks for the info! What happens at the OS level when fopen/fclose is called in PHP? Does PHP’s fopen open a file in the OS, and does fclose close that file? Or is the file descriptor handing at an OS level handled invisibly to the user by PHP? Also, does the Nothing Shared architecture still work if, for example, PHP segfaults?

PHP is written in C. The PHP internal Zend Memory Manager (ZendMM) used the “efree” function to release the memory (and in some rare cases the libc free() function). So PHP implements a “wrapper” around “libc” and runs therefore quite low level.
Request-bound dynamic memory allocations are tracked by ZendMM, and you’ll be informed about leaking. There are some very rare exceptions where the ZendMM is not able to track memory leaks.

I copied this from the internal documentation:

Beware however:

  • Obviously ZendMM doesn’t know anything about persistent allocations, or allocations that were performed in another way than using it. Hence, ZendMM can only warn you about allocations it is aware of, every traditional libc allocation won’t be reported in here, f.e.

  • If PHP shuts down in an incorrect maner (what we call an unclean shutdown), ZendMM will report tons of leaks. This is because when incorrectly shutdown, the engine uses a longjmp() call to a catch block, preventing every code that cleans memory to fire-in. Thus, many leaks get reported. This happens especially after a call to PHP’s exit()/die(), or if a fatal error gets triggered in some critical parts of PHP.

  • If you use a non-debug build of PHP, nothing shows on stderr, ZendMM is dumb but will still clean any allocated request-bound buffer that’s not been explicitly freed by the programmer

At the end of the day, I would say that none of this is relevant to 99.98% of the PHP developers. PHP has been tested and proven more than once in the last 20+ years.

If you want to learn more about the internals, you can find more information here:

1 Like

At the end of the day, I would say that none of this is relevant to 99.98% of the PHP developers.

Yeah, I kinda figured that. I’m definitely no exception, I’m just hoping to get a better understanding of what’s happening under the hood.

Thank you for the PHP Internals link, that’s a gold mine I was not aware of. I very quickly found this statement which answers my fopen/fclose question:

Resources must register a destructor. When users use resources in PHP userland, they usually don’t bother cleaning those when they don’t make use of them anymore. For example, it is not uncommon to see an fopen() call, and not see the matching fclose() call.

Thanks for taking the time to explain things to me!

1 Like