Expose control of the number of open files in libvips' cache.

Breaks API of existing cache method.
Disable libvips cache for I/O tests.
This commit is contained in:
Lovell Fuller 2016-01-06 20:37:37 +00:00
parent 8843211e12
commit 11329d5e09
29 changed files with 126 additions and 82 deletions

View File

@ -5,6 +5,7 @@
"maxcomplexity": 13, "maxcomplexity": 13,
"globals": { "globals": {
"before": true, "before": true,
"after": true,
"describe": true, "describe": true,
"it": true "it": true
} }

View File

@ -41,8 +41,8 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.12.0 | look |
| v0.13.0 | mind | | v0.13.0 | mind |
| v0.14.0 | needle |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@ -590,19 +590,26 @@ An Object containing the version numbers of libvips and, on Linux, its dependenc
### Utilities ### 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 * `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50
* `items` is the maximum number of operations to cache, with a default value of 500 * `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. This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript ```javascript
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 } var stats = sharp.cache();
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 } ```
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
```javascript
sharp.cache( { items: 200 } );
sharp.cache( { files: 0 } );
sharp.cache(false);
``` ```
#### sharp.concurrency([threads]) #### sharp.concurrency([threads])

View File

@ -1,5 +1,10 @@
# Changelog # 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 - "*look*"
#### v0.12.2 - 16<sup>th</sup> January 2016 #### v0.12.2 - 16<sup>th</sup> January 2016

View File

@ -834,18 +834,25 @@ Sharp.prototype.clone = function() {
return clone; 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) { module.exports.cache = function(options) {
if (typeof memory !== 'number' || Number.isNaN(memory)) { if (typeof options === 'boolean') {
memory = null; if (options) {
// Default cache settings of 50MB, 20 files, 100 items
return sharp.cache(50, 20, 100);
} else {
return sharp.cache(0, 0, 0);
} }
if (typeof items !== 'number' || Number.isNaN(items)) { } else if (typeof options === 'object') {
items = null; return sharp.cache(options.memory, options.files, options.items);
} else {
return sharp.cache();
} }
return sharp.cache(memory, items);
}; };
// Ensure default cache settings are set
module.exports.cache(true);
/* /*
Get and set size of thread pool Get and set size of thread pool

View File

@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.12.2", "version": "0.13.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",

View File

@ -11,10 +11,6 @@
NAN_MODULE_INIT(init) { NAN_MODULE_INIT(init) {
vips_init("sharp"); 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 // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());

View File

@ -24,33 +24,49 @@ using Nan::To;
using Nan::Utf8String; using Nan::Utf8String;
/* /*
Get and set cache memory and item limits Get and set cache limits
*/ */
NAN_METHOD(cache) { NAN_METHOD(cache) {
HandleScope(); HandleScope();
// Set cache memory limit // Set memory limit
if (info[0]->IsInt32()) { if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576); vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
} }
// Set file limit
// Set cache items limit
if (info[1]->IsInt32()) { if (info[1]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[1]).FromJust()); vips_cache_set_max_files(To<int32_t>(info[1]).FromJust());
}
// Set items limit
if (info[2]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust());
} }
// Get cache statistics // Get memory stats
Local<Object> cache = New<Object>(); Local<Object> memory = New<Object>();
Set(cache, New("current").ToLocalChecked(), Set(memory, New("current").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576))) New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))
); );
Set(cache, New("high").ToLocalChecked(), Set(memory, New("high").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))) New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576)))
); );
Set(cache, New("memory").ToLocalChecked(), Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))) New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576)))
); );
Set(cache, New("items").ToLocalChecked(), New<Integer>(vips_cache_get_max())); // Get file stats
Local<Object> files = New<Object>();
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files()));
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files()));
// Get item stats
Local<Object> items = New<Object>();
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size()));
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max()));
Local<Object> cache = New<Object>();
Set(cache, New("memory").ToLocalChecked(), memory);
Set(cache, New("files").ToLocalChecked(), files);
Set(cache, New("items").ToLocalChecked(), items);
info.GetReturnValue().Set(cache); info.GetReturnValue().Set(cache);
} }
@ -262,6 +278,7 @@ NAN_METHOD(_maxColourDistance) {
vips_object_local(hook, imagePremultipliedNoAlpha2); vips_object_local(hook, imagePremultipliedNoAlpha2);
image2 = imagePremultipliedNoAlpha2; image2 = imagePremultipliedNoAlpha2;
} }
// Calculate colour distance // Calculate colour distance
VipsImage *difference; VipsImage *difference;
if (vips_dE00(image1, image2, &difference, nullptr)) { if (vips_dE00(image1, image2, &difference, nullptr)) {
@ -276,5 +293,10 @@ NAN_METHOD(_maxColourDistance) {
return ThrowError(vips_error_buffer()); return ThrowError(vips_error_buffer());
} }
g_object_unref(hook); g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance)); info.GetReturnValue().Set(New<Number>(maxColourDistance));
} }

View File

@ -34,7 +34,7 @@ var magickFilterBilinear = 'Triangle';
var magickFilterBicubic = 'Lanczos'; var magickFilterBicubic = 'Lanczos';
// Disable libvips cache to ensure tests are as fair as they can be // Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(0); sharp.cache(false);
// Enable use of SIMD // Enable use of SIMD
sharp.simd(true); sharp.simd(true);

