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

bradlc.vscode-tailwindcss-0.10.5 makes CPU go Brrrrrrrr #973

Closed
Newbie012 opened this issue May 23, 2024 · 6 comments
Closed

bradlc.vscode-tailwindcss-0.10.5 makes CPU go Brrrrrrrr #973

Newbie012 opened this issue May 23, 2024 · 6 comments
Assignees

Comments

@Newbie012
Copy link

What version of Tailwind CSS are you using?

3.4.3

What build tool (or framework if it abstracts the build tool) are you using?

postcss 8.4.38

What version of Node.js are you using?

v20.10.0

What browser are you using?

N/A

What operating system are you using?

macOS

Reproduction URL

https://github.com/Newbie012/bug-tailwindcss-cpu

Describe your issue

I have a file with a lot of tailwind classes. Whenever I open it, the tailwind (vscode) extension freezes. You can see in the screenshot 2 processes (it opens a new one without closing the previous process every time I restart vscode) that points to the vscode extension.

image

Steps to Reproduce

  1. Clone the repository - https://github.com/Newbie012/bug-tailwindcss-cpu
  2. Run pnpm install
  3. Make sure your Activity Monitor is open.
  4. Open component.ts
  5. Try to hover on classes, you'll see "Loading...". Open Activity Monitor and see how "Code Helper" skyrocket to 99%~ CPU.

Please let me know if I missed any details so I can share it.

Thanks!

@Newbie012
Copy link
Author

Newbie012 commented May 24, 2024

After debugging the extension, I've figured out the source of the issue. It starts with this line:

for (let containerMatch of text.matchAll(containerRegex)) {

It seems like .matchAll freezes when I only hover on a class. This is because the extension takes only a portion of the file (which makes sense) and only then it tries to match against the predefined regex. In my case, I use tailwind-variants which suggests a regex that doesn't play nicely with incomplete code.

I was able to make a very minimal reproduction:

const text = `tv({
    base: "disabled:pointer-events-none disabled:opacity-50",
    compoundVariants: [
`
const regex = new RegExp("tv\\((([^()]*|\\([^()]*\\))*)\\)", "dg");

const matches = text.matchAll(regex);

// this will freeze the main thread.
matches.next()

While the regex issue is more related to tailwind-variants than tailwindcss itself, I still believe that something should be done in order to prevent high CPU usage.

@bitabs
Copy link

bitabs commented May 26, 2024

This is a huge problem that needs fixing

@thecrypticace thecrypticace transferred this issue from tailwindlabs/tailwindcss May 27, 2024
@thecrypticace
Copy link
Contributor

The regex is prone to catastrophic backtracking. The only fix is to actually write a different regex.

I looked into this months ago: #897 (comment)

In JS there is no way to "cancel" a regex once it's started searching. If you sample the process it'll actually be stuck inside V8 itself (the JS engine that powers Node).

Node does have worker threads so that might be a way to keep the main thread responsive (and maybe terminate a thread that's stuck) but I actually have no idea if that would work. I'll have to look into it.

@thecrypticace thecrypticace self-assigned this May 27, 2024
@thecrypticace
Copy link
Contributor

Going to close in favor of #963 given that it's the same problem

@Newbie012
Copy link
Author

@thecrypticace I ran some tests and while I was unable to .terminate a worker, I could stop it by calling process.exit, but I didn't want to kill the main process, so I created a "middleware" worker.

In a nutshell: Main Thread -> Timeout Worker -> Regex Worker

The timeout worker is responsible for forwarding the messages between the regex worker and the main process, while adding a "kill switch" if the regex worker fails to respond in a given time.

@thecrypticace
Copy link
Contributor

I am able to .terminate() a worker that's stuck in v8. Works fine from the main thread. The problem is more about creating a worker pool and making sure requests are appropriately distributed to non-busy workers, failed requests can be replayed, etc…

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

3 participants