Thanks to visit codestin.com
Credit goes to github.com

Skip to content

cloned TraceableStack operates differently than StackMiddleware #51564

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

Closed
rela589n opened this issue Sep 5, 2023 · 7 comments
Closed

cloned TraceableStack operates differently than StackMiddleware #51564

rela589n opened this issue Sep 5, 2023 · 7 comments

Comments

@rela589n
Copy link
Contributor

rela589n commented Sep 5, 2023

Symfony version(s) affected

6.x

Description

The StackMiddleware has it's own state ($offset field), which changes during the operation flow. Therefore, if at some point of time we clone this stack, we get a completely different instance, which may be safely used.

The problem lies in the absence of TraceableStack::__clone() method .
It is not possible to use cloned stack after main stack has been iterated over, since internally it references the same object.

See provided test for details.

How to reproduce

Below two tests are provided. First passess (StackMiddleware), second fails (TraceableStack).

class_exists(TraceableMiddleware::class);

final class TraceableStackCloneTest extends TestCase
{
    /** @dataProvider stackDataProvider */
    public function testClonedStackFramesFollowOriginalFrames(StackInterface $stack): void
    {
        $clonedStack = clone $stack;

        $nextFrame = $stack->next();
        $clonedStackNextFrame = $clonedStack->next();

        self::assertSame($nextFrame, $clonedStackNextFrame);
    }

    public function stackDataProvider(): array
    {
        $middlewareIterator = [
            $this->createMock(MiddlewareInterface::class),
            $this->createMock(MiddlewareInterface::class),
        ];

        return [
            [new StackMiddleware($middlewareIterator)], // passes
            [new TraceableStack(new StackMiddleware($middlewareIterator), $this->createMock(Stopwatch::class), 'command.bus', 'test')], // fails
        ];
    }
}

Possible Solution

I guess adding __clone method to TraceableStack would fix the issue:

    public function __clone(): void
    {
        $this->stack = clone $this->stack;
    }

Additional Context

PHP version: 8.2.8
Symfony messenger version: 6.2.7

@krciga22
Copy link
Contributor

Hi @rela589n,

Thanks for submitting this bug report. I tried out your test case and I can confirm the same bug is happening for Symfony version 6.3.4 and that the fix you suggested indeed makes the test case pass. I'm not sure whether this bug is necessary to fix since TraceableStack is marked as Internal, however, StackInterface does explicitly mention that all implementations must be cloneable, so it's likely this should be fixed.

In terms of the potential fix, I would also suggest cloning $stopwatch as well as it may have the same issue:

public function __clone(): void
{
    $this->stack = clone $this->stack;
    $this->stopwatch = clone $this->stopwatch;
}

Here's my reproduction of the test case including the potential fix (which causes it to pass): krciga22@9c71a0e

By the way, since TraceableStack is marked as Internal, I'm curious if you are utilizing it for Symfony development or for a separate project that utilizes Symfony framework and/or components?

Status: Reviewed

@stof
Copy link
Member

stof commented Sep 11, 2023

@krciga22 the Stopwatch must not be cloned, otherwise the traced timing won't be registered in the profiler due to registering them in a different Stopwatch.

@krciga22
Copy link
Contributor

@stof makes sense, I didn't realize that. TraceableStack is given a Stopwatch to use on construction and isn't expected to own/manage it, so it has no right to clone it.

@rela589n
Copy link
Contributor Author

Hi, @krciga22 ,
Thanks for the reply, I highly appreciate it

By the way, since TraceableStack is marked as Internal, I'm curious if you are utilizing it for Symfony development or for a separate project that utilizes Symfony framework and/or components?

I'm currently working on project built with Symfony framework. It has its own micro-framework built with Messenger component inside primarily used for cross-layer communication. Basically this is just a command bus with some additional middlewares providing common features for our specific needs.

The implementation of one more middleware required a source stack to be cloned. When running in prod mode everything worked fine, while running in dev-mode causes bug, since TraceableStack wrapper is added.

Current implementation uses following workaround for TraceableStack:

$closure = fn (): object => $this->stack = clone $this->stack;
$closure->call($stack);

It would be great if this bug was fixed so that we won't have to write dirty workarounds.

@krciga22
Copy link
Contributor

I see, definitely sounds like a plausible scenario that might occur when writing different middleware. I've worked on the fix for this based off of your suggested fix and will try to create a PR for it tonight.

In case you or @stof have any suggestions before I submit a PR, here's my latest commits: 6.4...krciga22:symfony:issue-51564

One thing I'm not sure of though is the line with class_exists(TraceableMiddleware::class);. I'm not sure if there's a better way to import the TraceableMiddleware class since it's in the same file as TraceableStack?

@stof
Copy link
Member

stof commented Sep 15, 2023

it should be inside the test at least, not at top-level

@krciga22
Copy link
Contributor

@stof , good call I thought there was something else funny about that line. I've gone ahead and updated the test as well as refactored it to (I hope) more accurately test that the cloned TraceableStack unstacks independently. I've also added another test to specifically test that the same Stopwatch is used by the clone and the original.

6.4...krciga22:symfony:issue-51564

nicolas-grekas added a commit that referenced this issue Sep 20, 2023
…tack independently (krciga22)

This PR was squashed before being merged into the 5.4 branch.

Discussion
----------

[Messenger] Fix cloned TraceableStack not unstacking the stack independently

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #51564
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

Fixed a bug with cloned `TraceableStack` not unstacking the stack independently from the original due to the __clone() method not yet being implemented. Clones of `StackMiddleware` currently unstack the stack independently, but not `TraceableStack`.

Commits
-------

b91bdc6 [Messenger] Fix cloned TraceableStack not unstacking the stack independently
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants