From 666c7e456682fdbe0e819a12c99cfd60be6feccf Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 25 Sep 2023 12:18:11 -0400 Subject: [PATCH] Batch reading content files to prevent `too many open files` error (#12079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor * Refactor * Batch content file reads in Node into groups of 500 We shouldn’t need to do this for our Rust code because it utilizes Rayon’s default thread pool for parallelism. This threadpool has roughly the number of cores as the number of available threads except when overridden. This generally is much, much lower than 500 and can be explicitly overridden via an env var to work around potential issues with open file descriptors if anyone ever runs into that. * Fix sequential/parallel flip * Update changelog --- CHANGELOG.md | 1 + src/lib/expandTailwindAtRules.js | 28 ++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 245179b79..092cc699f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix incorrectly generated CSS when using square brackets inside arbitrary properties ([#11709](https://github.com/tailwindlabs/tailwindcss/pull/11709)) - Make `content` optional for presets in TypeScript types ([#11730](https://github.com/tailwindlabs/tailwindcss/pull/11730)) - Handle variable colors that have variable fallback values ([#12049](https://github.com/tailwindlabs/tailwindcss/pull/12049)) +- Batch reading content files to prevent `too many open files` error ([#12079](https://github.com/tailwindlabs/tailwindcss/pull/12079)) ## [3.3.3] - 2023-07-13 diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index 835c1622f..2933d6f3e 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -145,14 +145,26 @@ export default function expandTailwindAtRules(context) { // getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen) // } } else { - await Promise.all( - context.changedContent.map(async ({ file, content, extension }) => { - let transformer = getTransformer(context.tailwindConfig, extension) - let extractor = getExtractor(context, extension) - content = file ? await fs.promises.readFile(file, 'utf8') : content - getClassCandidates(transformer(content), extractor, candidates, seen) - }) - ) + /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */ + let regexParserContent = [] + + for (let item of context.changedContent) { + let transformer = getTransformer(context.tailwindConfig, item.extension) + let extractor = getExtractor(context, item.extension) + regexParserContent.push([item, { transformer, extractor }]) + } + + const BATCH_SIZE = 500 + + for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) { + let batch = regexParserContent.slice(i, i + BATCH_SIZE) + await Promise.all( + batch.map(async ([{ file, content }, { transformer, extractor }]) => { + content = file ? await fs.promises.readFile(file, 'utf8') : content + getClassCandidates(transformer(content), extractor, candidates, seen) + }) + ) + } } env.DEBUG && console.timeEnd('Reading changed files')