Skip to content

Console section is incorrectly updated #35012

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
wskorodecki opened this issue Dec 17, 2019 · 12 comments
Closed

Console section is incorrectly updated #35012

wskorodecki opened this issue Dec 17, 2019 · 12 comments

Comments

@wskorodecki
Copy link
Contributor

wskorodecki commented Dec 17, 2019

Symfony version(s) affected: 4.3.9, 4.4.1
PHP version: 7.3.12

Description
During alternating updates of individual sections, those previously displayed are incorrectly updated. This is somehow related to the number of lines displayed in between. Change 54 to 53 and then to 52 in for-loop to see the difference.

Invalid output

$ ./bin/console test:output
Section #1 after first overwrite.
Section #2, line #1.
Section #2, line #1.
Section #1 after second overwrite.
Section #2, line #1.
Section #2, line #2.
Section #2, line #3.
Section #2, line #4.
Section #2, line #5.
Test line #0.
Test line #1.
Test line #2.
Test line #3.
Test line #4.
Test line #5.
Test line #6.
Test line #7.
Test line #8.
Test line #9.
Test line #10.
Test line #11.
Test line #12.
Test line #13.
Test line #14.
Test line #15.
Test line #16.
Test line #17.
Test line #18.
Test line #19.
Test line #20.
Test line #21.
Test line #22.
Test line #23.
Test line #24.
Test line #25.
Test line #26.
Test line #27.
Test line #28.
Test line #29.
Test line #30.
Test line #31.
Test line #32.
Test line #33.
Test line #34.
Test line #35.
Test line #36.
Test line #37.
Test line #38.
Test line #39.
Test line #40.
Test line #41.
Test line #42.
Test line #43.
Test line #44.
Test line #45.
Test line #46.
Test line #47.
Test line #48.
Test line #49.
Test line #50.
Test line #51.
Test line #52.
Test line #53.

How to reproduce

<?php

declare(strict_types=1);

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;

class OutputTestCommand extends Command
{
    protected static $defaultName = 'test:output';

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        /** @var ConsoleOutputInterface $output */
        /** @var ConsoleSectionOutput $s1 */
        $s1 = $output->section();
        $s1->writeln('Section #1, line #1.');

        /** @var ConsoleSectionOutput $s2 */
        $s2 = $output->section();
        for ($i = 1; $i <= 5; $i++) {
            $s2->writeln("Section #2, line #$i.");
        }

        $s1->overwrite('Section #1 after first overwrite.');

        $s3 = $output->section();
        for ($i = 0; $i < 54; ++$i) {
            $s3->writeln("Test line #$i.");
        }

        // Now the first section will not be updated correctly.
        $s1->overwrite('Section #1 after second overwrite.');
    }
}
@fancyweb
Copy link
Contributor

Status: reviewed.

This bug is related to the height of the terminal. With ANSI escape codes, we move the cursor up to erase the section content to replace it. It looks like this strategy does not work on the part of the terminal that is "not visible".

@carsonbot
Copy link

Hey, thanks for your report!
There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?

@carsonbot
Copy link

Just a quick reminder to make a comment on this. If I don't hear anything I'll close this.

@InvisibleSmiley
Copy link
Contributor

Still an issue with Symfony 5.2.1.

@carsonbot carsonbot removed the Stalled label Jan 4, 2021
@korve
Copy link
Contributor

korve commented Mar 18, 2021

@fancyweb do you see any solution to this? For me the section overwrite functionality seems fundamentally flawed. The cursor can only be moved within the view port (width: max number of columns or characters, height: max number of lines). So when scrolling up via the \x1b[T ANSI escape sequence a new line is inserted at the top of the buffer. There is no way of getting the previous output and insert it at the top when deleting a line on the bottom.

@carsonbot
Copy link

Hey, thanks for your report!
There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?

@carsonbot
Copy link

Friendly ping? Should this still be open? I will close if I don't hear anything.

@carsonbot
Copy link

Hey,

I didn't hear anything so I'm going to close it. Feel free to comment if this is still relevant, I can always reopen!

@lyrixx
Copy link
Member

lyrixx commented Jun 2, 2023

hello, I re-open this as the issue has not been fixed. I created a standalone reproducer that works outside full stack framework:

<?php

use Symfony\Component\Console\Output\ConsoleOutput;

require __DIR__ . '/vendor/autoload.php';

$output = new ConsoleOutput();

$s1 = $output->section();
$s1->writeln('Section #1, line #1.');

$s2 = $output->section();
for ($i = 1; $i <= 5; $i++) {
    $s2->writeln("Section #2, line #$i.");
}

$s1->overwrite('Section #1 after first overwrite.');

$s3 = $output->section();
for ($i = 1; $i < ($argv[1] ?? 10); ++$i) {
    $s3->writeln("Test line #$i.");
}

// Now the first section will not be updated correctly.
$s1->overwrite('Section #1 after second overwrite.');
php index.php 10

increase 10 until the bug occurs

I also created another reproducer, more fun (actually, this code I tried to put in production...) :)

