Skip to content

Sass hangs when a file is imported from a location with a large file tree #2548

@Tyoneb

Description

@Tyoneb

Issue

Summary

I have a scss file that contains an @import or @use statement that references a file in another location. That other location is provided using the --load-path CLI option. When I try to run sass on that single file, it hangs for what seems like an infinite amount of time (more than 10 minutes).

Details

  • Operating System: Windows
  • Runtime environment: NodeJS

Analysis

Root cause

I dug in your source code and understood that the root cause is the algorithm that walks up the source file location and caches the directories names with their "real" case (io.dart#_realCasePath). In my case, while walking up, it falls into a directory with more than 10000 subfolders and gets stuck there because the current implementation of io/js.dart#dirExists based on sequential synchronous calls to fs.existsSync and fs.statsSync is extremely costly (in my case, I measured ~1min for 1000 folders).

Based on the following comment in the code, you already know that the current implementation performs poorly:

TODO(nweiz): Use an SDK function for this when dart-lang/sdk#35370 and/or nodejs/node#24942 are fixed, or at least use FFI functions.

I looked at the mentioned issues and it seems that none of them get a lot of attention and they may never get implemented (especially on NodeJS side).

Proposal

Based on the discussions in the aforementioned issues, it seems important to avoid symbolic links resolution but I don't fully grasp why and I'm no expert on those, so the proposal below may not be valid...

I think you could leverage the NodeJS API fs.realpath.native to check whether or not the path contains a symbolic link, and if doesn't, you don't have to explore the entire file tree to get the proper case since realpath.native already returns it. If there is a symbolic link, then you can fall back to your existing mechanism.

I don't know Dart but here is what it could look like in JS:

const realPath = fs.realpathSync.native(path)
if (realPath.toUpperCase() === path.toUpperCase()) {
  // No symbolic link
  let reconstructed
  realPath.split(path.sep).forEach((part) => {
    reconstructed = reconstructed ? join(reconstructed, part) : part
    _realCasePath.putIfAbsent(reconstructed)
  })
  return realPath
}
return helper(path)

It's not perfect because it does not address the case of paths with symbolic links but it would noticeably speed up the path computation and caching for all other cases (especially if you get rid of the "walk up" mechanism as you already know the entire path has the correct case).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions