tailwindlabs / tailwindcss-intellisense

Intelligent Tailwind CSS tooling for Visual Studio Code
2.74k stars 181 forks source link

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

Closed Newbie012 closed 1 month ago

Newbie012 commented 1 month ago

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 commented 1 month ago

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

https://github.com/tailwindlabs/tailwindcss-intellisense/blob/b715097ca91d1c69ec0ab6d81de8c9435c0919e4/packages/tailwindcss-language-service/src/util/classes.ts#L34

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 commented 1 month ago

This is a huge problem that needs fixing

thecrypticace commented 1 month ago

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

I looked into this months ago: https://github.com/tailwindlabs/tailwindcss-intellisense/pull/897#issuecomment-1892792615

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 commented 1 month ago

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

Newbie012 commented 1 month ago

@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 commented 1 month ago

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…