Skip to content

Commit a09c9c1

Browse files
committed
AG-43002: flat sequential applying patches instead of recursion
1 parent 82c530e commit a09c9c1

3 files changed

Lines changed: 60 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.1] - 2025-06-06
9+
10+
### Changed
11+
12+
- Instead of recursively applying patches, now it will apply them one by one
13+
in chain to reduce memory usage [extension#5].
14+
15+
[1.1.1]: https://github.com/AdguardTeam/DiffBuilder/compare/v1.1.0...v1.1.1
16+
[extension#5]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3230
17+
818
## [1.1.0] - 2025-03-13
919

1020
### Changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adguard/diff-builder",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "A tool for generating differential updates for filter lists.",
55
"repository": {
66
"type": "git",

src/diff-updater/update.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ interface RcsOperation {
5252
numberOfLines: number;
5353
}
5454

55+
/**
56+
*
57+
*/
58+
type AppliedPatchResult = {
59+
/**
60+
* The updated filter content after applying the patch, or just the original
61+
* filter content if the patch was not applied.
62+
*/
63+
filterContent: string;
64+
65+
/**
66+
* A promise that resolves via applied patch.
67+
* If the patch is not available, it resolves to null.
68+
*/
69+
nextPatchTask?: Promise<AppliedPatchResult | null>;
70+
};
71+
72+
type ApplyPatchParamsWithRecursiveFlag = ApplyPatchParams & {
73+
/**
74+
* Indicates whether the patch is being applied recursively.
75+
*/
76+
isRecursiveUpdate: boolean,
77+
};
78+
5579
/**
5680
* If the differential update is not available the server may signal about that
5781
* by returning one of the following responses.
@@ -331,16 +355,15 @@ export const extractBaseUrl = (filterUrl: string): string => {
331355
* 2. The {@link UnacceptableResponseError} if the network request returns an unacceptable status code.
332356
*/
333357
export const applyPatch = async (params: ApplyPatchParams): Promise<string | null> => {
334-
const tasks: Promise<string | null>[] = [];
335-
336-
// Wrapper to hide the callStack parameter from the user.
337-
// eslint-disable-next-line max-len
338-
const applyPatchWrapper = async (innerParams: ApplyPatchParams & { callStack: number, tasks: Promise<string | null>[] }): Promise<string | null> => {
358+
// Wrapper to hide the `isRecursiveUpdate` parameter from the user.
359+
const applyPatchWrapper = async (
360+
innerParams: ApplyPatchParamsWithRecursiveFlag,
361+
): Promise<AppliedPatchResult | null> => {
339362
const {
340363
filterUrl,
341364
filterContent,
342365
verbose = false,
343-
callStack,
366+
isRecursiveUpdate,
344367
} = innerParams;
345368

346369
const filterLines = splitByLines(filterContent);
@@ -354,7 +377,7 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
354377

355378
// If the patch has not expired yet, return the filter content without changes.
356379
if (!checkPatchExpired(diffPath)) {
357-
return filterContent;
380+
return { filterContent };
358381
}
359382

360383
const log = createLogger(verbose);
@@ -366,13 +389,13 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
366389
baseUrl,
367390
diffPath,
368391
baseUrl.startsWith('http://') || baseUrl.startsWith('https://'),
369-
callStack > 0,
392+
isRecursiveUpdate,
370393
log,
371394
);
372395

373396
// Update is not available yet.
374397
if (res === null) {
375-
return filterContent;
398+
return { filterContent };
376399
}
377400

378401
patch = res;
@@ -400,55 +423,49 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
400423
}
401424

402425
try {
403-
const promise = applyPatchWrapper({
426+
const nextPatchTask = applyPatchWrapper({
404427
filterUrl,
405428
filterContent: updatedFilter,
406-
callStack: callStack + 1,
429+
isRecursiveUpdate: true,
407430
verbose,
408-
tasks,
409431
});
410432

411-
tasks.push(promise);
412-
413-
return updatedFilter;
433+
return { filterContent: updatedFilter, nextPatchTask };
414434
} catch (e) {
415435
// If we catch an error during the recursive update, we will return
416436
// the last successfully applied patch.
417-
return updatedFilter;
437+
return { filterContent: updatedFilter };
418438
}
419439
};
420440

421-
tasks.push(applyPatchWrapper(Object.assign(params, { callStack: 0, tasks })));
441+
const paramsWithRecursiveFlag = { ...params, isRecursiveUpdate: false };
422442

423-
let task;
443+
let task: Promise<AppliedPatchResult | null> | null = applyPatchWrapper(paramsWithRecursiveFlag);
424444
let latestFilter: string | null = null;
425445

426446
// Apply patches until there are no more tasks to process.
427447
// This allows to apply multiple patches in a row if the filter supports it
428448
// without recursive calls, since applying patches can be a memory-intensive
429449
// operation, because of large amount of contexts for each function call.
430-
do {
431-
task = tasks.shift();
432-
433-
let freshFilter = null;
450+
while (task) {
451+
let freshFilter: AppliedPatchResult | null = null;
434452

435-
try {
436-
// eslint-disable-next-line no-await-in-loop
437-
freshFilter = await task;
438-
} catch (e) {
439-
// If we catch an error during the patch application, we will return
440-
// the last successfully applied patch.
441-
return latestFilter;
442-
}
453+
// Without try-await since we should throw an error in some cases and
454+
// all needed catches is inside the `applyPatchWrapper` function.
455+
// eslint-disable-next-line no-await-in-loop
456+
freshFilter = await task;
443457

444458
// If there is no fresh filter, it means that the patch was not applied
445459
// or the filter does not support Diff-Path tag anymore.
446460
if (!freshFilter) {
461+
// If there is no result, it means that the patch was not applied
462+
// or the filter does not support Diff-Path tag anymore.
447463
return latestFilter;
448464
}
449465

450-
latestFilter = freshFilter;
451-
} while (task);
466+
latestFilter = freshFilter.filterContent;
467+
task = freshFilter.nextPatchTask || null;
468+
}
452469

453470
return latestFilter;
454471
};

0 commit comments

Comments
 (0)