Skip to content

Commit 533893d

Browse files
Add timeout option (#478)
* Add timeout option * Add docs * Add test of timeout feature * Add release notes
1 parent 1f1ec96 commit 533893d

File tree

4 files changed

+29
-2
lines changed

4 files changed

+29
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ Certain options can be provided in the `options` object of *any* method that cal
168168
(Note that if the ONLY option you want to provide is a callback, you can pass the callback function directly as the `options` parameter instead of passing an object with a `callback` property.)
169169
* `maxEditLength`: a number specifying the maximum edit distance to consider between the old and new texts. If the edit distance is higher than this, jsdiff will return `undefined` instead of a diff. You can use this to limit the computational cost of diffing large, very different texts by giving up early if the cost will be huge. Works for functions that return change objects and also for `structuredPatch`, but not other patch-generation functions.
170170
171+
* `timeout`: a number of milliseconds after which the diffing algorithm will abort and return `undefined`. Supported by the same functions as `maxEditLength`.
172+
171173
### Defining custom diffing behaviors
172174
173175
If you need behavior a little different to what any of the text diffing functions above offer, you can roll your own by customizing both the tokenization behavior used and the notion of equality used to determine if two tokens are equal.

release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [#344](https://github.com/kpdecker/jsdiff/issues/344) `diffLines`, `createTwoFilesPatch`, and other patch-creation methods now take an optional `stripTrailingCr: true` option which causes Windows-style `\r\n` line endings to be replaced with Unix-style `\n` line endings before calculating the diff, just like GNU `diff`'s `--strip-trailing-cr` flag.
1111
- [#451](https://github.com/kpdecker/jsdiff/pull/451) Added `diff.formatPatch`.
1212
- [#450](https://github.com/kpdecker/jsdiff/pull/450) Added `diff.reversePatch`.
13+
- [#478](https://github.com/kpdecker/jsdiff/pull/478) Added `timeout` option.
1314

1415
## v5.1.0
1516

src/diff/base.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Diff.prototype = {
3333
if(options.maxEditLength) {
3434
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
3535
}
36+
const maxExecutionTime = options.timeout ?? Infinity;
37+
const abortAfterTimestamp = Date.now() + maxExecutionTime;
3638

3739
let bestPath = [{ oldPos: -1, lastComponent: undefined }];
3840

@@ -128,7 +130,7 @@ Diff.prototype = {
128130
if (callback) {
129131
(function exec() {
130132
setTimeout(function() {
131-
if (editLength > maxEditLength) {
133+
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
132134
return callback();
133135
}
134136

@@ -138,7 +140,7 @@ Diff.prototype = {
138140
}, 0);
139141
}());
140142
} else {
141-
while (editLength <= maxEditLength) {
143+
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
142144
let ret = execEditLength();
143145
if (ret) {
144146
return ret;

test/diff/array.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,27 @@ describe('diff/array', function() {
7575
{count: 1, value: [d], removed: undefined, added: true}
7676
]);
7777
});
78+
it('Should terminate early if execution time exceeds `timeout` ms', function() {
79+
// To test this, we also pass a comparator that hot sleeps as a way to
80+
// artificially slow down execution so we reach the timeout.
81+
function comparator(left, right) {
82+
const start = Date.now();
83+
// Hot-sleep for 10ms
84+
while (Date.now() < start + 10) {
85+
// Do nothing
86+
}
87+
return left === right;
88+
}
89+
90+
// It will require 14 comparisons (140ms) to diff these arrays:
91+
const arr1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
92+
const arr2 = ['a', 'b', 'c', 'd', 'x', 'y', 'z'];
93+
94+
// So with a timeout of 50ms, we are guaranteed failure:
95+
expect(diffArrays(arr1, arr2, {comparator, timeout: 50})).to.be.undefined;
96+
97+
// But with a longer timeout, we expect success:
98+
expect(diffArrays(arr1, arr2, {comparator, timeout: 1000})).not.to.be.undefined;
99+
});
78100
});
79101
});

0 commit comments

Comments
 (0)