Skip to content

Commit 7874bf3

Browse files
committed
AG-43002: flat sequential applying patches instead of recursion
Squashed commit of the following: commit c57fa2f Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Mon Jun 9 17:05:56 2025 +0300 fixes commit 407261b Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Fri Jun 6 14:50:38 2025 +0300 Revert "tgz for tests" This reverts commit 3d7a141. commit 3d7a141 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Fri Jun 6 14:50:29 2025 +0300 tgz for tests commit bf9b719 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Fri Jun 6 14:50:13 2025 +0300 added tgz command commit 69c8dfe Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Fri Jun 6 14:49:08 2025 +0300 fixes commit 05d9450 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Thu Jun 5 22:32:16 2025 +0300 grammar errors commit e506778 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Thu Jun 5 22:31:20 2025 +0300 update desc commit 5773db5 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Thu Jun 5 22:30:13 2025 +0300 fixed desc commit a09c9c1 Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Thu Jun 5 22:22:33 2025 +0300 AG-43002: flat sequential applying patches instead of recursion commit 82c530e Author: Dmitriy Seregin <d.seregin@adguard.com> Date: Thu Jun 5 22:21:10 2025 +0300 version with tasks queue
1 parent 1333209 commit 7874bf3

3 files changed

Lines changed: 95 additions & 25 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-09
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 [AdguardBrowserExtension#3230].
14+
15+
[1.1.1]: https://github.com/AdguardTeam/DiffBuilder/compare/v1.1.0...v1.1.1
16+
[AdguardBrowserExtension#3230]: https://github.com/AdguardTeam/AdguardBrowserExtension/issues/3230
17+
818
## [1.1.0] - 2025-03-13
919

1020
### Changed

package.json

Lines changed: 3 additions & 2 deletions
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",
@@ -50,7 +50,8 @@
5050
"watch": "rollup -c rollup.config.ts --configPlugin typescript -w",
5151
"build:types": "tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outdir dist/types",
5252
"lint": "eslint --cache . && tsc --noEmit",
53-
"test": "jest"
53+
"test": "jest",
54+
"tgz": "pnpm pack --out diff-builder.tgz"
5455
},
5556
"dependencies": {
5657
"commander": "^11.1.0",

src/diff-updater/update.ts

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

55+
/**
56+
* Contains filter with updated or original content and a promise that resolves
57+
* if next patch is available, containing the next patch task and the updated
58+
* filter content.
59+
*/
60+
type AppliedPatchResult = {
61+
/**
62+
* The updated filter content after applying the patch, or just the original
63+
* filter content if the patch was not applied.
64+
*/
65+
filterContent: string;
66+
67+
/**
68+
* A promise that resolves via applied patch.
69+
* If the patch is not available, it resolves to null.
70+
*/
71+
nextPatchTask?: Promise<AppliedPatchResult | null>;
72+
};
73+
74+
/**
75+
* Extends the ApplyPatchParams interface to include a flag indicating whether
76+
* the patch is being applied recursively to hide it from the user.
77+
*/
78+
type ApplyPatchParamsWithRecursiveFlag = ApplyPatchParams & {
79+
/**
80+
* Indicates whether the patch is being applied recursively.
81+
*/
82+
isRecursiveUpdate: boolean,
83+
};
84+
5585
/**
5686
* If the differential update is not available the server may signal about that
5787
* by returning one of the following responses.
@@ -218,7 +248,8 @@ const checkPatchExpired = (diffPath: string): boolean => {
218248
* If `isFileHostedViaNetworkProtocol` is `false`, only 2xx status codes are
219249
* accepted, indicating a successful local or similar file request.
220250
* Any other status codes result in an error.
221-
* @param isRecursiveUpdate Indicates whether the function is called recursively.
251+
* @param isRecursiveUpdate Indicates whether the it is an applying multiple
252+
* patches in a row.
222253
* @param log A function that logs a message.
223254
*
224255
* @returns A promise that resolves to the content of the downloaded file
@@ -258,9 +289,12 @@ const downloadFile = async (
258289
}
259290
}
260291

261-
if ((response.status === AcceptableHttpStatusCodes.NotFound
262-
|| response.status === AcceptableHttpStatusCodes.NoContent) && !isRecursiveUpdate) {
263-
log('Update is not available.');
292+
if (response.status === AcceptableHttpStatusCodes.NotFound
293+
|| response.status === AcceptableHttpStatusCodes.NoContent
294+
) {
295+
if (!isRecursiveUpdate) {
296+
log('Update is not available.');
297+
}
264298
return null;
265299
}
266300

@@ -319,22 +353,25 @@ export const extractBaseUrl = (filterUrl: string): string => {
319353
* @param params The parameters for applying the patch {@link ApplyPatchParams}.
320354
*
321355
* @returns A promise that resolves to the updated filter content after applying the patch,
322-
* or null if there is no Diff-Path tag in the filter.
356+
* or null only if there is no Diff-Path tag in the filter - this is a special
357+
* case to prevent future unnecessary patch requests.
323358
*
324359
* @throws
325360
* 1. An {@link Error} if there is an error during
326361
* - the patch application process
327-
* - during network request.
362+
* - or during the network request.
328363
* 2. The {@link UnacceptableResponseError} if the network request returns an unacceptable status code.
329364
*/
330365
export const applyPatch = async (params: ApplyPatchParams): Promise<string | null> => {
331-
// Wrapper to hide the callStack parameter from the user.
332-
const applyPatchWrapper = async (innerParams: ApplyPatchParams & { callStack: number }): Promise<string | null> => {
366+
// Wrapper to hide the `isRecursiveUpdate` parameter from the user.
367+
const applyPatchWrapper = async (
368+
innerParams: ApplyPatchParamsWithRecursiveFlag,
369+
): Promise<AppliedPatchResult | null> => {
333370
const {
334371
filterUrl,
335372
filterContent,
336373
verbose = false,
337-
callStack,
374+
isRecursiveUpdate,
338375
} = innerParams;
339376

340377
const filterLines = splitByLines(filterContent);
@@ -348,7 +385,7 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
348385

349386
// If the patch has not expired yet, return the filter content without changes.
350387
if (!checkPatchExpired(diffPath)) {
351-
return filterContent;
388+
return { filterContent };
352389
}
353390

354391
const log = createLogger(verbose);
@@ -360,13 +397,13 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
360397
baseUrl,
361398
diffPath,
362399
baseUrl.startsWith('http://') || baseUrl.startsWith('https://'),
363-
callStack > 0,
400+
isRecursiveUpdate,
364401
log,
365402
);
366403

367404
// Update is not available yet.
368405
if (res === null) {
369-
return filterContent;
406+
return { filterContent };
370407
}
371408

372409
patch = res;
@@ -394,26 +431,48 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
394431
}
395432

396433
try {
397-
const recursiveUpdatedFilter = await applyPatchWrapper({
434+
const nextPatchTask = applyPatchWrapper({
398435
filterUrl,
399436
filterContent: updatedFilter,
400-
callStack: callStack + 1,
437+
isRecursiveUpdate: true,
401438
verbose,
402439
});
403440

404-
// It can be null if the filter dropped support for Diff-Path in new versions.
405-
if (recursiveUpdatedFilter === null) {
406-
// Then we return the filter with the last successfully applied patch.
407-
return updatedFilter;
408-
}
409-
410-
return recursiveUpdatedFilter;
441+
return { filterContent: updatedFilter, nextPatchTask };
411442
} catch (e) {
412443
// If we catch an error during the recursive update, we will return
413444
// the last successfully applied patch.
414-
return updatedFilter;
445+
return { filterContent: updatedFilter };
415446
}
416447
};
417448

418-
return applyPatchWrapper(Object.assign(params, { callStack: 0 }));
449+
const paramsWithRecursiveFlag = { ...params, isRecursiveUpdate: false };
450+
451+
let applyingPatchTask: Promise<AppliedPatchResult | null> | null = applyPatchWrapper(paramsWithRecursiveFlag);
452+
let latestFilter: string | null = null;
453+
454+
// Apply patches until there are no more new patches to apply.
455+
// This allows to apply multiple patches in a row if the filter supports it
456+
// without recursive calls, since applying patches can be a memory-intensive
457+
// operation, because of large amount of contexts for each function call.
458+
while (applyingPatchTask) {
459+
let freshFilter: AppliedPatchResult | null = null;
460+
461+
// Without try-await since we should throw an error in some cases and
462+
// all needed catches are inside the `applyPatchWrapper` function.
463+
// eslint-disable-next-line no-await-in-loop
464+
freshFilter = await applyingPatchTask;
465+
466+
// If there is no fresh filter, it means that the patch was not applied
467+
// or the filter does not support Diff-Path tag anymore, so we return
468+
// the latest filter content.
469+
if (!freshFilter) {
470+
return latestFilter;
471+
}
472+
473+
latestFilter = freshFilter.filterContent;
474+
applyingPatchTask = freshFilter.nextPatchTask || null;
475+
}
476+
477+
return latestFilter;
419478
};

0 commit comments

Comments
 (0)