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

Formatter's is_decorated property is temporarily disabled during remove_format breaking concurrent threads #423

Open
vfazio opened this issue Apr 28, 2024 · 1 comment

Comments

@vfazio
Copy link

vfazio commented Apr 28, 2024

See python-poetry/poetry#9334

Poetry relies on the formatter's is_decorated property to be consistent when multiple threads query the property.

If one thread is in the middle of updating a section and has changed the formatter's is_decorated property via remove_format, a second thread may query it and get back an incorrect result.

If cleo is not thread-safe, the developers of poetry should be made aware, otherwise there may need to be changes to how remove_format and format work in relation to whether decorated output is supported in general vs decorated output is being emitted for the current format operation.

@vfazio vfazio changed the title Formatter's decorated property is temporarily disabled during remove_format breaking concurrent threads Formatter's is_decorated property is temporarily disabled during remove_format breaking concurrent threads Apr 28, 2024
@vfazio vfazio changed the title Formatter's is_decorated property is temporarily disabled during remove_format breaking concurrent threads Formatter's is_decorated property is temporarily disabled during remove_format breaking concurrent threads Apr 28, 2024
@vfazio
Copy link
Author

vfazio commented Apr 29, 2024

Note that this seems to be an issue because the Formatter object is a shared instance across the IO objects, in Poetry's case StreamOutput and child SectionOutput objects (see StreamOutput.section)

One "workaround" would be to duplicate the Formatter object instead of sharing the object. That way the StreamOutput object could query its Formatter object independent of what the SectionOutput objects are using. This would allow the sections to manipulate their formatters independently and existing code may "just work" without having to make:

Formatter.remove_format -> Formatter.format -> Formatter.format_and_wrap -> Formatter._apply_current_style

aware that while the formatter supports decorating, this operation should not be decorated.

If that's not ideal because formatter styles need to be kept in sync between the parent output object and child sections, maybe leverage a different private instance flag:

    def remove_format(self, text: str) -> str:
        self._suppress_decoration = True
        text = re.sub(r"\033\[[^m]*m", "", self.format(text))
        self._suppress_decoration = False

        return text

    def _apply_current_style(
        self, text: str, current: str, width: int, current_line_length: int
    ) -> tuple[str, int]:
        if not text:
            return "", current_line_length

        if not width:
            if self.is_decorated() and not self._suppress_decoration:
                return self._style_stack.current.apply(text), current_line_length

            return text, current_line_length

</snip>
        if self.is_decorated() and not self._suppress_decoration:
            apply = self._style_stack.current.apply
            text = "\n".join(map(apply, lines))
            
        return text, current_line_length

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

No branches or pull requests

1 participant