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

Add a per-importer cache for loads that aren't cacheable en masse #2219

Merged
merged 4 commits into from Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 39 additions & 35 deletions lib/src/async_import_cache.dart
Expand Up @@ -43,30 +43,20 @@ final class AsyncImportCache {
/// The `forImport` in each key is true when this canonicalization is for an
/// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule.
///
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
/// This cache covers loads that go through the entire chain of [_importers],
/// but it doesn't cover individual loads or loads in which any importer
/// accesses `containingUrl`. See also [_perImporterCanonicalizeCache].
final _canonicalizeCache =
<(Uri, {bool forImport}), AsyncCanonicalizeResult?>{};

/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
/// Like [_canonicalizeCache] but also includes the specific importer in the
/// key.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache = <(
Uri, {
bool forImport,
AsyncImporter baseImporter,
Uri? baseUrl
}),
AsyncCanonicalizeResult?>{};
/// This is used to cache both relative imports from the base importer and
/// individual importer results in the case where some other component of the
/// importer chain isn't cacheable.
final _perImporterCanonicalizeCache =
<(AsyncImporter, Uri, {bool forImport}), AsyncCanonicalizeResult?>{};

/// The parsed stylesheets for each canonicalized import URL.
final _importCache = <Uri, Stylesheet?>{};
Expand Down Expand Up @@ -154,14 +144,11 @@ final class AsyncImportCache {
}