<?php

use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleSectionOutput;

require __DIR__ . '/vendor/autoload.php';

function run(ConsoleSectionOutput $section) {
    for ($i=0; $i < 10; $i++) {
        $section->writeln(bin2hex(random_bytes(16)));
        Fiber::suspend();
    }
}

$output = new ConsoleOutput();

// Ensure you screen has at least 20 lines of height
$maxHeight = 5;

$output->section()->writeln('<info>Building for Linux</info>');

$section1 = $output->section();
$section1->setMaxHeight($maxHeight);

$output->section()->writeln('<info>Building for Darwin</info>');

$section2 = $output->section();
$section2->setMaxHeight($maxHeight);

$output->section()->writeln('<info>Building for Windows</info>');

$section3 = $output->section();
$section3->setMaxHeight($maxHeight);

$f1 = new Fiber(fn() => run($section1));
$f2 = new Fiber(fn() => run($section2));
$f3 = new Fiber(fn() => run($section3));

$f1->start();
$f2->start();
$f3->start();

while ($f1->isSuspended() || $f2->isSuspended() || $f3->isSuspended()) {
    if ($f1->isSuspended()) {
        $f1->resume();
    }
    if ($f2->isSuspended()) {
        $f2->resume();
    }
    if ($f3->isSuspended()) {
        $f3->resume();
    }
    usleep(100_000);
}

$output->writeln('<info>Done</info>');

@joelwurtz
Copy link
Contributor

Resolving this is not easy and it certainly needs a new behavior for sections, let me explain :

As an example we have :

  • 3 sections
  • A max terminal height of 50
  • we write 30 lines to first section
  • we write 20 lines to second section
  • we write 20 lines to third section
  • we add 20 lines to the first section

In this case we can erase 50 lines max (as we cannot write on a not visible part of the terminal) and output 30 lines of the first section (20 lines added, 10 erased) and then the others section.

  • then we add 20 the third section

so we see 10 lines of the second and 40 lines of the third) -> no problem

  • now we add 10 lines to the first section

History will be bugged and there is no possibility to fix that as the second section will be rewrited again but not in the visible part, then we will have it multiple times in the history

Also user will never see the update to the first section as it will always be out of the view part

A way to partially fix this would be to reorder session when we write to them, so the last updated section will always be in last in the terminal, it will not fix the history but at least it would allow user to see what happens in last

Another solution would be to use the maxHeight parameter of the section and auto adjust their size given the number of sections, and to avoid having lost output we could, when all the sections are finished clear everything and redraw all output to have a clean history

Not sure what is wanted here ? (for me the last solution is the best but it should be available as an option)

@lyrixx
Copy link
Member

lyrixx commented Aug 11, 2023

IMHO we can close this once #51355 is merged. the issue was closed, whithout activity, and I reopened it with a special use-case, and @joelwurtz fixed it.

@stof
Copy link
Member

stof commented Aug 11, 2023

@lyrixx I agree with that. But I opened a documentation issue about better explaining the limitation that your section-based UI can only mutate the visible part of the terminal content

fabpot added a commit that referenced this issue Aug 12, 2023
…ax height (joelwurtz)

This PR was merged into the 6.3 branch.

Discussion
----------

[Console] fix section output when multiples section with max height

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Partial fix for #35012
| License       | MIT
| Doc PR        |

This pr does not fix #35012

However it fixes the second script wrote by `@lyrixx` (#35012 (comment)) with fiber and parallel output.

In short when there was multiple sections with max height, it was not taking into account the max height of other sections when doing a full rewrite, leading to erase more lines than intended.

Commits
-------

28e9da6 fix(console): fix section output when multiples section with max height
@lyrixx lyrixx closed this as completed Aug 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants