Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[YieldReady] to allow extensions to tell when they're ready for yielding #3999

Merged
merged 1 commit into from
Feb 15, 2024

Conversation

nicolas-grekas
Copy link
Contributor

@nicolas-grekas nicolas-grekas commented Feb 12, 2024

The current opt-in based on "use_yield" doesn't work, because there is no decider to enable the flag:
the flag can be turned on only when all extensions are compatible, and this is fragile, because extensions currently don't have a way to signal that they are ready for yielding.

This PR takes the following approach instead:

  • Between each "yield", the output buffer is checked and emptied.
  • Extensions need to label their node implementations with #[YieldReady]. If they don't, a deprecation is raised.

On Twig 4, we could then just remove this output buffer check and possibly throw when a node is not "YieldReady".

(Classes that extend AbstractExpression are implicitly considered "YieldReady".)

Best reviewed ignoring whitespaces.

src/Node/BlockNode.php Show resolved Hide resolved
src/Template.php Show resolved Hide resolved
src/Node/YieldReady.php Outdated Show resolved Hide resolved
Copy link
Member

@stof stof left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the performance impact of the runtime check combined with yield ?

src/Compiler.php Show resolved Hide resolved
@stof
Copy link
Member

stof commented Feb 12, 2024

Note that an effect of this change is that support for Fiber-based runtimes will become possible only as of Twig 4, even if all extensions are yield ready, as there is no way anymore to opt-in for the yield-only mode that unlocks that usage.

@nicolas-grekas nicolas-grekas force-pushed the yield-layer branch 6 times, most recently from 95c2801 to 61c7afe Compare February 13, 2024 08:40
@nicolas-grekas nicolas-grekas changed the title Replace "use_yield" by #[YieldReady] on nodes Add #[YieldReady] to allow extensions to tell when they're ready for yielding Feb 13, 2024
@nicolas-grekas
Copy link
Contributor Author

nicolas-grekas commented Feb 13, 2024

What is the performance impact of the runtime check combined with yield ?

I did a micro-benchmark comparing the time it takes to call ob_get_length() vs the time it takes to call an empty function c() {}.
I complete 10M iterations in 180ms for the ob function vs 210ms for the empty one.
This should be fine.

Note that an effect of this change is that support for Fiber-based runtimes will become possible only as of Twig 4, even if all extensions are yield ready, as there is no way anymore to opt-in for the yield-only mode that unlocks that usage.

I improved this by adding the "use_yield" flag back. When it's turned on, the node visitor will throw if any nodes are not #[YieldReady], and the Template will skip using any ob functions so that things are at full speed.

@nicolas-grekas nicolas-grekas force-pushed the yield-layer branch 5 times, most recently from d03553d to 285f863 Compare February 13, 2024 09:36
@nicolas-grekas
Copy link
Contributor Author

nicolas-grekas commented Feb 13, 2024

PR ready (failures are false positives)

src/Environment.php Outdated Show resolved Hide resolved
src/Node/CaptureNode.php Show resolved Hide resolved
src/Node/YieldReady.php Outdated Show resolved Hide resolved
src/Node/YieldReady.php Outdated Show resolved Hide resolved
src/NodeVisitor/YieldNotReadyNodeVisitor.php Outdated Show resolved Hide resolved
src/NodeVisitor/YieldNotReadyNodeVisitor.php Outdated Show resolved Hide resolved
@nicolas-grekas nicolas-grekas force-pushed the yield-layer branch 2 times, most recently from 0fc9739 to 03b884c Compare February 13, 2024 11:39
@nicolas-grekas
Copy link
Contributor Author

All comments addressed.

src/Environment.php Outdated Show resolved Hide resolved
@@ -82,7 +84,7 @@ public function __toString()
public function compile(Compiler $compiler)
{
foreach ($this->nodes as $node) {
$node->compile($compiler);
$compiler->subcompile($node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be reverted

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd better not: this line allows the compiler to accurately tell which node is leaking via "echo". If I remove it, the error/deprecation message will report about the wrong node (the wrapping one).
Can't we keep this line? Why if not? Calling subcompile instead of compile looks OK to me also, semantically speaking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that $compiler->subcompile($node); is already calling $node->compile($compiler);

if ($this->env->isDebug()) {
ob_start();
} else {
ob_start(function () { return ''; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've removed this "feature". I'm fine as we won't be able to replicate it with yield, but that should be noted in the CHANGELOG.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've read #1962 and #3058 and I think this is fine now: both issues are handled by using yield instead of output buffering. I'd keep things as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the changelog to tell about what this PR does, but about this specific behavior, I can't figure out what to write. It's too complex to describe to really be a "feature".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe CoreExtension should keep that logic in its captureOutput. The issue of not leaking output buffers in case of fatal errors still exists when this BC layer is used.

@fabpot
Copy link
Contributor

fabpot commented Feb 15, 2024

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 0990f81 into twigphp:3.x Feb 15, 2024
16 of 67 checks passed
nicolas-grekas added a commit to symfony/symfony that referenced this pull request Feb 15, 2024
This PR was merged into the 5.4 branch.

Discussion
----------

[TwigBridge] Fix compat with Twig v3.9

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

This PR relies on twigphp/Twig#3999

Commits
-------

f563a4c [TwigBridge] Fix compat with Twig v3.9
symfony-splitter pushed a commit to symfony/web-profiler-bundle that referenced this pull request Feb 15, 2024
This PR was merged into the 5.4 branch.

Discussion
----------

[TwigBridge] Fix compat with Twig v3.9

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | no
| Deprecations? | no
| Issues        | -
| License       | MIT

This PR relies on twigphp/Twig#3999

Commits
-------

f563a4c5a8 [TwigBridge] Fix compat with Twig v3.9
@nicolas-grekas nicolas-grekas deleted the yield-layer branch February 15, 2024 11:25
fabpot added a commit that referenced this pull request Aug 19, 2024
…colas-grekas)

This PR was merged into the 3.x branch.

Discussion
----------

Swap BC layer for yield-ready and reclaim perf loss

Follows #3999

Fix #4146
Fix #4103

When `use_yield` is set to false (the default), this PR reverts the implementation of the `render()` method to use a wrapping output buffer instead of hooking between each steps of generators. In this mode, the behavior of the yield method is not "pure": it triggers a mix of yield and echo. But this is fine for render and display methods.

When `use_yield` is set to `true`, we skip that wrapping output buffer. This makes twig compatible with fibers (and this also makes compilation fail if a non-YieldReady extension is found.)

That makes the name of the option not ideal, but BC rulez FTW.

Commits
-------

5d1a19a Swap BC layer for yield-ready and reclaim perf loss
@fabpot
Copy link
Contributor

fabpot commented Aug 19, 2024

Performance regression fixed in #4216

fabpot added a commit that referenced this pull request Sep 25, 2024
This PR was merged into the 3.x branch.

Discussion
----------

Add some tests

Closes #3691

The bug described in #3691 has been fixed via #3999.

This PR adds tests to prove it works now and to avoid future regressions.

Commits
-------

85c2ba5 Add some tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants