Skip to content

Commit

Permalink
Add timeout function to limit processing time
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Sep 22, 2021
1 parent 197d4cf commit 1dd4be6
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 1 deletion.
20 changes: 20 additions & 0 deletions docs/api-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,26 @@ sharp('input.tiff')

Returns **Sharp**

## timeout

Set a timeout for processing, in seconds.
Use a value of zero to continue processing indefinitely, the default behaviour.

The clock starts when libvips opens an input image for processing.
Time spent waiting for a libuv thread to become available is not included.

### Parameters

* `options` **[Object][6]**

* `options.seconds` **[number][9]** Number of seconds after which processing will be stopped

Returns **Sharp**

**Meta**

* **since**: 0.29.2

[1]: #withmetadata

[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
Expand Down
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Requires libvips v8.11.3

### v0.29.2 - TBD

* Add `timeout` function to limit processing time.

* Ensure `sharp.versions` is populated from vendored libvips.

* Allow use of 'tif' to select TIFF output.
Expand Down
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ const Sharp = function (input, options) {
tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
timeoutSeconds: 0,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings
Expand Down
26 changes: 26 additions & 0 deletions lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,31 @@ function tile (options) {
return this._updateFormatOut('dz');
}

/**
* Set a timeout for processing, in seconds.
* Use a value of zero to continue processing indefinitely, the default behaviour.
*
* The clock starts when libvips opens an input image for processing.
* Time spent waiting for a libuv thread to become available is not included.
*
* @since 0.29.2
*
* @param {Object} options
* @param {number} options.seconds - Number of seconds after which processing will be stopped
* @returns {Sharp}
*/
function timeout (options) {
if (!is.plainObject(options)) {
throw is.invalidParameterError('options', 'object', options);
}
if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
this.options.timeoutSeconds = options.seconds;
} else {
throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
}
return this;
}

/**
* Update the output format unless options.force is false,
* in which case revert to input format.
Expand Down Expand Up @@ -1129,6 +1154,7 @@ module.exports = function (Sharp) {
gif,
raw,
tile,
timeout,
// Private
_updateFormatOut,
_setBooleanOption,
Expand Down
27 changes: 27 additions & 0 deletions src/common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,33 @@ namespace sharp {
return warning;
}

/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const seconds) {
if (seconds > 0) {
VipsImage *im = image.get_image();
if (im->progress_signal == NULL) {
int *timeout = VIPS_NEW(im, int);
*timeout = seconds;
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
vips_image_set_progress(im, TRUE);
}
}
}

/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
// printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
if (*timeout > 0 && progress->run >= *timeout) {
vips_image_set_kill(im, TRUE);
vips_error("timeout", "%d%% complete", progress->percent);
*timeout = 0;
}
}

/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
Expand Down
10 changes: 10 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ namespace sharp {
*/
std::string VipsWarningPop();

/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const timeoutSeconds);

/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);

/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,7 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->loop);

// Output
sharp::SetTimeout(image, baton->timeoutSeconds);
if (baton->fileOut.empty()) {
// Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
Expand Down Expand Up @@ -1451,6 +1452,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
std::string k = sharp::AttrAsStr(mdStrKeys, i);
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
}
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
// Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ struct PipelineBaton {
double withMetadataDensity;
std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs;
int timeoutSeconds;
std::unique_ptr<double[]> convKernel;
int convKernelWidth;
int convKernelHeight;
Expand Down Expand Up @@ -315,6 +316,7 @@ struct PipelineBaton {
withMetadata(false),
withMetadataOrientation(-1),
withMetadataDensity(0.0),
timeoutSeconds(0),
convKernelWidth(0),
convKernelHeight(0),
convKernelScale(0.0),
Expand Down
26 changes: 26 additions & 0 deletions test/unit/timeout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const assert = require('assert');

const sharp = require('../../');
const fixtures = require('../fixtures');

describe('Timeout', function () {
it('Will timeout after 1s when performing slow blur operation', () => assert.rejects(
() => sharp(fixtures.inputJpg)
.blur(100)
.timeout({ seconds: 1 })
.toBuffer(),
/timeout: [0-9]+% complete/
));

it('invalid object', () => assert.throws(
() => sharp().timeout('fail'),
/Expected object for options but received fail of type string/
));

it('invalid seconds', () => assert.throws(
() => sharp().timeout({ seconds: 'fail' }),
/Expected integer between 0 and 3600 for seconds but received fail of type string/
));
});

0 comments on commit 1dd4be6

Please sign in to comment.