Skip to content

Commit

Permalink
Add timeout option (#478)
Browse files Browse the repository at this point in the history
* Add timeout option

* Add docs

* Add test of timeout feature

* Add release notes
  • Loading branch information
ExplodingCabbage committed Jan 26, 2024
1 parent 1f1ec96 commit 533893d
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ Certain options can be provided in the `options` object of *any* method that cal
(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.)
* `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.

* `timeout`: a number of milliseconds after which the diffing algorithm will abort and return `undefined`. Supported by the same functions as `maxEditLength`.

### Defining custom diffing behaviors

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.
Expand Down
1 change: 1 addition & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [#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.
- [#451](https://github.com/kpdecker/jsdiff/pull/451) Added `diff.formatPatch`.
- [#450](https://github.com/kpdecker/jsdiff/pull/450) Added `diff.reversePatch`.
- [#478](https://github.com/kpdecker/jsdiff/pull/478) Added `timeout` option.

## v5.1.0

Expand Down
6 changes: 4 additions & 2 deletions src/diff/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Diff.prototype = {
if(options.maxEditLength) {
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
}
const maxExecutionTime = options.timeout ?? Infinity;
const abortAfterTimestamp = Date.now() + maxExecutionTime;

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

Expand Down Expand Up @@ -128,7 +130,7 @@ Diff.prototype = {
if (callback) {
(function exec() {
setTimeout(function() {
if (editLength > maxEditLength) {
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
return callback();
}

Expand All @@ -138,7 +140,7 @@ Diff.prototype = {
}, 0);
}());
} else {
while (editLength <= maxEditLength) {
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
let ret = execEditLength();
if (ret) {
return ret;
Expand Down
22 changes: 22 additions & 0 deletions test/diff/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,27 @@ describe('diff/array', function() {
{count: 1, value: [d], removed: undefined, added: true}
]);
});
it('Should terminate early if execution time exceeds `timeout` ms', function() {
// To test this, we also pass a comparator that hot sleeps as a way to
// artificially slow down execution so we reach the timeout.
function comparator(left, right) {
const start = Date.now();
// Hot-sleep for 10ms
while (Date.now() < start + 10) {
// Do nothing
}
return left === right;
}

// It will require 14 comparisons (140ms) to diff these arrays:
const arr1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const arr2 = ['a', 'b', 'c', 'd', 'x', 'y', 'z'];

// So with a timeout of 50ms, we are guaranteed failure:
expect(diffArrays(arr1, arr2, {comparator, timeout: 50})).to.be.undefined;

// But with a longer timeout, we expect success:
expect(diffArrays(arr1, arr2, {comparator, timeout: 1000})).not.to.be.undefined;
});
});
});

0 comments on commit 533893d

Please sign in to comment.