if (baseImporter != null && url.scheme == '') {
var relativeResult = await putIfAbsentAsync(_relativeCanonicalizeCache, (
url,
forImport: forImport,
baseImporter: baseImporter,
baseUrl: baseUrl
), () async {
var (result, cacheable) = await _canonicalize(
baseImporter, baseUrl?.resolveUri(url) ?? url, baseUrl, forImport);
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var relativeResult = await putIfAbsentAsync(_perImporterCanonicalizeCache,
(baseImporter, resolvedUrl, forImport: forImport), () async {
var (result, cacheable) =
await _canonicalize(baseImporter, resolvedUrl, baseUrl, forImport);
assert(
cacheable,
"Relative loads should always be cacheable because they never "
Expand All @@ -181,17 +168,34 @@ final class AsyncImportCache {
// `canonicalize()` calls we've attempted are cacheable. Only if they are do
// we store the result in the cache.
var cacheable = true;
for (var importer in _importers) {
for (var i = 0; i < _importers.length; i++) {
var importer = _importers[i];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a check around here for the presence of _perImporterCanonicalizeCache, no?

Otherwise _canonicalize gets executed for all the importers previous to the one that returned a non-null and non-cacheable result

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, actually checking the cache is an important part of a caching scheme 😅

switch (await _canonicalize(importer, url, baseUrl, forImport)) {
case (var result?, true) when cacheable:
_canonicalizeCache[key] = result;
return result;

case (var result?, _):
return result;

case (_, false):
cacheable = false;
case (var result, true) when !cacheable:
_perImporterCanonicalizeCache[(importer, url, forImport: forImport)] =
result;
if (result != null) return result;

case (var result, false):
if (cacheable) {
// If this is the first uncacheable result, add all previous results
// to the per-importer cache so we don't have to re-run them for
// future uses of this importer.
for (var j = 0; i < j; j++) {
nex3 marked this conversation as resolved.
Show resolved Hide resolved
_perImporterCanonicalizeCache[(
_importers[j],
url,
forImport: forImport
)] = null;
}
cacheable = false;
}

if (result != null) return result;
}
}

Expand Down Expand Up @@ -324,7 +328,7 @@ final class AsyncImportCache {
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove((url, forImport: false));
_canonicalizeCache.remove((url, forImport: true));
_relativeCanonicalizeCache.removeWhere((key, _) => key.$1 == url);
_perImporterCanonicalizeCache.removeWhere((key, _) => key.$2 == url);
}

/// Clears the cached parse tree for the stylesheet with the given
Expand Down
76 changes: 40 additions & 36 deletions lib/src/import_cache.dart
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_import_cache.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: 37dd173d676ec6cf201a25b3cca9ac81d92b1433
// Checksum: 0d8b131cce06d4a2f42d01d06243693d16c8d1de
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -46,29 +46,19 @@ final class ImportCache {
/// The `forImport` in each key is true when this canonicalization is for an
/// `@import` rule. Otherwise, it's for a `@use` or `@forward` rule.
///
/// This cache isn't used for relative imports, because they depend on the
/// specific base importer. That's stored separately in
/// [_relativeCanonicalizeCache].
/// This cache covers loads that go through the entire chain of [_importers],
/// but it doesn't cover individual loads or loads in which any importer
/// accesses `containingUrl`. See also [_perImporterCanonicalizeCache].
final _canonicalizeCache = <(Uri, {bool forImport}), CanonicalizeResult?>{};

/// The canonicalized URLs for each non-canonical URL that's resolved using a
/// relative importer.
/// Like [_canonicalizeCache] but also includes the specific importer in the
/// key.
///
/// The map's keys have four parts:
///
/// 1. The URL passed to [canonicalize] (the same as in [_canonicalizeCache]).
/// 2. Whether the canonicalization is for an `@import` rule.
/// 3. The `baseImporter` passed to [canonicalize].
/// 4. The `baseUrl` passed to [canonicalize].
///
/// The map's values are the same as the return value of [canonicalize].
final _relativeCanonicalizeCache = <(
Uri, {
bool forImport,
Importer baseImporter,
Uri? baseUrl
}),
CanonicalizeResult?>{};
/// This is used to cache both relative imports from the base importer and
/// individual importer results in the case where some other component of the
/// importer chain isn't cacheable.
final _perImporterCanonicalizeCache =
<(Importer, Uri, {bool forImport}), CanonicalizeResult?>{};

/// The parsed stylesheets for each canonicalized import URL.
final _importCache = <Uri, Stylesheet?>{};
Expand Down Expand Up @@ -154,14 +144,11 @@ final class ImportCache {
}

if (baseImporter != null && url.scheme == '') {
var relativeResult = _relativeCanonicalizeCache.putIfAbsent((
url,
forImport: forImport,
baseImporter: baseImporter,
baseUrl: baseUrl
), () {
var (result, cacheable) = _canonicalize(
baseImporter, baseUrl?.resolveUri(url) ?? url, baseUrl, forImport);
var resolvedUrl = baseUrl?.resolveUri(url) ?? url;
var relativeResult = _perImporterCanonicalizeCache
.putIfAbsent((baseImporter, resolvedUrl, forImport: forImport), () {
var (result, cacheable) =
_canonicalize(baseImporter, resolvedUrl, baseUrl, forImport);
assert(
cacheable,
"Relative loads should always be cacheable because they never "
Expand All @@ -181,17 +168,34 @@ final class ImportCache {
// `canonicalize()` calls we've attempted are cacheable. Only if they are do
// we store the result in the cache.
var cacheable = true;
for (var importer in _importers) {
for (var i = 0; i < _importers.length; i++) {
var importer = _importers[i];
switch (_canonicalize(importer, url, baseUrl, forImport)) {
case (var result?, true) when cacheable:
_canonicalizeCache[key] = result;
return result;

case (var result?, _):
return result;

case (_, false):
cacheable = false;
case (var result, true) when !cacheable:
_perImporterCanonicalizeCache[(importer, url, forImport: forImport)] =
result;
if (result != null) return result;

case (var result, false):
if (cacheable) {
// If this is the first uncacheable result, add all previous results
// to the per-importer cache so we don't have to re-run them for
// future uses of this importer.
for (var j = 0; i < j; j++) {
_perImporterCanonicalizeCache[(
_importers[j],
url,
forImport: forImport
)] = null;
}
cacheable = false;
}

if (result != null) return result;
}
}

Expand Down Expand Up @@ -321,7 +325,7 @@ final class ImportCache {
void clearCanonicalize(Uri url) {
_canonicalizeCache.remove((url, forImport: false));
_canonicalizeCache.remove((url, forImport: true));
_relativeCanonicalizeCache.removeWhere((key, _) => key.$1 == url);
_perImporterCanonicalizeCache.removeWhere((key, _) => key.$2 == url);
}

/// Clears the cached parse tree for the stylesheet with the given
Expand Down