diff --git a/ChangeLog b/ChangeLog index bcd848da..3be50ed7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +xx/1/23 8.14.1 + +- add vips_thread_isworker() compatibility function [remicollet] +- add vips_semaphore_down_timeout() [kleisauke] +- idle threads are removed after 15s [kleisauke] +- fix version number in gtk-doc index [kleisauke] + 22/12/22 8.14.0 - remove autotools diff --git a/doc/libvips-docs.xml.in b/doc/libvips-docs.xml similarity index 97% rename from doc/libvips-docs.xml.in rename to doc/libvips-docs.xml index a4f36e8a..ff45096e 100644 --- a/doc/libvips-docs.xml.in +++ b/doc/libvips-docs.xml @@ -4,12 +4,13 @@ "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [ + ]> libvips Reference Manual - For libvips @VIPS_VERSION@. The latest version of this documentation + For libvips &version;. The latest version of this documentation can be found on the libvips website. diff --git a/doc/meson.build b/doc/meson.build index 4542b10c..d9767ccf 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -51,7 +51,7 @@ private_libvips_headers = [ private_headers = [] foreach private_libvips_header : private_libvips_headers - private_headers += [join_paths(meson.project_source_root(), 'libvips', private_libvips_header)] + private_headers += [join_paths(project_source_root, 'libvips', private_libvips_header)] endforeach markdown_content_files = files( @@ -126,8 +126,12 @@ images = files( ) version_conf = configuration_data() -version_conf.set('VIPS_VERSION', meson.project_version ()) -vips_docs = configure_file(input: 'libvips-docs.xml.in', output: 'libvips-docs.xml', configuration: version_conf) +version_conf.set('VIPS_VERSION', meson.project_version()) +version_xml = configure_file( + input: 'version.xml.in', + output: '@BASENAME@', + configuration: version_conf +) glib_prefix = glib_dep.get_variable(pkgconfig: 'prefix') glib_docpath = glib_prefix / 'share' / 'gtk-doc' / 'html' @@ -135,16 +139,14 @@ docpath = get_option('prefix') / get_option('datadir') / 'gtk-doc' / 'html' gnome.gtkdoc('libvips', mode: 'none', - main_xml: 'libvips-docs.xml.in', + main_xml: 'libvips-docs.xml', src_dir: [ - join_paths(meson.project_source_root(), 'libvips'), - join_paths(meson.project_build_root(), 'libvips'), + join_paths(project_source_root, 'libvips'), + join_paths(project_build_root, 'libvips'), ], dependencies: libvips_dep, - scan_args: [ - '--ignore-headers=' + ' '.join(private_headers), - '--rebuild-types', - ], + ignore_headers: private_headers, + scan_args: '--rebuild-types', mkdb_args: [ '--default-includes=vips/vips.h' ], fixxref_args: [ '--html-dir=@0@'.format(docpath), @@ -152,7 +154,7 @@ gnome.gtkdoc('libvips', '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gobject')), '--extra-dir=@0@'.format(join_paths(glib_docpath, 'gio')), ], - content_files: [ content_files, markdown_content_files_docbook ], + content_files: [ content_files, markdown_content_files_docbook, version_xml ], expand_content_files: [ content_files ], html_assets: images, install: true diff --git a/doc/version.xml.in b/doc/version.xml.in new file mode 100644 index 00000000..5c53e293 --- /dev/null +++ b/doc/version.xml.in @@ -0,0 +1 @@ +@VIPS_VERSION@ \ No newline at end of file diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c index b312386e..476fa548 100644 --- a/libvips/deprecated/rename.c +++ b/libvips/deprecated/rename.c @@ -129,6 +129,12 @@ im_warning( const char *fmt, ... ) va_end( ap ); } +void * +vips_g_thread_join( GThread *thread ) +{ + return( g_thread_join( thread ) ); +} + int im_affine( IMAGE *in, IMAGE *out, double a, double b, double c, double d, double dx, double dy, @@ -823,3 +829,11 @@ vips_free( void *buf ) return( 0 ); } +/* Use vips_thread_isvips() instead. + */ +gboolean +vips_thread_isworker( void ) +{ + return( vips_thread_isvips() ); +} + diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h index 1f3df78b..4084134d 100644 --- a/libvips/include/vips/almostdeprecated.h +++ b/libvips/include/vips/almostdeprecated.h @@ -147,6 +147,9 @@ VIPS_DEPRECATED_FOR(g_warning) void im_warning( const char *fmt, ... ) G_GNUC_PRINTF( 1, 2 ); +VIPS_DEPRECATED_FOR(g_thread_join) +void *vips_g_thread_join( GThread *thread ); + VIPS_DEPRECATED int im_iterate( VipsImage *im, VipsStartFn start, im_generate_fn generate, VipsStopFn stop, @@ -398,6 +401,9 @@ void vips_vinfo( const char *domain, const char *fmt, va_list ap ); VIPS_DEPRECATED_FOR(vips_autorot) VipsAngle vips_autorot_get_angle( VipsImage *image ); +VIPS_DEPRECATED_FOR(vips_thread_isvips) +gboolean vips_thread_isworker( void ); + /* iofuncs */ VIPS_DEPRECATED_FOR(g_free) diff --git a/libvips/include/vips/semaphore.h b/libvips/include/vips/semaphore.h index 0762aa44..75a05f81 100644 --- a/libvips/include/vips/semaphore.h +++ b/libvips/include/vips/semaphore.h @@ -58,12 +58,14 @@ typedef struct { VIPS_API int vips_semaphore_up( VipsSemaphore *s ); VIPS_API -int vips_semaphore_down( VipsSemaphore *s ); -VIPS_API int vips_semaphore_upn( VipsSemaphore *s, int n ); VIPS_API +int vips_semaphore_down( VipsSemaphore *s ); +VIPS_API int vips_semaphore_downn( VipsSemaphore *s, int n ); VIPS_API +int vips_semaphore_down_timeout( VipsSemaphore *s, gint64 timeout ); +VIPS_API void vips_semaphore_destroy( VipsSemaphore *s ); VIPS_API void vips_semaphore_init( VipsSemaphore *s, int v, char *name ); diff --git a/libvips/include/vips/thread.h b/libvips/include/vips/thread.h index ac3cd528..347a7113 100644 --- a/libvips/include/vips/thread.h +++ b/libvips/include/vips/thread.h @@ -55,8 +55,6 @@ void vips_g_cond_free( GCond * ); */ VIPS_API GThread *vips_g_thread_new( const char *, GThreadFunc, gpointer ); -VIPS_API -void *vips_g_thread_join( GThread *thread ); VIPS_API gboolean vips_thread_isvips( void ); diff --git a/libvips/iofuncs/semaphore.c b/libvips/iofuncs/semaphore.c index d627a420..606217e9 100644 --- a/libvips/iofuncs/semaphore.c +++ b/libvips/iofuncs/semaphore.c @@ -110,37 +110,72 @@ vips_semaphore_up( VipsSemaphore *s ) } /* Wait for sem>n, then subtract n. + * Returns -1 when the monotonic time in @end_time was passed. */ -int -vips_semaphore_downn( VipsSemaphore *s, int n ) +static int +vips__semaphore_downn_until( VipsSemaphore *s, int n, gint64 end_time ) { int value_after_op; - VIPS_GATE_START( "vips_semaphore_downn: wait" ); + VIPS_GATE_START( "vips__semaphore_downn_until: wait" ); g_mutex_lock( s->mutex ); - while( s->v < n ) - g_cond_wait( s->cond, s->mutex ); + while( s->v < n ) { + if( end_time == -1 ) + g_cond_wait( s->cond, s->mutex ); + else if( !g_cond_wait_until( s->cond, s->mutex, end_time ) ) { + /* timeout has passed. + */ + g_mutex_unlock( s->mutex ); + + VIPS_GATE_STOP( "vips__semaphore_downn_until: wait" ); + return( -1 ); + } + } + s->v -= n; value_after_op = s->v; g_mutex_unlock( s->mutex ); #ifdef DEBUG_IO - printf( "vips_semaphore_downn(\"%s\",%d): %d\n", + printf( "vips__semaphore_downn_until(\"%s\",%d): %d\n", s->name, n, value_after_op ); #endif /*DEBUG_IO*/ - VIPS_GATE_STOP( "vips_semaphore_downn: wait" ); + VIPS_GATE_STOP( "vips__semaphore_downn_until: wait" ); return( value_after_op ); } -/* Wait for sem > 0, then decrement. +/* Wait for sem>n, then subtract n. n must be >= 0. Returns the new semaphore + * value. + */ +int +vips_semaphore_downn( VipsSemaphore *s, int n ) +{ + g_assert( n >= 0 ); + + return( vips__semaphore_downn_until( s, n, -1 ) ); +} + +/* Wait for sem > 0, then decrement. Returns the new semaphore value. */ int vips_semaphore_down( VipsSemaphore *s ) { - return( vips_semaphore_downn( s, 1 ) ); + return( vips__semaphore_downn_until( s, 1, -1 ) ); +} + +/* Wait for sem > 0, then decrement. + * Returns -1 when @timeout (in microseconds) has passed, or the new + * semaphore value. + */ +int +vips_semaphore_down_timeout( VipsSemaphore *s, gint64 timeout ) +{ + gint64 end_time = g_get_monotonic_time () + timeout; + + return( vips__semaphore_downn_until( s, 1, end_time ) ); } diff --git a/libvips/iofuncs/sinkscreen.c b/libvips/iofuncs/sinkscreen.c index 3728e8d6..ebf34c39 100644 --- a/libvips/iofuncs/sinkscreen.c +++ b/libvips/iofuncs/sinkscreen.c @@ -456,7 +456,7 @@ vips__render_shutdown( void ) vips_semaphore_up( &n_render_dirty_sem ); - (void) vips_g_thread_join( thread ); + (void) g_thread_join( thread ); } else g_mutex_unlock( render_dirty_lock ); diff --git a/libvips/iofuncs/thread.c b/libvips/iofuncs/thread.c index ae75f6b2..4859538c 100644 --- a/libvips/iofuncs/thread.c +++ b/libvips/iofuncs/thread.c @@ -182,19 +182,6 @@ vips_g_thread_new( const char *domain, GThreadFunc func, gpointer data ) return( thread ); } -void * -vips_g_thread_join( GThread *thread ) -{ - void *result; - - result = g_thread_join( thread ); - - VIPS_DEBUG_MSG_RED( "vips_g_thread_join: g_thread_join( %p )\n", - thread ); - - return( result ); -} - static int get_num_processors( void ) { diff --git a/libvips/iofuncs/threadset.c b/libvips/iofuncs/threadset.c index 4e9cb10c..06c91c4b 100644 --- a/libvips/iofuncs/threadset.c +++ b/libvips/iofuncs/threadset.c @@ -92,6 +92,11 @@ struct _VipsThreadset { int max_threads; }; +/* The maximum relative time (in microseconds) that a thread waits + * for work before being stopped. + */ +static const gint64 max_idle_time = 15 * G_TIME_SPAN_SECOND; + /* The thread work function. */ static void * @@ -101,9 +106,13 @@ vips_threadset_work( void *pointer ) VipsThreadset *set = member->set; for(;;) { - /* Wait to be given work. + /* Wait for at least 15 seconds to be given work. + */ + if( vips_semaphore_down_timeout( &member->idle, max_idle_time ) == -1 ) + break; + + /* Killed or no task available? Leave this thread. */ - vips_semaphore_down( &member->idle ); if( member->kill || !member->func ) break; @@ -134,9 +143,18 @@ vips_threadset_work( void *pointer ) g_mutex_unlock( set->lock ); } - /* Kill has been requested. We leave this thread on the members - * list so it can be found and joined. + /* Timed-out or kill has been requested ... remove from both free + * and member list. */ + g_mutex_lock( set->lock ); + set->free = g_slist_remove( set->free, member ); + set->members = g_slist_remove( set->members, member ); + set->n_threads -= 1; + g_mutex_unlock( set->lock ); + + vips_semaphore_destroy( &member->idle ); + + VIPS_FREE( member ); return( NULL ); } @@ -168,11 +186,16 @@ vips_threadset_add( VipsThreadset *set ) return( NULL ); } + /* Ensure idle threads are freed on exit, this + * ref is increased before the thread is joined. + */ + g_thread_unref( member->thread ); + g_mutex_lock( set->lock ); set->members = g_slist_prepend( set->members, member ); set->n_threads += 1; set->n_threads_highwater = - VIPS_MAX( set->n_threads_highwater, set->n_threads );; + VIPS_MAX( set->n_threads_highwater, set->n_threads ); g_mutex_unlock( set->lock ); return( member ); @@ -274,20 +297,17 @@ vips_threadset_run( VipsThreadset *set, static void vips_threadset_kill_member( VipsThreadsetMember *member ) { - VipsThreadset *set = member->set; + GThread *thread; + thread = g_thread_ref( member->thread ); member->kill = TRUE; + vips_semaphore_up( &member->idle ); - g_thread_join( member->thread ); - vips_semaphore_destroy( &member->idle ); + (void) g_thread_join( thread ); - g_mutex_lock( set->lock ); - set->free = g_slist_remove( set->free, member ); - set->n_threads -= 1; - g_mutex_unlock( set->lock ); - - VIPS_FREE( member ); + /* member is freed on thread exit. + */ } /** @@ -309,10 +329,8 @@ vips_threadset_free( VipsThreadset *set ) member = NULL; g_mutex_lock( set->lock ); - if( set->members ) { + if( set->members ) member = (VipsThreadsetMember *) set->members->data; - set->members = g_slist_remove( set->members, member ); - } g_mutex_unlock( set->lock ); if( !member ) diff --git a/meson.build b/meson.build index 15c0a4a5..05ede4b0 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('vips', 'c', 'cpp', - version: '8.14.0', - meson_version: '>=0.56', + version: '8.14.1', + meson_version: '>=0.55', default_options: [ # this is what glib uses (one of our required deps), so we use it too 'c_std=gnu99', @@ -19,7 +19,7 @@ version_patch = version_parts[2] # binary interface changed: increment current, reset revision to 0 # binary interface changes backwards compatible?: increment age # binary interface changes not backwards compatible?: reset age to 0 -library_revision = 0 +library_revision = 1 library_current = 58 library_age = 16 library_version = '@0@.@1@.@2@'.format(library_current - library_age, library_age, library_revision) @@ -32,8 +32,7 @@ i18n = import('i18n') add_project_arguments('-Drestrict=__restrict', language: 'cpp') -# if we're optimising (eg. release mode) we turn off cast checks and -# g_asserts +# if we're optimising (eg. release mode) we turn off cast checks and g_asserts if get_option('optimization') in ['2', '3', 's'] add_project_arguments('-DG_DISABLE_CAST_CHECKS', language : ['cpp', 'c']) add_project_arguments('-DG_DISABLE_CHECKS', language : ['cpp', 'c']) @@ -71,6 +70,10 @@ nodelete_link_args = cc.get_supported_link_arguments('-Wl,-z,nodelete') prefix_dir = get_option('prefix') lib_dir = prefix_dir / get_option('libdir') + +project_source_root = meson.current_source_dir() +project_build_root = meson.current_build_dir() + if gmodule_dep.found() and gmodule_dep.get_variable(pkgconfig: 'gmodule_supported') == 'true' # Disable modules by default when building static libraries modules_enabled = get_option('modules').enabled() or get_option('default_library') == 'shared' diff --git a/test/meson.build b/test/meson.build index 89a5e9cc..b80c499d 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,6 +1,6 @@ variables_data = configuration_data() -variables_data.set('abs_top_srcdir', meson.project_source_root()) -variables_data.set('abs_top_builddir', meson.project_build_root()) +variables_data.set('abs_top_srcdir', project_source_root) +variables_data.set('abs_top_builddir', project_build_root) variables_data.set('PYTHON', pymod.find_installation().full_path()) variables_sh = configure_file(