Skip to content

Conversation

dktapps
Copy link
Contributor

@dktapps dktapps commented Nov 24, 2021

These issues were caused by #491, because it made stream_read() return an empty string.
OPcache then cached this empty string, leading to failures to load files from phars or file:// locations.

This PR changes FileReadTrapStreamWrapper to allow autoloading to proceed as normal, and only intervenes to grab the first loaded filename. For everything else, it passes back to the regular functions.

It's not OK to return an empty string, because OPcache will remember it and won't read the actual file the next time the file is asked for.
So, to make this work in all scenarios, we need to actually implement file operations so that the class can be loaded for OPcache correctly.
Since we allow autoloading to proceed as normal, loading one class might cause more classes to be loaded. We only want to capture the first one.
@dktapps
Copy link
Contributor Author

dktapps commented Nov 24, 2021

This one fails because it used the cache from the previous broken build https://github.com/phpstan/phpstan-src/runs/4317359673?check_suite_focus=true

seems like an issue with the CI ...

Will fix the other issues shortly.

@dktapps
Copy link
Contributor Author

dktapps commented Nov 24, 2021

I see this causes a bunch of issues with static reflection. Not sure what the best way to solve those is, short of creating two implementations (obviously not ideal).

@dktapps
Copy link
Contributor Author

dktapps commented Nov 24, 2021

I attempted to use opcache_reset() and opcache_invalidate() to get this issue to gtfo, but opcache just won't cooperate. reset has no effect, and invalidate() returns false because it doesn't understand realpath() on phar:// (https://bugs.php.net/bug.php?id=78316). This is a really infuriating issue.

@dktapps
Copy link
Contributor Author

dktapps commented Nov 24, 2021

I think the only bulletproof solution to this problem is to do ini_set("opcache.enable", "off") somewhere. Since static reflection may anyway be used when dynamic reflection is enabled, there's no reliable way to know whether the actual code should be loaded or not, and if it's not loaded, this problem will occur because OPcache will grab the fake data.

Sadly, even just turning off OPcache temporarily can't be done, as opcache.enable can only be disabled at runtime. Once it's disabled, it can't be reenabled.

dktapps added a commit to pmmp/PocketMine-MP that referenced this pull request Nov 24, 2021
when using dynamic reflection (which is the default), any time static reflection comes into play, bad shit starts to happen because of FileReadTrapStreamWrapper.
I attempted to fix these issues (phpstan/phpstan-src#801) and failed miserably.
So, to save the hassle, it's time to just remove OPcache from the picture (which, unfortunately, also means that PHPStan will not benefit from JIT).
@mvorisek
Copy link
Contributor

disabling opcache causes increased memory usage and lower performance

@dktapps
Copy link
Contributor Author

dktapps commented Nov 25, 2021

Yeah, I'm aware. It also means PHPStan can't benefit from JIT. But until this bug is resolved, it's the only option I have available.

Trouble is, I don't see any way to fix this problem. If opcache_invalidate() worked, we might be OK, but even Nikita was nervous to fix it due to unknowns. Until static reflection is forced for everyone, this will continue to be a problem.

@mvorisek
Copy link
Contributor

The php-src issue seems fixable, let's try to fix it there first, wait for bugfix releases and disable opcache for the older/unmaitaned php versions.

@dktapps
Copy link
Contributor Author

dktapps commented Nov 25, 2021

I doubt it will get fixed upstream. This issue with opcache_invalidate() has been known since >2y and no activity on it due to low demand. I've made some suggestions and hopefully it gets fixed, but I'm not hopeful.

@dktapps
Copy link
Contributor Author

dktapps commented Nov 26, 2021

It looks like opcache_invalidate() does not do anything in the current request context (the script will not be recompiled, even when opcache_invalidate() succeeds). It only forces a recompile in subsequent requests.

It looks like the only ways to fix this problem are:

  1. Patch php-src to make opcache not cache scripts which are empty (weird special case, doubtful it would be accepted upstream).
  2. Disable opcache during PHPStan execution.

@dktapps
Copy link
Contributor Author

dktapps commented Nov 26, 2021

I should also point out that this can happen without OPcache when require_once is used. @ondrejmirtes this is a really broken hack :/

@staabm
Copy link
Contributor

staabm commented Nov 26, 2021

Not sure its related: there is another open PR to this file in #722

maybe thats something which works on the same problem your are trying to fix.
I didn‘t know, just remembered the other PR and figured maybe its helpful for you

@dktapps
Copy link
Contributor Author

dktapps commented Nov 26, 2021

No, that PR isn't related to this bug.

@ondrejmirtes
Copy link
Member

Thank you for this effort. Would you be able to report a bug to php-src? Or it isn't PHP's fault at all? Looks to me like opcache_invalidate ought to always work.

@dktapps
Copy link
Contributor Author

dktapps commented Dec 8, 2021

Even if opcache_invalidate() worked on phar://, it wouldn't work for this case anyway, since it only forces the file to be recompiled in the next request. I tested it with regular non-phar files also (the bug happens with require_once without opcache too: #801 (comment))

TL;DR: This is not PHP's fault. I can't find a right solution to this problem :(

@ondrejmirtes
Copy link
Member

Just dealt with this in the obvious way - let it actually autoload the file! e30f446

PHPStan should now work even with OPCache enabled, please test dev-master.

@dktapps
Copy link
Contributor Author

dktapps commented Dec 22, 2021

Great, thanks.

I suppose that fix should be fine in most cases since autoloaded classes usually won't directly execute code anyway.

@ondrejmirtes
Copy link
Member

One catch though - I had to revert to the old way of doing things when disableRuntimeReflectionProvider=true: c2d2c2f

@dktapps
Copy link
Contributor Author

dktapps commented Dec 22, 2021

In that case it should be fine anyway, since all classes are reflected statically. The issue only occurred when mixing static and dynamic reflection.

@ondrejmirtes
Copy link
Member

Maybe it's this obvious: ae53760

@dktapps
Copy link
Contributor Author

dktapps commented Apr 29, 2022

I tried that already, it doesn't work for this case.

@ondrejmirtes
Copy link
Member

It helped to solve my issues. If you comment it out in this PR, some tests fail (with OPCache enabled): #1265

@dktapps
Copy link
Contributor Author

dktapps commented Apr 30, 2022

The problems I had arose when using phars, since opcache_invalidate() doesn't like phar:// paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants