diff --git a/.jshintrc b/.jshintrc index c751c36a..6c2235e4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -5,6 +5,7 @@ "maxcomplexity": 13, "globals": { "before": true, + "after": true, "describe": true, "it": true } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbf14c19..040017cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,8 +41,8 @@ Any change that modifies the existing public API should be added to the relevant | Release | WIP branch | | ------: | :--------- | -| v0.12.0 | look | | v0.13.0 | mind | +| v0.14.0 | needle | Please squash your changes into a single commit using a command like `git rebase -i upstream/`. diff --git a/docs/api.md b/docs/api.md index 294ebdee..28e4c6f4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -590,19 +590,26 @@ An Object containing the version numbers of libvips and, on Linux, its dependenc ### Utilities -#### sharp.cache([memory], [items]) +#### sharp.cache([options]) -If `memory` or `items` are provided, set the limits of _libvips'_ operation cache. +If `options` is provided, sets the limits of _libvips'_ operation cache. -* `memory` is the maximum memory in MB to use for this cache, with a default value of 100 -* `items` is the maximum number of operations to cache, with a default value of 500 +* `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50 +* `options.files` is the maximum number of files to hold open, with a default value of 20 +* `options.items` is the maximum number of operations to cache, with a default value of 100 + +`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching. This method always returns cache statistics, useful for determining how much working memory is required for a particular task. ```javascript -var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 } -sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 } -sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200} +var stats = sharp.cache(); +``` + +```javascript +sharp.cache( { items: 200 } ); +sharp.cache( { files: 0 } ); +sharp.cache(false); ``` #### sharp.concurrency([threads]) diff --git a/docs/changelog.md b/docs/changelog.md index f4e61e9a..41a67fd7 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,10 @@ # Changelog +### v0.13 - "*mind*" + +* Control number of open files in libvips' cache; breaks existing `cache` behaviour. + [#315](https://github.com/lovell/sharp/issues/315) + ### v0.12 - "*look*" #### v0.12.2 - 16th January 2016 diff --git a/index.js b/index.js index 268796d0..9ee6e5d0 100644 --- a/index.js +++ b/index.js @@ -834,18 +834,25 @@ Sharp.prototype.clone = function() { return clone; }; -/* - Get and set cache memory and item limits +/** + Get and set cache memory, file and item limits */ -module.exports.cache = function(memory, items) { - if (typeof memory !== 'number' || Number.isNaN(memory)) { - memory = null; +module.exports.cache = function(options) { + if (typeof options === 'boolean') { + if (options) { + // Default cache settings of 50MB, 20 files, 100 items + return sharp.cache(50, 20, 100); + } else { + return sharp.cache(0, 0, 0); + } + } else if (typeof options === 'object') { + return sharp.cache(options.memory, options.files, options.items); + } else { + return sharp.cache(); } - if (typeof items !== 'number' || Number.isNaN(items)) { - items = null; - } - return sharp.cache(memory, items); }; +// Ensure default cache settings are set +module.exports.cache(true); /* Get and set size of thread pool diff --git a/package.json b/package.json index 1d337f2f..248db5c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharp", - "version": "0.12.2", + "version": "0.13.0", "author": "Lovell Fuller ", "contributors": [ "Pierre Inglebert ", diff --git a/src/sharp.cc b/src/sharp.cc index 7849b141..75fbd5bd 100644 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -11,10 +11,6 @@ NAN_MODULE_INIT(init) { vips_init("sharp"); - // Set libvips operation cache limits - vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB - vips_cache_set_max(500); // 500 operations - // Methods available to JavaScript Nan::Set(target, Nan::New("metadata").ToLocalChecked(), Nan::GetFunction(Nan::New(metadata)).ToLocalChecked()); diff --git a/src/utilities.cc b/src/utilities.cc index 1a20616a..e6d1c138 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -24,33 +24,49 @@ using Nan::To; using Nan::Utf8String; /* - Get and set cache memory and item limits + Get and set cache limits */ NAN_METHOD(cache) { HandleScope(); - // Set cache memory limit + // Set memory limit if (info[0]->IsInt32()) { vips_cache_set_max_mem(To(info[0]).FromJust() * 1048576); } - - // Set cache items limit + // Set file limit if (info[1]->IsInt32()) { - vips_cache_set_max(To(info[1]).FromJust()); + vips_cache_set_max_files(To(info[1]).FromJust()); + } + // Set items limit + if (info[2]->IsInt32()) { + vips_cache_set_max(To(info[2]).FromJust()); } - // Get cache statistics - Local cache = New(); - Set(cache, New("current").ToLocalChecked(), + // Get memory stats + Local memory = New(); + Set(memory, New("current").ToLocalChecked(), New(static_cast(round(vips_tracked_get_mem() / 1048576))) ); - Set(cache, New("high").ToLocalChecked(), + Set(memory, New("high").ToLocalChecked(), New(static_cast(round(vips_tracked_get_mem_highwater() / 1048576))) ); - Set(cache, New("memory").ToLocalChecked(), + Set(memory, New("max").ToLocalChecked(), New(static_cast(round(vips_cache_get_max_mem() / 1048576))) ); - Set(cache, New("items").ToLocalChecked(), New(vips_cache_get_max())); + // Get file stats + Local files = New(); + Set(files, New("current").ToLocalChecked(), New(vips_tracked_get_files())); + Set(files, New("max").ToLocalChecked(), New(vips_cache_get_max_files())); + + // Get item stats + Local items = New(); + Set(items, New("current").ToLocalChecked(), New(vips_cache_get_size())); + Set(items, New("max").ToLocalChecked(), New(vips_cache_get_max())); + + Local cache = New(); + Set(cache, New("memory").ToLocalChecked(), memory); + Set(cache, New("files").ToLocalChecked(), files); + Set(cache, New("items").ToLocalChecked(), items); info.GetReturnValue().Set(cache); } @@ -262,6 +278,7 @@ NAN_METHOD(_maxColourDistance) { vips_object_local(hook, imagePremultipliedNoAlpha2); image2 = imagePremultipliedNoAlpha2; } + // Calculate colour distance VipsImage *difference; if (vips_dE00(image1, image2, &difference, nullptr)) { @@ -276,5 +293,10 @@ NAN_METHOD(_maxColourDistance) { return ThrowError(vips_error_buffer()); } g_object_unref(hook); + + // Clean up libvips' per-request data and threads + vips_error_clear(); + vips_thread_shutdown(); + info.GetReturnValue().Set(New(maxColourDistance)); } diff --git a/test/bench/perf.js b/test/bench/perf.js index 7752cdd8..687f20dc 100644 --- a/test/bench/perf.js +++ b/test/bench/perf.js @@ -34,7 +34,7 @@ var magickFilterBilinear = 'Triangle'; var magickFilterBicubic = 'Lanczos'; // Disable libvips cache to ensure tests are as fair as they can be -sharp.cache(0); +sharp.cache(false); // Enable use of SIMD sharp.simd(true); diff --git a/test/unit/alpha.js b/test/unit/alpha.js index ceeeb865..ecae36ed 100644 --- a/test/unit/alpha.js +++ b/test/unit/alpha.js @@ -4,8 +4,6 @@ var assert = require('assert'); var fixtures = require('../fixtures'); var sharp = require('../../index'); -sharp.cache(0); - describe('Alpha transparency', function() { it('Flatten to black', function(done) { diff --git a/test/unit/blur.js b/test/unit/blur.js index 15f50404..e56aee08 100644 --- a/test/unit/blur.js +++ b/test/unit/blur.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Blur', function() { it('specific radius 1', function(done) { diff --git a/test/unit/clone.js b/test/unit/clone.js index 6e826827..42dbd7ce 100644 --- a/test/unit/clone.js +++ b/test/unit/clone.js @@ -6,9 +6,15 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Clone', function() { + + before(function() { + sharp.cache(false); + }); + after(function() { + sharp.cache(true); + }); + it('Read from Stream and write to multiple Streams', function(done) { var finishEventsExpected = 2; // Output stream 1 @@ -55,4 +61,5 @@ describe('Clone', function() { // Go fs.createReadStream(fixtures.inputJpg).pipe(rotator); }); + }); diff --git a/test/unit/colourspace.js b/test/unit/colourspace.js index 5b2de725..af07ff8d 100644 --- a/test/unit/colourspace.js +++ b/test/unit/colourspace.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Colour space conversion', function() { it('To greyscale', function(done) { diff --git a/test/unit/crop.js b/test/unit/crop.js index 4a616bcb..04e72d04 100644 --- a/test/unit/crop.js +++ b/test/unit/crop.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Crop gravities', function() { var testSettings = [ diff --git a/test/unit/embed.js b/test/unit/embed.js index 3dd29d9f..aa4e30cd 100644 --- a/test/unit/embed.js +++ b/test/unit/embed.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Embed', function() { it('JPEG within PNG, no alpha channel', function(done) { diff --git a/test/unit/extract.js b/test/unit/extract.js index fda4d4a5..608557cd 100644 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Partial image extraction', function() { describe('using the legacy extract(top,left,width,height) syntax', function () { it('JPEG', function(done) { diff --git a/test/unit/gamma.js b/test/unit/gamma.js index b109e28e..1c29cc6a 100644 --- a/test/unit/gamma.js +++ b/test/unit/gamma.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Gamma correction', function() { it('value of 0.0 (disabled)', function(done) { diff --git a/test/unit/interpolation.js b/test/unit/interpolation.js index 6349b149..9945cfc4 100644 --- a/test/unit/interpolation.js +++ b/test/unit/interpolation.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Interpolation', function() { it('nearest neighbour', function(done) { diff --git a/test/unit/io.js b/test/unit/io.js index 16afccf7..1f50e5f0 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -6,10 +6,15 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Input/output', function() { + before(function() { + sharp.cache(false); + }); + after(function() { + sharp.cache(true); + }); + it('Read from File and write to Stream', function(done) { var writable = fs.createWriteStream(fixtures.outputJpg); writable.on('finish', function() { diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 595502aa..956ed304 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -8,8 +8,6 @@ var icc = require('icc'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Image metadata', function() { it('JPEG', function(done) { diff --git a/test/unit/negate.js b/test/unit/negate.js index c8936934..bffaca7d 100644 --- a/test/unit/negate.js +++ b/test/unit/negate.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Negate', function() { it('negate (jpeg)', function(done) { sharp(fixtures.inputJpg) diff --git a/test/unit/normalize.js b/test/unit/normalize.js index deda0851..8f469976 100644 --- a/test/unit/normalize.js +++ b/test/unit/normalize.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Normalization', function () { it('uses the same prototype for both spellings', function () { diff --git a/test/unit/overlay.js b/test/unit/overlay.js index 9eff6dc3..b0a94896 100644 --- a/test/unit/overlay.js +++ b/test/unit/overlay.js @@ -4,8 +4,6 @@ var assert = require('assert'); var fixtures = require('../fixtures'); var sharp = require('../../index'); -sharp.cache(0); - // Helpers var getPaths = function(baseName, extension) { if (typeof extension === 'undefined') { diff --git a/test/unit/resize.js b/test/unit/resize.js index 7cfd6bae..f991ed05 100644 --- a/test/unit/resize.js +++ b/test/unit/resize.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Resize dimensions', function() { it('Exact crop', function(done) { diff --git a/test/unit/rotate.js b/test/unit/rotate.js index 83cce49b..b1e6d7a6 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Rotation', function() { ['Landscape', 'Portrait'].forEach(function(orientation) { diff --git a/test/unit/sharpen.js b/test/unit/sharpen.js index c989b901..22c75e24 100644 --- a/test/unit/sharpen.js +++ b/test/unit/sharpen.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Sharpen', function() { it('specific radius 10', function(done) { diff --git a/test/unit/threshold.js b/test/unit/threshold.js index 56d674e6..7609e582 100644 --- a/test/unit/threshold.js +++ b/test/unit/threshold.js @@ -5,8 +5,6 @@ var assert = require('assert'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - describe('Threshold', function() { it('threshold 1 jpeg', function(done) { sharp(fixtures.inputJpg) diff --git a/test/unit/tile.js b/test/unit/tile.js index 46bce654..6b0c797e 100644 --- a/test/unit/tile.js +++ b/test/unit/tile.js @@ -10,8 +10,6 @@ var rimraf = require('rimraf'); var sharp = require('../../index'); var fixtures = require('../fixtures'); -sharp.cache(0); - // Verifies all tiles in a given dz output directory are <= size var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) { // Get levels diff --git a/test/unit/util.js b/test/unit/util.js index a7efbf31..dde22caf 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -10,20 +10,48 @@ describe('Utilities', function() { describe('Cache', function() { it('Can be disabled', function() { - var cache = sharp.cache(0, 0); - assert.strictEqual(0, cache.memory); - assert.strictEqual(0, cache.items); + sharp.cache(false); + var cache = sharp.cache(false); + assert.strictEqual(cache.memory.current, 0); + assert.strictEqual(cache.memory.max, 0); + assert.strictEqual(typeof cache.memory.high, 'number'); + assert.strictEqual(cache.files.current, 0); + assert.strictEqual(cache.files.max, 0); + assert.strictEqual(cache.items.current, 0); + assert.strictEqual(cache.items.max, 0); }); - it('Can be set to a maximum of 50MB and 500 items', function() { - var cache = sharp.cache(50, 500); - assert.strictEqual(50, cache.memory); - assert.strictEqual(500, cache.items); + it('Can be enabled with defaults', function() { + var cache = sharp.cache(true); + assert.strictEqual(cache.memory.max, 50); + assert.strictEqual(cache.files.max, 20); + assert.strictEqual(cache.items.max, 100); + }); + it('Can be set to zero', function() { + var cache = sharp.cache({ + memory: 0, + files: 0, + items: 0 + }); + assert.strictEqual(cache.memory.max, 0); + assert.strictEqual(cache.files.max, 0); + assert.strictEqual(cache.items.max, 0); + }); + it('Can be set to a maximum of 10MB, 100 files and 1000 items', function() { + var cache = sharp.cache({ + memory: 10, + files: 100, + items: 1000 + }); + assert.strictEqual(cache.memory.max, 10); + assert.strictEqual(cache.files.max, 100); + assert.strictEqual(cache.items.max, 1000); }); it('Ignores invalid values', function() { - sharp.cache(50, 500); + sharp.cache(true); var cache = sharp.cache('spoons'); - assert.strictEqual(50, cache.memory); - assert.strictEqual(500, cache.items); + assert.strictEqual(cache.memory.max, 50); + assert.strictEqual(cache.files.max, 20); + assert.strictEqual(cache.items.max, 100); }); });