1100 lines
26 KiB
C
1100 lines
26 KiB
C
/* cache vips operations
|
|
*
|
|
* 20/6/12
|
|
* - try to make it compile on centos5
|
|
* 7/7/12
|
|
* - add a lock so we can run operations from many threads
|
|
* 28/11/19 [MaxKellermann]
|
|
* - make invalidate advisory rather than immediate
|
|
*/
|
|
|
|
/*
|
|
|
|
This file is part of VIPS.
|
|
|
|
VIPS is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
TODO
|
|
|
|
what about delayed writes ... do we ever write in close? we shouldn't,
|
|
should do in evalend or written or somesuch
|
|
|
|
use g_param_values_cmp() instead of value_equal()?
|
|
|
|
*/
|
|
|
|
/*
|
|
#define VIPS_DEBUG
|
|
#define DEBUG
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <vips/intl.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /*HAVE_UNISTD_H*/
|
|
#include <ctype.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/internal.h>
|
|
#include <vips/debug.h>
|
|
|
|
/* Set by GOption from the command line, eg. "12m".
|
|
*/
|
|
gboolean vips__cache_dump = FALSE;
|
|
gboolean vips__cache_trace = FALSE;
|
|
|
|
/* Max number of cached operations.
|
|
*
|
|
* It was 10,000, but this was too high for batch-style applications with
|
|
* little reuse.
|
|
*/
|
|
static int vips_cache_max = 100;
|
|
|
|
/* How many tracked open files we allow before we start dropping cache.
|
|
*/
|
|
static int vips_cache_max_files = 100;
|
|
|
|
/* How much RAM we spend on caches before we start dropping cached operations
|
|
* ... default 100mb.
|
|
*
|
|
* It was 1gb, but that's a lot of memory for things like vipsthumbnail where
|
|
* there will be (almost) no reuse. Default low and let apps raise it if it'd
|
|
* be useful.
|
|
*/
|
|
static size_t vips_cache_max_mem = 100 * 1024 * 1024;
|
|
|
|
/* Hold a ref to all "recent" operations.
|
|
*/
|
|
static GHashTable *vips_cache_table = NULL;
|
|
|
|
/* A 'time' counter: increment on all cache ops. Use this to detect LRU.
|
|
*/
|
|
static int vips_cache_time = 0;
|
|
|
|
/* Protect cache access with this.
|
|
*/
|
|
static GMutex *vips_cache_lock = NULL;
|
|
|
|
/* Old versions of glib are missing these. When we abandon centos 5, switch to
|
|
* g_int64_hash() and g_double_hash().
|
|
*/
|
|
#define INT64_HASH(X) (g_direct_hash(X))
|
|
#define DOUBLE_HASH(X) (g_direct_hash(X))
|
|
|
|
/* A cache entry.
|
|
*/
|
|
typedef struct _VipsOperationCacheEntry {
|
|
VipsOperation *operation;
|
|
|
|
/* When we added this operation to cache .. used to find LRU for
|
|
* flush.
|
|
*/
|
|
int time;
|
|
|
|
/* We listen for "invalidate" from the operation. Track the id here so
|
|
* we can disconnect when we drop an operation.
|
|
*/
|
|
gulong invalidate_id;
|
|
|
|
/* Set if someone thinks this cache entry should be dropped.
|
|
*/
|
|
gboolean invalid;
|
|
|
|
} VipsOperationCacheEntry;
|
|
|
|
/* Pass in the pspec so we can get the generic type. For example, a
|
|
* held in a GParamSpec allowing OBJECT, but the value could be of type
|
|
* VipsImage. generics are much faster to compare.
|
|
*/
|
|
static unsigned int
|
|
vips_value_hash( GParamSpec *pspec, GValue *value )
|
|
{
|
|
GType generic = G_PARAM_SPEC_TYPE( pspec );
|
|
|
|
/* Not compile-time constants, so we have to use a set of if()s. Could
|
|
* make a table at run time I guess.
|
|
*/
|
|
|
|
if( generic == G_TYPE_PARAM_BOOLEAN )
|
|
return( (unsigned int) g_value_get_boolean( value ) );
|
|
else if( generic == G_TYPE_PARAM_CHAR )
|
|
return( (unsigned int) g_value_get_schar( value ) );
|
|
else if( generic == G_TYPE_PARAM_UCHAR )
|
|
return( (unsigned int) g_value_get_uchar( value ) );
|
|
else if( generic == G_TYPE_PARAM_INT )
|
|
return( (unsigned int) g_value_get_int( value ) );
|
|
else if( generic == G_TYPE_PARAM_UINT )
|
|
return( (unsigned int) g_value_get_uint( value ) );
|
|
else if( generic == G_TYPE_PARAM_LONG )
|
|
return( (unsigned int) g_value_get_long( value ) );
|
|
else if( generic == G_TYPE_PARAM_ULONG )
|
|
return( (unsigned int) g_value_get_ulong( value ) );
|
|
else if( generic == G_TYPE_PARAM_ENUM )
|
|
return( (unsigned int) g_value_get_enum( value ) );
|
|
else if( generic == G_TYPE_PARAM_FLAGS )
|
|
return( (unsigned int) g_value_get_flags( value ) );
|
|
else if( generic == G_TYPE_PARAM_UINT64 ) {
|
|
guint64 i = g_value_get_uint64( value );
|
|
|
|
return( INT64_HASH( (gint64 *) &i ) );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_INT64 ) {
|
|
gint64 i = g_value_get_int64( value );
|
|
|
|
return( INT64_HASH( &i ) );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_FLOAT ) {
|
|
float f = g_value_get_float( value );
|
|
|
|
return( g_direct_hash( (void *) &f ) );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_DOUBLE ) {
|
|
double d = g_value_get_double( value );
|
|
|
|
return( DOUBLE_HASH( &d ) );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_STRING ) {
|
|
const char *s = g_value_get_string( value );
|
|
|
|
return( s ? g_str_hash( s ) : 0 );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_BOXED ) {
|
|
void *p = g_value_get_boxed( value );
|
|
|
|
return( p ? g_direct_hash( p ) : 0 );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_POINTER ) {
|
|
void *p = g_value_get_pointer( value );
|
|
|
|
return( p ? g_direct_hash( p ) : 0 );
|
|
}
|
|
else if( generic == G_TYPE_PARAM_OBJECT ) {
|
|
void *p = g_value_get_object( value );
|
|
|
|
return( p ? g_direct_hash( p ) : 0 );
|
|
}
|
|
else {
|
|
/* Fallback: convert to a string and hash that.
|
|
* This is very slow, print a warning if we use it
|
|
* so we can add another case.
|
|
*/
|
|
char *s;
|
|
unsigned int hash;
|
|
|
|
s = g_strdup_value_contents( value );
|
|
hash = g_str_hash( s );
|
|
|
|
printf( "vips_value_hash: no case for %s\n", s );
|
|
printf( "\ttype %d, %s\n",
|
|
(int) G_VALUE_TYPE( value ),
|
|
g_type_name( G_VALUE_TYPE( value ) ) );
|
|
printf( "\tgeneric %d, %s\n",
|
|
(int) G_VALUE_TYPE( generic ),
|
|
g_type_name( generic ) );
|
|
|
|
g_free( s );
|
|
|
|
return( hash );
|
|
}
|
|
}
|
|
|
|
/* Pass in the pspec so we can get the generic type. For example, a
|
|
* value could be held in a GParamSpec allowing OBJECT, but the value
|
|
* could be of type VipsImage. generics are much faster to compare.
|
|
*/
|
|
static gboolean
|
|
vips_value_equal( GParamSpec *pspec, GValue *v1, GValue *v2 )
|
|
{
|
|
GType generic = G_PARAM_SPEC_TYPE( pspec );
|
|
GType t1 = G_VALUE_TYPE( v1 );
|
|
GType t2 = G_VALUE_TYPE( v2 );
|
|
|
|
if( t1 != t2 )
|
|
return( FALSE );
|
|
|
|
/* Not compile-time constants, so we have to use a set of if()s. Could
|
|
* make a table at run time I guess.
|
|
*/
|
|
|
|
if( generic == G_TYPE_PARAM_BOOLEAN )
|
|
return( g_value_get_boolean( v1 ) ==
|
|
g_value_get_boolean( v2 ) );
|
|
else if( generic == G_TYPE_PARAM_CHAR )
|
|
return( g_value_get_schar( v1 ) ==
|
|
g_value_get_schar( v2 ) );
|
|
if( generic == G_TYPE_PARAM_UCHAR )
|
|
return( g_value_get_uchar( v1 ) ==
|
|
g_value_get_uchar( v2 ) );
|
|
if( generic == G_TYPE_PARAM_INT )
|
|
return( g_value_get_int( v1 ) ==
|
|
g_value_get_int( v2 ) );
|
|
if( generic == G_TYPE_PARAM_UINT )
|
|
return( g_value_get_uint( v1 ) ==
|
|
g_value_get_uint( v2 ) );
|
|
if( generic == G_TYPE_PARAM_LONG )
|
|
return( g_value_get_long( v1 ) ==
|
|
g_value_get_long( v2 ) );
|
|
if( generic == G_TYPE_PARAM_ULONG )
|
|
return( g_value_get_ulong( v1 ) ==
|
|
g_value_get_ulong( v2 ) );
|
|
if( generic == G_TYPE_PARAM_ENUM )
|
|
return( g_value_get_enum( v1 ) ==
|
|
g_value_get_enum( v2 ) );
|
|
if( generic == G_TYPE_PARAM_FLAGS )
|
|
return( g_value_get_flags( v1 ) ==
|
|
g_value_get_flags( v2 ) );
|
|
if( generic == G_TYPE_PARAM_UINT64 )
|
|
return( g_value_get_uint64( v1 ) ==
|
|
g_value_get_uint64( v2 ) );
|
|
if( generic == G_TYPE_PARAM_INT64 )
|
|
return( g_value_get_int64( v1 ) ==
|
|
g_value_get_int64( v2 ) );
|
|
if( generic == G_TYPE_PARAM_FLOAT )
|
|
return( g_value_get_float( v1 ) ==
|
|
g_value_get_float( v2 ) );
|
|
if( generic == G_TYPE_PARAM_DOUBLE )
|
|
return( g_value_get_double( v1 ) ==
|
|
g_value_get_double( v2 ) );
|
|
if( generic == G_TYPE_PARAM_STRING ) {
|
|
const char *s1 = g_value_get_string( v1 );
|
|
const char *s2 = g_value_get_string( v2 );
|
|
|
|
if( s1 == s2 )
|
|
return( TRUE );
|
|
else
|
|
return( s1 && s2 && strcmp( s1, s2 ) == 0 );
|
|
}
|
|
if( generic == G_TYPE_PARAM_BOXED )
|
|
return( g_value_get_boxed( v1 ) ==
|
|
g_value_get_boxed( v2 ) );
|
|
if( generic == G_TYPE_PARAM_POINTER )
|
|
return( g_value_get_pointer( v1 ) ==
|
|
g_value_get_pointer( v2 ) );
|
|
if( generic == G_TYPE_PARAM_OBJECT )
|
|
return( g_value_get_object( v1 ) ==
|
|
g_value_get_object( v2 ) );
|
|
else {
|
|
/* Fallback: convert to a string and compare that.
|
|
* This is very slow, print a warning if we use it
|
|
* so we can add another case.
|
|
*/
|
|
char *s1;
|
|
char *s2;
|
|
gboolean equal;
|
|
|
|
s1 = g_strdup_value_contents( v1 );
|
|
s2 = g_strdup_value_contents( v2 );
|
|
equal = strcmp( s1, s2 ) == 0;
|
|
|
|
printf( "vips_value_equal: no case for %s, %s\n",
|
|
s1, s2 );
|
|
printf( "\tt1 %d, %s\n", (int) t1, g_type_name( t1 ) );
|
|
printf( "\tt2 %d, %s\n", (int) t2, g_type_name( t2 ) );
|
|
printf( "\tgeneric %d, %s\n",
|
|
(int) G_VALUE_TYPE( generic ),
|
|
g_type_name( generic ) );
|
|
|
|
g_free( s1 );
|
|
g_free( s2 );
|
|
|
|
return( equal );
|
|
}
|
|
}
|
|
|
|
static void *
|
|
vips_object_hash_arg( VipsObject *object,
|
|
GParamSpec *pspec,
|
|
VipsArgumentClass *argument_class,
|
|
VipsArgumentInstance *argument_instance,
|
|
void *a, void *b )
|
|
{
|
|
unsigned int *hash = (unsigned int *) a;
|
|
|
|
if( (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
|
|
(argument_class->flags & VIPS_ARGUMENT_INPUT) &&
|
|
argument_instance->assigned ) {
|
|
const char *name = g_param_spec_get_name( pspec );
|
|
GType type = G_PARAM_SPEC_VALUE_TYPE( pspec );
|
|
GValue value = { 0, };
|
|
|
|
g_value_init( &value, type );
|
|
g_object_get_property( G_OBJECT( object ), name, &value );
|
|
*hash = (*hash << 1) ^ vips_value_hash( pspec, &value );
|
|
g_value_unset( &value );
|
|
}
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
/* Find a hash from the input arguments to a VipsOperstion.
|
|
*/
|
|
static unsigned int
|
|
vips_operation_hash( VipsOperation *operation )
|
|
{
|
|
if( !operation->found_hash ) {
|
|
guint hash;
|
|
|
|
/* Include the operation type in the hash.
|
|
*/
|
|
hash = (guint) G_OBJECT_TYPE( operation );
|
|
(void) vips_argument_map( VIPS_OBJECT( operation ),
|
|
vips_object_hash_arg, &hash, NULL );
|
|
|
|
/* Make sure we can't have a zero hash value.
|
|
*/
|
|
hash |= 1;
|
|
|
|
operation->hash = hash;
|
|
operation->found_hash = TRUE;
|
|
}
|
|
|
|
return( operation->hash );
|
|
}
|
|
|
|
static void *
|
|
vips_object_equal_arg( VipsObject *object,
|
|
GParamSpec *pspec,
|
|
VipsArgumentClass *argument_class,
|
|
VipsArgumentInstance *argument_instance,
|
|
void *a, void *b )
|
|
{
|
|
VipsObject *other = (VipsObject *) a;
|
|
|
|
const char *name = g_param_spec_get_name( pspec );
|
|
GType type = G_PARAM_SPEC_VALUE_TYPE( pspec );
|
|
GValue v1 = { 0, };
|
|
GValue v2 = { 0, };
|
|
|
|
gboolean equal;
|
|
|
|
/* Only test assigned input constructor args.
|
|
*/
|
|
if( !(argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) ||
|
|
!(argument_class->flags & VIPS_ARGUMENT_INPUT) ||
|
|
!argument_instance->assigned )
|
|
return( NULL );
|
|
|
|
/* If this is an optional arg, we need to check that this was
|
|
* assigned on @other as well.
|
|
*/
|
|
if( !(argument_class->flags & VIPS_ARGUMENT_REQUIRED) &&
|
|
!vips_object_argument_isset( other, name ) )
|
|
/* Optional and was not set on other ... we've found a
|
|
* difference!
|
|
*/
|
|
return( object );
|
|
|
|
g_value_init( &v1, type );
|
|
g_value_init( &v2, type );
|
|
g_object_get_property( G_OBJECT( object ), name, &v1 );
|
|
g_object_get_property( G_OBJECT( other ), name, &v2 );
|
|
equal = vips_value_equal( pspec, &v1, &v2 );
|
|
g_value_unset( &v1 );
|
|
g_value_unset( &v2 );
|
|
|
|
/* Stop (return non-NULL) if we've found a difference.
|
|
*/
|
|
return( !equal ? object : NULL );
|
|
}
|
|
|
|
/* Are two objects equal, ie. have the same inputs.
|
|
*/
|
|
static gboolean
|
|
vips_operation_equal( VipsOperation *a, VipsOperation *b )
|
|
{
|
|
if( a == b )
|
|
return( TRUE );
|
|
|
|
if( G_OBJECT_TYPE( a ) == G_OBJECT_TYPE( b ) &&
|
|
vips_operation_hash( a ) == vips_operation_hash( b ) &&
|
|
!vips_argument_map( VIPS_OBJECT( a ),
|
|
vips_object_equal_arg, b, NULL ) )
|
|
return( TRUE );
|
|
|
|
return( FALSE );
|
|
}
|
|
|
|
void *
|
|
vips__cache_once_init( void *data )
|
|
{
|
|
vips_cache_lock = vips_g_mutex_new();
|
|
|
|
vips_cache_table = g_hash_table_new(
|
|
(GHashFunc) vips_operation_hash,
|
|
(GEqualFunc) vips_operation_equal );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
void
|
|
vips__cache_init( void )
|
|
{
|
|
static GOnce once = G_ONCE_INIT;
|
|
|
|
VIPS_ONCE( &once, vips__cache_once_init, NULL );
|
|
}
|
|
|
|
static void *
|
|
vips_cache_print_fn( void *value, void *a, void *b )
|
|
{
|
|
VipsOperationCacheEntry *entry = value;
|
|
|
|
char str[32768];
|
|
VipsBuf buf = VIPS_BUF_STATIC( str );
|
|
|
|
vips_object_to_string( VIPS_OBJECT( entry->operation ), &buf );
|
|
|
|
printf( "%p - %s\n", value, vips_buf_all( &buf ) );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
static void
|
|
vips_cache_print_nolock( void )
|
|
{
|
|
if( vips_cache_table ) {
|
|
printf( "Operation cache:\n" );
|
|
vips_hash_table_map( vips_cache_table,
|
|
vips_cache_print_fn, NULL, NULL );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* vips_cache_print:
|
|
*
|
|
* Print the whole operation cache to stdout. Handy for debugging.
|
|
*/
|
|
void
|
|
vips_cache_print( void )
|
|
{
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
vips_cache_print_nolock();
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
}
|
|
|
|
static void *
|
|
vips_object_unref_arg( VipsObject *object,
|
|
GParamSpec *pspec,
|
|
VipsArgumentClass *argument_class,
|
|
VipsArgumentInstance *argument_instance,
|
|
void *a, void *b )
|
|
{
|
|
if( (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
|
|
(argument_class->flags & VIPS_ARGUMENT_OUTPUT) &&
|
|
argument_instance->assigned &&
|
|
G_IS_PARAM_SPEC_OBJECT( pspec ) ) {
|
|
GObject *value;
|
|
|
|
/* This will up the ref count for us.
|
|
*/
|
|
g_object_get( G_OBJECT( object ),
|
|
g_param_spec_get_name( pspec ), &value, NULL );
|
|
|
|
/* Drop the ref we just got, then drop the ref we make when we
|
|
* added to the cache.
|
|
*/
|
|
g_object_unref( value );
|
|
g_object_unref( value );
|
|
}
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
static void
|
|
vips_cache_unref( VipsOperation *operation )
|
|
{
|
|
#ifdef DEBUG
|
|
printf( "vips_cache_unref: " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
#endif /*DEBUG*/
|
|
|
|
(void) vips_argument_map( VIPS_OBJECT( operation ),
|
|
vips_object_unref_arg, NULL, NULL );
|
|
g_object_unref( operation );
|
|
}
|
|
|
|
/* Remove an operation from the cache.
|
|
*/
|
|
static void
|
|
vips_cache_remove( VipsOperation *operation )
|
|
{
|
|
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
|
|
g_hash_table_lookup( vips_cache_table, operation );
|
|
|
|
#ifdef DEBUG
|
|
printf( "vips_cache_remove: " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
#endif /*DEBUG*/
|
|
|
|
g_assert( entry );
|
|
|
|
if( entry->invalidate_id ) {
|
|
g_signal_handler_disconnect( operation, entry->invalidate_id );
|
|
entry->invalidate_id = 0;
|
|
}
|
|
|
|
g_hash_table_remove( vips_cache_table, operation );
|
|
vips_cache_unref( operation );
|
|
|
|
g_free( entry );
|
|
}
|
|
|
|
static void *
|
|
vips_object_ref_arg( VipsObject *object,
|
|
GParamSpec *pspec,
|
|
VipsArgumentClass *argument_class,
|
|
VipsArgumentInstance *argument_instance,
|
|
void *a, void *b )
|
|
{
|
|
if( (argument_class->flags & VIPS_ARGUMENT_CONSTRUCT) &&
|
|
(argument_class->flags & VIPS_ARGUMENT_OUTPUT) &&
|
|
argument_instance->assigned &&
|
|
G_IS_PARAM_SPEC_OBJECT( pspec ) ) {
|
|
GObject *value;
|
|
|
|
/* This will up the ref count for us.
|
|
*/
|
|
g_object_get( G_OBJECT( object ),
|
|
g_param_spec_get_name( pspec ), &value, NULL );
|
|
}
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
static void
|
|
vips_operation_touch( VipsOperation *operation )
|
|
{
|
|
VipsOperationCacheEntry *entry = (VipsOperationCacheEntry *)
|
|
g_hash_table_lookup( vips_cache_table, operation );
|
|
|
|
vips_cache_time += 1;
|
|
|
|
/* Don't up the time for invalid items -- we want them to fall out of
|
|
* cache.
|
|
*/
|
|
if( !entry->invalid )
|
|
entry->time = vips_cache_time;
|
|
}
|
|
|
|
/* Ref an operation for the cache. The operation itself, plus all the output
|
|
* objects it makes.
|
|
*/
|
|
static void
|
|
vips_cache_ref( VipsOperation *operation )
|
|
{
|
|
#ifdef DEBUG
|
|
printf( "vips_cache_ref: " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
#endif /*DEBUG*/
|
|
|
|
g_object_ref( operation );
|
|
(void) vips_argument_map( VIPS_OBJECT( operation ),
|
|
vips_object_ref_arg, NULL, NULL );
|
|
vips_operation_touch( operation );
|
|
}
|
|
|
|
static void
|
|
vips_cache_invalidate_cb( VipsOperation *operation,
|
|
VipsOperationCacheEntry *entry )
|
|
{
|
|
#ifdef DEBUG
|
|
printf( "vips_cache_invalidate_cb: " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
#endif /*DEBUG*/
|
|
|
|
entry->invalid = TRUE;
|
|
}
|
|
|
|
static void
|
|
vips_cache_insert( VipsOperation *operation )
|
|
{
|
|
VipsOperationCacheEntry *entry = g_new( VipsOperationCacheEntry, 1 );
|
|
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_insert: adding to cache" );
|
|
vips_object_print_dump( VIPS_OBJECT( operation ) );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
entry->operation = operation;
|
|
entry->time = 0;
|
|
entry->invalidate_id = 0;
|
|
entry->invalid = FALSE;
|
|
|
|
g_hash_table_insert( vips_cache_table, operation, entry );
|
|
vips_cache_ref( operation );
|
|
|
|
/* If the operation signals "invalidate", we must tag this cache entry
|
|
* for removal.
|
|
*/
|
|
entry->invalidate_id = g_signal_connect( operation, "invalidate",
|
|
G_CALLBACK( vips_cache_invalidate_cb ), entry );
|
|
}
|
|
|
|
static void *
|
|
vips_cache_get_first_fn( void *value, void *a, void *b )
|
|
{
|
|
return( value );
|
|
}
|
|
|
|
/* Return the first item.
|
|
*/
|
|
static VipsOperation *
|
|
vips_cache_get_first( void )
|
|
{
|
|
VipsOperationCacheEntry *entry;
|
|
|
|
if( vips_cache_table &&
|
|
(entry = vips_hash_table_map( vips_cache_table,
|
|
vips_cache_get_first_fn, NULL, NULL )) )
|
|
return( VIPS_OPERATION( entry->operation ) );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_drop_all:
|
|
*
|
|
* Drop the whole operation cache, handy for leak tracking. Also called
|
|
* automatically on vips_shutdown().
|
|
*/
|
|
void
|
|
vips_cache_drop_all( void )
|
|
{
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_drop_all:\n" );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
if( vips_cache_table ) {
|
|
VipsOperation *operation;
|
|
|
|
if( vips__cache_dump )
|
|
vips_cache_print_nolock();
|
|
|
|
/* We can't modify the hash in the callback from
|
|
* g_hash_table_foreach() and friends. Repeatedly drop the
|
|
* first item instead.
|
|
*/
|
|
while( (operation = vips_cache_get_first()) )
|
|
vips_cache_remove( operation );
|
|
|
|
VIPS_FREEF( g_hash_table_unref, vips_cache_table );
|
|
}
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
}
|
|
|
|
static void
|
|
vips_cache_get_lru_cb( VipsOperation *key, VipsOperationCacheEntry *value,
|
|
VipsOperationCacheEntry **best )
|
|
{
|
|
if( !*best ||
|
|
(*best)->time > value->time )
|
|
*best = value;
|
|
}
|
|
|
|
/* Get the least-recently-used cache item.
|
|
*
|
|
* TODO ... will this be too expensive? probably not
|
|
*/
|
|
static VipsOperation *
|
|
vips_cache_get_lru( void )
|
|
{
|
|
VipsOperationCacheEntry *entry;
|
|
|
|
entry = NULL;
|
|
g_hash_table_foreach( vips_cache_table,
|
|
(GHFunc) vips_cache_get_lru_cb, &entry );
|
|
|
|
if( entry )
|
|
return( entry->operation );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
/* Is the cache full? Drop until it's not.
|
|
*/
|
|
static void
|
|
vips_cache_trim( void )
|
|
{
|
|
VipsOperation *operation;
|
|
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
while( vips_cache_table &&
|
|
(g_hash_table_size( vips_cache_table ) > vips_cache_max ||
|
|
vips_tracked_get_files() > vips_cache_max_files ||
|
|
vips_tracked_get_mem() > vips_cache_max_mem) &&
|
|
(operation = vips_cache_get_lru()) ) {
|
|
#ifdef DEBUG
|
|
printf( "vips_cache_trim: trimming " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
#endif /*DEBUG*/
|
|
|
|
vips_cache_remove( operation );
|
|
}
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_operation_lookup:
|
|
* @operation: (transfer none): pointer to operation to lookup
|
|
*
|
|
* Look up an unbuilt @operation in the cache. If we get a hit, ref and
|
|
* return the old operation. If there's no hit, return NULL.
|
|
*
|
|
* Returns: (transfer full): the cache hit, if any.
|
|
*/
|
|
VipsOperation *
|
|
vips_cache_operation_lookup( VipsOperation *operation )
|
|
{
|
|
VipsOperationCacheEntry *hit;
|
|
VipsOperation *result;
|
|
|
|
g_assert( VIPS_IS_OPERATION( operation ) );
|
|
g_assert( !VIPS_OBJECT( operation )->constructed );
|
|
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_lookup: " );
|
|
vips_object_print_dump( VIPS_OBJECT( operation ) );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
result = NULL;
|
|
|
|
if( (hit = g_hash_table_lookup( vips_cache_table, operation )) ) {
|
|
if( hit->invalid ) {
|
|
/* There but has been tagged for removal.
|
|
*/
|
|
vips_cache_remove( hit->operation );
|
|
hit = NULL;
|
|
}
|
|
else {
|
|
if( vips__cache_trace ) {
|
|
printf( "vips cache*: " );
|
|
vips_object_print_summary(
|
|
VIPS_OBJECT( operation ) );
|
|
}
|
|
|
|
result = hit->operation;
|
|
vips_cache_ref( result );
|
|
}
|
|
}
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_lookup: result = %p\n", result );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
return( result );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_operation_add:
|
|
* @operation: (transfer none): pointer to operation to add
|
|
*
|
|
* Add a built operation to the cache. The cache will ref the operation.
|
|
*/
|
|
void
|
|
vips_cache_operation_add( VipsOperation *operation )
|
|
{
|
|
g_assert( VIPS_OBJECT( operation )->constructed );
|
|
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_add: adding " );
|
|
vips_object_print_dump( VIPS_OBJECT( operation ) );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
/* If two threads call the same operation at the same time,
|
|
* we can get multiple adds. Let the first one win. See
|
|
* https://github.com/libvips/libvips/pull/181
|
|
*/
|
|
if( !g_hash_table_lookup( vips_cache_table, operation ) ) {
|
|
VipsOperationFlags flags =
|
|
vips_operation_get_flags( operation );
|
|
gboolean nocache = flags & VIPS_OPERATION_NOCACHE;
|
|
|
|
/* Has to be after _build() so we can see output args.
|
|
*/
|
|
if( vips__cache_trace ) {
|
|
if( nocache )
|
|
printf( "vips cache : " );
|
|
else
|
|
printf( "vips cache+: " );
|
|
vips_object_print_summary( VIPS_OBJECT( operation ) );
|
|
}
|
|
|
|
if( !nocache )
|
|
vips_cache_insert( operation );
|
|
}
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
|
|
vips_cache_trim();
|
|
}
|
|
|
|
/**
|
|
* vips_cache_operation_buildp: (skip)
|
|
* @operation: pointer to operation to lookup
|
|
*
|
|
* Look up @operation in the cache. If we get a hit, unref @operation, ref the
|
|
* old one and return that through the argument pointer.
|
|
*
|
|
* If we miss, build and add @operation.
|
|
*
|
|
* Returns: 0 on success, or -1 on error.
|
|
*/
|
|
int
|
|
vips_cache_operation_buildp( VipsOperation **operation )
|
|
{
|
|
VipsOperation *hit;
|
|
|
|
g_assert( VIPS_IS_OPERATION( *operation ) );
|
|
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_buildp: " );
|
|
vips_object_print_dump( VIPS_OBJECT( *operation ) );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
if( (hit = vips_cache_operation_lookup( *operation )) ) {
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_buildp: cache hit %p\n", hit );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
g_object_unref( *operation );
|
|
*operation = hit;
|
|
}
|
|
else {
|
|
#ifdef VIPS_DEBUG
|
|
printf( "vips_cache_operation_buildp: cache miss, building\n" );
|
|
#endif /*VIPS_DEBUG*/
|
|
|
|
if( vips_object_build( VIPS_OBJECT( *operation ) ) )
|
|
return( -1 );
|
|
|
|
vips_cache_operation_add( *operation );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_operation_build:
|
|
* @operation: (transfer none): operation to lookup
|
|
*
|
|
* A binding-friendly version of vips_cache_operation_buildp().
|
|
*
|
|
* After calling this, @operation has the same ref count as when it went in,
|
|
* and the result must be freed with vips_object_unref_outputs() and
|
|
* g_object_unref().
|
|
*
|
|
* Returns: (transfer full): The built operation.
|
|
*/
|
|
VipsOperation *
|
|
vips_cache_operation_build( VipsOperation *operation )
|
|
{
|
|
VipsOperation *orig_operation = operation;
|
|
|
|
/* Stop it being unreffed for us on hit.
|
|
*/
|
|
g_object_ref( orig_operation );
|
|
|
|
if( vips_cache_operation_buildp( &operation ) ) {
|
|
g_object_unref( orig_operation );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
return( operation );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_set_max:
|
|
* @max: maximum number of operation to cache
|
|
*
|
|
* Set the maximum number of operations we keep in cache.
|
|
*/
|
|
void
|
|
vips_cache_set_max( int max )
|
|
{
|
|
vips_cache_max = max;
|
|
vips_cache_trim();
|
|
}
|
|
|
|
/**
|
|
* vips_cache_set_max_mem:
|
|
* @max_mem: maximum amount of tracked memory we use
|
|
*
|
|
* Set the maximum amount of tracked memory we allow before we start dropping
|
|
* cached operations. See vips_tracked_get_mem().
|
|
*
|
|
* libvips only tracks memory it allocates, it can't track memory allocated by
|
|
* external libraries. If you use an operation like vips_magickload(), most of
|
|
* the memory it uses won't be included.
|
|
*
|
|
* See also: vips_tracked_get_mem().
|
|
*/
|
|
void
|
|
vips_cache_set_max_mem( size_t max_mem )
|
|
{
|
|
vips_cache_max_mem = max_mem;
|
|
vips_cache_trim();
|
|
}
|
|
|
|
/**
|
|
* vips_cache_get_max:
|
|
*
|
|
* Get the maximum number of operations we keep in cache.
|
|
*
|
|
* Returns: the maximum number of operations we keep in cache
|
|
*/
|
|
int
|
|
vips_cache_get_max( void )
|
|
{
|
|
return( vips_cache_max );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_get_size:
|
|
*
|
|
* Get the current number of operations in cache.
|
|
*
|
|
* Returns: get the current number of operations in cache.
|
|
*/
|
|
int
|
|
vips_cache_get_size( void )
|
|
{
|
|
guint size;
|
|
|
|
g_mutex_lock( vips_cache_lock );
|
|
|
|
size = 0;
|
|
if( vips_cache_table )
|
|
size = g_hash_table_size( vips_cache_table );
|
|
|
|
g_mutex_unlock( vips_cache_lock );
|
|
|
|
return( size );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_get_max_mem:
|
|
*
|
|
* Get the maximum amount of tracked memory we allow before we start dropping
|
|
* cached operations. See vips_tracked_get_mem().
|
|
*
|
|
* See also: vips_tracked_get_mem().
|
|
*
|
|
* Returns: the maximum amount of tracked memory we allow
|
|
*/
|
|
size_t
|
|
vips_cache_get_max_mem( void )
|
|
{
|
|
return( vips_cache_max_mem );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_get_max_files:
|
|
*
|
|
* Get the maximum number of tracked files we allow before we start dropping
|
|
* cached operations. See vips_tracked_get_files().
|
|
*
|
|
* libvips only tracks file descriptors it allocates, it can't track ones
|
|
* allocated by external libraries. If you use an operation like
|
|
* vips_magickload(), most of the descriptors it uses won't be included.
|
|
*
|
|
* See also: vips_tracked_get_files().
|
|
*
|
|
* Returns: the maximum number of tracked files we allow
|
|
*/
|
|
int
|
|
vips_cache_get_max_files( void )
|
|
{
|
|
return( vips_cache_max_files );
|
|
}
|
|
|
|
/**
|
|
* vips_cache_set_max_files:
|
|
* @max_files: max open files we allow
|
|
*
|
|
* Set the maximum number of tracked files we allow before we start dropping
|
|
* cached operations. See vips_tracked_get_files().
|
|
*
|
|
* See also: vips_tracked_get_files().
|
|
*/
|
|
void
|
|
vips_cache_set_max_files( int max_files )
|
|
{
|
|
vips_cache_max_files = max_files;
|
|
vips_cache_trim();
|
|
}
|
|
|
|
/**
|
|
* vips_cache_set_dump:
|
|
* @dump: if %TRUE, dump the operation cache on exit
|
|
*
|
|
* Handy for debugging. Print the operation cache to stdout just before exit.
|
|
*
|
|
* See also: vips_cache_set_trace().
|
|
*/
|
|
void
|
|
vips_cache_set_dump( gboolean dump )
|
|
{
|
|
vips__cache_dump = dump;
|
|
}
|
|
|
|
/**
|
|
* vips_cache_set_trace:
|
|
* @trace: if %TRUE, trace the operation cache
|
|
*
|
|
* Handy for debugging. Print operation cache actions to stdout as we run.
|
|
*
|
|
* You can set the environment variable `VIPS_TRACE` to turn this option on, or
|
|
* use the command-line flag `--vips-cache-trace`.
|
|
*
|
|
* See also: vips_cache_set_dump().
|
|
*/
|
|
void
|
|
vips_cache_set_trace( gboolean trace )
|
|
{
|
|
vips__cache_trace = trace;
|
|
}
|