View File

@ -4,8 +4,6 @@ var assert = require('assert');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
var sharp = require('../../index'); var sharp = require('../../index');
sharp.cache(0);
describe('Alpha transparency', function() { describe('Alpha transparency', function() {
it('Flatten to black', function(done) { it('Flatten to black', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Blur', function() { describe('Blur', function() {
it('specific radius 1', function(done) { it('specific radius 1', function(done) {

View File

@ -6,9 +6,15 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Clone', function() { describe('Clone', function() {
before(function() {
sharp.cache(false);
});
after(function() {
sharp.cache(true);
});
it('Read from Stream and write to multiple Streams', function(done) { it('Read from Stream and write to multiple Streams', function(done) {
var finishEventsExpected = 2; var finishEventsExpected = 2;
// Output stream 1 // Output stream 1
@ -55,4 +61,5 @@ describe('Clone', function() {
// Go // Go
fs.createReadStream(fixtures.inputJpg).pipe(rotator); fs.createReadStream(fixtures.inputJpg).pipe(rotator);
}); });
}); });

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Colour space conversion', function() { describe('Colour space conversion', function() {
it('To greyscale', function(done) { it('To greyscale', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Crop gravities', function() { describe('Crop gravities', function() {
var testSettings = [ var testSettings = [

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Embed', function() { describe('Embed', function() {
it('JPEG within PNG, no alpha channel', function(done) { it('JPEG within PNG, no alpha channel', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Partial image extraction', function() { describe('Partial image extraction', function() {
describe('using the legacy extract(top,left,width,height) syntax', function () { describe('using the legacy extract(top,left,width,height) syntax', function () {
it('JPEG', function(done) { it('JPEG', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Gamma correction', function() { describe('Gamma correction', function() {
it('value of 0.0 (disabled)', function(done) { it('value of 0.0 (disabled)', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Interpolation', function() { describe('Interpolation', function() {
it('nearest neighbour', function(done) { it('nearest neighbour', function(done) {

View File

@ -6,10 +6,15 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Input/output', function() { describe('Input/output', function() {
before(function() {
sharp.cache(false);
});
after(function() {
sharp.cache(true);
});
it('Read from File and write to Stream', function(done) { it('Read from File and write to Stream', function(done) {
var writable = fs.createWriteStream(fixtures.outputJpg); var writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function() { writable.on('finish', function() {

View File

@ -8,8 +8,6 @@ var icc = require('icc');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Image metadata', function() { describe('Image metadata', function() {
it('JPEG', function(done) { it('JPEG', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Negate', function() { describe('Negate', function() {
it('negate (jpeg)', function(done) { it('negate (jpeg)', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Normalization', function () { describe('Normalization', function () {
it('uses the same prototype for both spellings', function () { it('uses the same prototype for both spellings', function () {

View File

@ -4,8 +4,6 @@ var assert = require('assert');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
var sharp = require('../../index'); var sharp = require('../../index');
sharp.cache(0);
// Helpers // Helpers
var getPaths = function(baseName, extension) { var getPaths = function(baseName, extension) {
if (typeof extension === 'undefined') { if (typeof extension === 'undefined') {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Resize dimensions', function() { describe('Resize dimensions', function() {
it('Exact crop', function(done) { it('Exact crop', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Rotation', function() { describe('Rotation', function() {
['Landscape', 'Portrait'].forEach(function(orientation) { ['Landscape', 'Portrait'].forEach(function(orientation) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Sharpen', function() { describe('Sharpen', function() {
it('specific radius 10', function(done) { it('specific radius 10', function(done) {

View File

@ -5,8 +5,6 @@ var assert = require('assert');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
describe('Threshold', function() { describe('Threshold', function() {
it('threshold 1 jpeg', function(done) { it('threshold 1 jpeg', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)

View File

@ -10,8 +10,6 @@ var rimraf = require('rimraf');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.cache(0);
// Verifies all tiles in a given dz output directory are <= size // Verifies all tiles in a given dz output directory are <= size
var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) { var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) {
// Get levels // Get levels

View File

@ -10,20 +10,48 @@ describe('Utilities', function() {
describe('Cache', function() { describe('Cache', function() {
it('Can be disabled', function() { it('Can be disabled', function() {
var cache = sharp.cache(0, 0); sharp.cache(false);
assert.strictEqual(0, cache.memory); var cache = sharp.cache(false);
assert.strictEqual(0, cache.items); 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() { it('Can be enabled with defaults', function() {
var cache = sharp.cache(50, 500); var cache = sharp.cache(true);
assert.strictEqual(50, cache.memory); assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(500, cache.items); 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() { it('Ignores invalid values', function() {
sharp.cache(50, 500); sharp.cache(true);
var cache = sharp.cache('spoons'); var cache = sharp.cache('spoons');
assert.strictEqual(50, cache.memory); assert.strictEqual(cache.memory.max, 50);
assert.strictEqual(500, cache.items); assert.strictEqual(cache.files.max, 20);
assert.strictEqual(cache.items.max, 100);
}); });
}); });