Add timeout function to limit processing time

This commit is contained in:
Lovell Fuller 2021-09-22 10:33:46 +01:00
parent 197d4cf835
commit 1dd4be670d
10 changed files with 117 additions and 1 deletions

View File

@ -542,6 +542,26 @@ sharp('input.tiff')
Returns **Sharp** 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 [1]: #withmetadata
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

View File

@ -6,6 +6,8 @@ Requires libvips v8.11.3
### v0.29.2 - TBD ### v0.29.2 - TBD
* Add `timeout` function to limit processing time.
* Ensure `sharp.versions` is populated from vendored libvips. * Ensure `sharp.versions` is populated from vendored libvips.
* Allow use of 'tif' to select TIFF output. * Allow use of 'tif' to select TIFF output.

File diff suppressed because one or more lines are too long

View File

@ -273,6 +273,7 @@ const Sharp = function (input, options) {
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
tileCentre: false, tileCentre: false,
tileId: 'https://example.com/iiif', tileId: 'https://example.com/iiif',
timeoutSeconds: 0,
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings

View File

@ -975,6 +975,31 @@ function tile (options) {
return this._updateFormatOut('dz'); 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, * Update the output format unless options.force is false,
* in which case revert to input format. * in which case revert to input format.
@ -1129,6 +1154,7 @@ module.exports = function (Sharp) {
gif, gif,
raw, raw,
tile, tile,
timeout,
// Private // Private
_updateFormatOut, _updateFormatOut,
_setBooleanOption, _setBooleanOption,

View File

@ -610,6 +610,33 @@ namespace sharp {
return warning; 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 Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed. within the input image, applying the given gravity during an embed.

View File

@ -250,6 +250,16 @@ namespace sharp {
*/ */
std::string VipsWarningPop(); 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 Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed. within the input image, applying the given gravity during an embed.

View File

@ -764,6 +764,7 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->loop); baton->loop);
// Output // Output
sharp::SetTimeout(image, baton->timeoutSeconds);
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
@ -1451,6 +1452,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
std::string k = sharp::AttrAsStr(mdStrKeys, i); std::string k = sharp::AttrAsStr(mdStrKeys, i);
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k))); baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
} }
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
// Format-specific // Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");

View File

@ -182,6 +182,7 @@ struct PipelineBaton {
double withMetadataDensity; double withMetadataDensity;
std::string withMetadataIcc; std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs; std::unordered_map<std::string, std::string> withMetadataStrs;
int timeoutSeconds;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
@ -315,6 +316,7 @@ struct PipelineBaton {
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
withMetadataDensity(0.0), withMetadataDensity(0.0),
timeoutSeconds(0),
convKernelWidth(0), convKernelWidth(0),
convKernelHeight(0), convKernelHeight(0),
convKernelScale(0.0), convKernelScale(0.0),

26
test/unit/timeout.js Normal file
View File

@ -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/
));
});