1 /* Manage sets of pixel buffers on an image.
2  *
3  * 30/10/06
4  *	- from window.c
5  * 2/2/07
6  * 	- speed up the search, use our own lock (thanks Christian)
7  * 5/2/07
8  * 	- split to many buffer lists per image
9  * 11/2/07
10  * 	- split to a buffer hash per thread
11  * 	- reuse buffer mallocs when we can
12  * 20/2/07
13  * 	- add VipsBufferCacheList and we can avoid some hash ops on
14  * 	  done/undone
15  * 5/3/10
16  * 	- move invalid stuff to region
17  * 	- move link maintenance to im_demand_hint
18  * 21/9/11
19  * 	- switch to vips_tracked_malloc()
20  * 18/12/13
21  * 	- keep a few buffers in reserve per image, stops malloc/free
22  * 	  cycling when sharing is repeatedly discovered
23  * 6/6/16
24  * 	- free buffers on image close as well as thread exit, so main thread
25  * 	  buffers don't clog up the system
26  * 13/10/16
27  * 	- better solution: don't keep a buffercache for non-workers
28  */
29 
30 /*
31 
32     This file is part of VIPS.
33 
34     VIPS is free software; you can redistribute it and/or modify
35     it under the terms of the GNU Lesser General Public License as published by
36     the Free Software Foundation; either version 2 of the License, or
37     (at your option) any later version.
38 
39     This program is distributed in the hope that it will be useful,
40     but WITHOUT ANY WARRANTY; without even the implied warranty of
41     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
42     GNU Lesser General Public License for more details.
43 
44     You should have received a copy of the GNU Lesser General Public License
45     along with this program; if not, write to the Free Software
46     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
47     02110-1301  USA
48 
49  */
50 
51 /*
52 
53     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
54 
55  */
56 
57 /*
58 #define DEBUG_VERBOSE
59 #define DEBUG_CREATE
60 #define DEBUG
61  */
62 
63 #ifdef HAVE_CONFIG_H
64 #include <config.h>
65 #endif /*HAVE_CONFIG_H*/
66 #include <vips/intl.h>
67 
68 #include <stdio.h>
69 #include <stdlib.h>
70 
71 #include <vips/vips.h>
72 #include <vips/internal.h>
73 #include <vips/thread.h>
74 
75 #ifdef DEBUG
76 /* Track all buffers here for debugging.
77  */
78 static GSList *vips__buffer_all = NULL;
79 #endif /*DEBUG*/
80 
81 #ifdef DEBUG_CREATE
82 static GSList *vips__buffer_cache_all = NULL;
83 #endif /*DEBUG_CREATE*/
84 
85 /* The maximum numbers of buffers we hold in reserve per image.
86  */
87 static const int buffer_cache_max_reserve = 2;
88 
89 /* Workers have a BufferThread (and BufferCache) in a GPrivate they have
90  * exclusive access to.
91  */
92 static GPrivate *buffer_thread_key = NULL;
93 
94 void
vips_buffer_print(VipsBuffer * buffer)95 vips_buffer_print( VipsBuffer *buffer )
96 {
97 	printf( "VipsBuffer: %p ref_count = %d, ", buffer, buffer->ref_count );
98 	printf( "im = %p, ", buffer->im );
99 	printf( "area.left = %d, ", buffer->area.left );
100 	printf( "area.top = %d, ", buffer->area.top );
101 	printf( "area.width = %d, ", buffer->area.width );
102 	printf( "area.height = %d, ", buffer->area.height );
103 	printf( "done = %d, ", buffer->done );
104 	printf( "cache = %p, ", buffer->cache );
105 	printf( "buf = %p, ", buffer->buf );
106 	printf( "bsize = %zd\n", buffer->bsize );
107 }
108 
109 #ifdef DEBUG
110 static void *
vips_buffer_dump(VipsBuffer * buffer,size_t * reserve,size_t * alive)111 vips_buffer_dump( VipsBuffer *buffer, size_t *reserve, size_t *alive )
112 {
113 	vips_buffer_print( buffer );
114 
115 	g_assert( buffer->im );
116 	g_assert( buffer->buf );
117 
118 	if( !buffer->cache &&
119 		!buffer->done ) {
120 		/* Global buffer, not linked to any cache.
121 		 */
122 		printf( "global buffer %p, %.3g MB\n",
123 			buffer, buffer->bsize / (1024 * 1024.0) );
124 		*alive += buffer->bsize;
125 	}
126 
127 	else if( buffer->cache &&
128 		buffer->done &&
129 		!vips_rect_isempty( &buffer->area ) &&
130 		g_slist_find( buffer->cache->buffers, buffer ) ) {
131 		/* Published on a thread.
132 		 */
133 		printf( "thread buffer %p, %.3g MB\n",
134 			buffer, buffer->bsize / (1024 * 1024.0) );
135 		*alive += buffer->bsize;
136 	}
137 
138 	else if( buffer->ref_count == 0 &&
139 		buffer->cache &&
140 		!buffer->done &&
141 		vips_rect_isempty( &buffer->area ) &&
142 		g_slist_find( buffer->cache->reserve, buffer ) )
143 		/* Held in reserve.
144 		 */
145 		*reserve += buffer->bsize;
146 
147 	else
148 		printf( "buffer craziness!\n" );
149 
150 	return( NULL );
151 }
152 #endif /*DEBUG*/
153 
154 #ifdef DEBUG_CREATE
155 static void *
vips_buffer_cache_dump(VipsBufferCache * cache,void * a,void * b)156 vips_buffer_cache_dump( VipsBufferCache *cache, void *a, void *b )
157 {
158 	printf( "VipsBufferCache: %p\n", cache );
159 	printf( "\t%d buffers\n", g_slist_length( cache->buffers ) );
160 	printf( "\tthread %p\n", cache->thread );
161 	printf( "\timage %p\n", cache->im );
162 	printf( "\tbuffer_thread %p\n", cache->buffer_thread );
163 	printf( "\t%d in reserve\n", g_slist_length( cache->reserve ) );
164 
165 	return( NULL );
166 }
167 #endif /*DEBUG_CREATE*/
168 
169 void
vips_buffer_dump_all(void)170 vips_buffer_dump_all( void )
171 {
172 #ifdef DEBUG
173 	if( vips__buffer_all ) {
174 		size_t reserve;
175 		size_t alive;
176 
177 		printf( "buffers:\n" );
178 
179 		reserve = 0;
180 		alive = 0;
181 		vips_slist_map2( vips__buffer_all,
182 			(VipsSListMap2Fn) vips_buffer_dump, &reserve, &alive );
183 		printf( "%.3g MB alive\n", alive / (1024 * 1024.0) );
184 		printf( "%.3g MB in reserve\n", reserve / (1024 * 1024.0) );
185 	}
186 
187 #ifdef DEBUG_CREATE
188 	if( vips__buffer_cache_all ) {
189 		printf( "buffers: %d buffer cache still alive\n",
190 			g_slist_length( vips__buffer_cache_all ) );
191 		vips_slist_map2( vips__buffer_cache_all,
192 			(VipsSListMap2Fn) vips_buffer_cache_dump, NULL, NULL );
193 		printf( "g_thread_self() == %p\n", g_thread_self() );
194 	}
195 #endif /*DEBUG_CREATE*/
196 #endif /*DEBUG*/
197 }
198 
199 static void
vips_buffer_free(VipsBuffer * buffer)200 vips_buffer_free( VipsBuffer *buffer )
201 {
202 	VIPS_FREEF( vips_tracked_free, buffer->buf );
203 	buffer->bsize = 0;
204 	g_free( buffer );
205 
206 #ifdef DEBUG
207 	g_mutex_lock( vips__global_lock );
208 
209 	g_assert( g_slist_find( vips__buffer_all, buffer ) );
210 	vips__buffer_all = g_slist_remove( vips__buffer_all, buffer );
211 
212 	g_mutex_unlock( vips__global_lock );
213 #endif /*DEBUG*/
214 
215 #ifdef DEBUG_VERBOSE
216 	printf( "vips_buffer_free: freeing buffer %p\n", buffer );
217 #endif /*DEBUG_VERBOSE*/
218 }
219 
220 static void
buffer_thread_free(VipsBufferThread * buffer_thread)221 buffer_thread_free( VipsBufferThread *buffer_thread )
222 {
223 	VIPS_FREEF( g_hash_table_destroy, buffer_thread->hash );
224 	VIPS_FREE( buffer_thread );
225 }
226 
227 /* Run for GDestroyNotify on the VipsBufferThread hash.
228  */
229 static void
buffer_cache_free(VipsBufferCache * cache)230 buffer_cache_free( VipsBufferCache *cache )
231 {
232 	GSList *p;
233 
234 #ifdef DEBUG_CREATE
235 	g_mutex_lock( vips__global_lock );
236 	vips__buffer_cache_all =
237 		g_slist_remove( vips__buffer_cache_all, cache );
238 	g_mutex_unlock( vips__global_lock );
239 
240 	printf( "buffer_cache_free: freeing cache %p on thread %p\n",
241 		cache, g_thread_self() );
242 	printf( "\t(%d caches left)\n",
243 		g_slist_length( vips__buffer_cache_all ) );
244 #endif /*DEBUG_CREATE*/
245 
246 	/* Need to mark undone so we don't try and take them off this cache on
247 	 * unref.
248 	 */
249 	for( p = cache->buffers; p; p = p->next ) {
250 		VipsBuffer *buffer = (VipsBuffer *) p->data;
251 
252 		g_assert( buffer->done );
253 		g_assert( buffer->cache == cache );
254 
255 		buffer->done = FALSE;
256 		buffer->cache = NULL;
257 	}
258 	VIPS_FREEF( g_slist_free, cache->buffers );
259 
260 	for( p = cache->reserve; p; p = p->next ) {
261 		VipsBuffer *buffer = (VipsBuffer *) p->data;
262 
263 		vips_buffer_free( buffer );
264 	}
265 	VIPS_FREEF( g_slist_free, cache->reserve );
266 
267 	g_free( cache );
268 }
269 
270 static VipsBufferCache *
buffer_cache_new(VipsBufferThread * buffer_thread,VipsImage * im)271 buffer_cache_new( VipsBufferThread *buffer_thread, VipsImage *im )
272 {
273 	VipsBufferCache *cache;
274 
275 	cache = g_new( VipsBufferCache, 1 );
276 	cache->buffers = NULL;
277 	cache->thread = g_thread_self();
278 	cache->im = im;
279 	cache->buffer_thread = buffer_thread;
280 	cache->reserve = NULL;
281 	cache->n_reserve = 0;
282 
283 #ifdef DEBUG_CREATE
284 	g_mutex_lock( vips__global_lock );
285 	vips__buffer_cache_all =
286 		g_slist_prepend( vips__buffer_cache_all, cache );
287 	g_mutex_unlock( vips__global_lock );
288 
289 	printf( "buffer_cache_new: new cache %p for thread %p on image %p\n",
290 		cache, g_thread_self(), im );
291 	printf( "\t(%d caches now)\n",
292 		g_slist_length( vips__buffer_cache_all ) );
293 #endif /*DEBUG_CREATE*/
294 
295 	return( cache );
296 }
297 
298 static VipsBufferThread *
buffer_thread_new(void)299 buffer_thread_new( void )
300 {
301 	VipsBufferThread *buffer_thread;
302 
303 	buffer_thread = g_new( VipsBufferThread, 1 );
304 	buffer_thread->hash = g_hash_table_new_full(
305 		g_direct_hash, g_direct_equal,
306 		NULL, (GDestroyNotify) buffer_cache_free );
307 	buffer_thread->thread = g_thread_self();
308 
309 	return( buffer_thread );
310 }
311 
312 /* Get our private VipsBufferThread. NULL for non-worker threads.
313  */
314 static VipsBufferThread *
buffer_thread_get(void)315 buffer_thread_get( void )
316 {
317 	VipsBufferThread *buffer_thread;
318 
319 	if( vips_thread_isworker() ) {
320 		/* Workers get a private set of buffers.
321 		 */
322 		if( !(buffer_thread = g_private_get( buffer_thread_key )) ) {
323 			buffer_thread = buffer_thread_new();
324 			g_private_set( buffer_thread_key, buffer_thread );
325 		}
326 
327 		g_assert( buffer_thread->thread == g_thread_self() );
328 	}
329 	else
330 		/* Non-workers don't have one.
331 		 */
332 		buffer_thread = NULL;
333 
334 	return( buffer_thread );
335 }
336 
337 /* Get the VipsBufferCache for this image, or NULL for a non-worker.
338  */
339 static VipsBufferCache *
buffer_cache_get(VipsImage * im)340 buffer_cache_get( VipsImage *im )
341 {
342 	VipsBufferThread *buffer_thread;
343 	VipsBufferCache *cache;
344 
345 	if( (buffer_thread = buffer_thread_get()) ) {
346 		if( !(cache = (VipsBufferCache *)
347 			g_hash_table_lookup( buffer_thread->hash, im )) ) {
348 			cache = buffer_cache_new( buffer_thread, im );
349 			g_hash_table_insert( buffer_thread->hash, im, cache );
350 		}
351 
352 		g_assert( cache->thread == g_thread_self() );
353 	}
354 	else
355 		cache = NULL;
356 
357 	return( cache );
358 }
359 
360 /* Pixels have been calculated: publish for other parts of this thread to see.
361  */
362 void
vips_buffer_done(VipsBuffer * buffer)363 vips_buffer_done( VipsBuffer *buffer )
364 {
365 	VipsImage *im = buffer->im;
366 	VipsBufferCache *cache;
367 
368 	if( !buffer->done &&
369 		(cache = buffer_cache_get( im )) ) {
370 		g_assert( !g_slist_find( cache->buffers, buffer ) );
371 		g_assert( !buffer->cache );
372 
373 		buffer->done = TRUE;
374 		buffer->cache = cache;
375 
376 		cache->buffers = g_slist_prepend( cache->buffers, buffer );
377 
378 #ifdef DEBUG_VERBOSE
379 		printf( "vips_buffer_done: "
380 			"thread %p adding buffer %p to cache %p\n",
381 			g_thread_self(), buffer, cache );
382 		vips_buffer_print( buffer );
383 #endif /*DEBUG_VERBOSE*/
384 	}
385 }
386 
387 /* Take off the public 'done' list. Make sure it has no calculated pixels in.
388  */
389 void
vips_buffer_undone(VipsBuffer * buffer)390 vips_buffer_undone( VipsBuffer *buffer )
391 {
392 	if( buffer->done ) {
393 		VipsBufferCache *cache = buffer->cache;
394 
395 #ifdef DEBUG_VERBOSE
396 		printf( "vips_buffer_undone: thread %p removing "
397 			"buffer %p from cache %p\n",
398 			g_thread_self(), buffer, cache );
399 #endif /*DEBUG_VERBOSE*/
400 
401 		g_assert( cache->thread == g_thread_self() );
402 		g_assert( cache->buffer_thread->thread == cache->thread );
403 		g_assert( g_slist_find( cache->buffers, buffer ) );
404 		g_assert( buffer_thread_get() );
405 		g_assert( cache->buffer_thread == buffer_thread_get() );
406 
407 		cache->buffers = g_slist_remove( cache->buffers, buffer );
408 		buffer->done = FALSE;
409 
410 #ifdef DEBUG_VERBOSE
411 		printf( "vips_buffer_undone: %d buffers left\n",
412 			g_slist_length( cache->buffers ) );
413 #endif /*DEBUG_VERBOSE*/
414 	}
415 
416 	buffer->cache = NULL;
417 	buffer->area.width = 0;
418 	buffer->area.height = 0;
419 }
420 
421 void
vips_buffer_unref(VipsBuffer * buffer)422 vips_buffer_unref( VipsBuffer *buffer )
423 {
424 #ifdef DEBUG_VERBOSE
425 	printf( "** vips_buffer_unref: left = %d, top = %d, "
426 		"width = %d, height = %d (%p)\n",
427 		buffer->area.left, buffer->area.top,
428 		buffer->area.width, buffer->area.height,
429 		buffer );
430 #endif /*DEBUG_VERBOSE*/
431 
432 	g_assert( buffer->ref_count > 0 );
433 
434 	buffer->ref_count -= 1;
435 
436 	if( buffer->ref_count == 0 ) {
437 		VipsBufferCache *cache;
438 
439 #ifdef DEBUG_VERBOSE
440 		if( !buffer->done )
441 			printf( "vips_buffer_unref: buffer was not done\n" );
442 #endif /*DEBUG_VERBOSE*/
443 
444 		vips_buffer_undone( buffer );
445 
446 		/* Place on this thread's reserve list for reuse.
447 		 */
448 		if( (cache = buffer_cache_get( buffer->im )) &&
449 			cache->n_reserve < buffer_cache_max_reserve ) {
450 			g_assert( !buffer->cache );
451 
452 			cache->reserve =
453 				g_slist_prepend( cache->reserve, buffer );
454 			cache->n_reserve += 1;
455 
456 			buffer->cache = cache;
457 			buffer->area.width = 0;
458 			buffer->area.height = 0;
459 		}
460 		else
461 			vips_buffer_free( buffer );
462 	}
463 }
464 
465 static int
buffer_move(VipsBuffer * buffer,VipsRect * area)466 buffer_move( VipsBuffer *buffer, VipsRect *area )
467 {
468 	VipsImage *im = buffer->im;
469 	size_t new_bsize;
470 
471 	g_assert( buffer->ref_count == 1 );
472 
473 	vips_buffer_undone( buffer );
474 	g_assert( !buffer->done );
475 
476 	buffer->area = *area;
477 
478 	new_bsize = (size_t) VIPS_IMAGE_SIZEOF_PEL( im ) *
479 		area->width * area->height;
480 	if( buffer->bsize < new_bsize ||
481 		!buffer->buf ) {
482 		buffer->bsize = new_bsize;
483 		VIPS_FREEF( vips_tracked_free, buffer->buf );
484 		if( !(buffer->buf = vips_tracked_malloc( buffer->bsize )) )
485 			return( -1 );
486 	}
487 
488 	return( 0 );
489 }
490 
491 /* Make a new buffer.
492  */
493 VipsBuffer *
vips_buffer_new(VipsImage * im,VipsRect * area)494 vips_buffer_new( VipsImage *im, VipsRect *area )
495 {
496 	VipsBufferCache *cache;
497 	VipsBuffer *buffer;
498 
499 	if( (cache = buffer_cache_get( im )) &&
500 		cache->reserve ) {
501 		buffer = (VipsBuffer *) cache->reserve->data;
502 		cache->reserve = g_slist_remove( cache->reserve, buffer );
503 		cache->n_reserve -= 1;
504 
505 		g_assert( buffer->im == im );
506 		g_assert( buffer->done == FALSE );
507 		g_assert( buffer->cache );
508 
509 		buffer->ref_count = 1;
510 		buffer->done = FALSE;
511 		buffer->cache = NULL;
512 	}
513 	else {
514 		buffer = g_new0( VipsBuffer, 1 );
515 		buffer->ref_count = 1;
516 		buffer->im = im;
517 		buffer->done = FALSE;
518 		buffer->cache = NULL;
519 		buffer->buf = NULL;
520 		buffer->bsize = 0;
521 
522 #ifdef DEBUG
523 		g_mutex_lock( vips__global_lock );
524 		vips__buffer_all =
525 			g_slist_prepend( vips__buffer_all, buffer );
526 		g_mutex_unlock( vips__global_lock );
527 #endif /*DEBUG*/
528 	}
529 
530 	if( buffer_move( buffer, area ) ) {
531 		vips_buffer_free( buffer );
532 		return( NULL );
533 	}
534 
535 	return( buffer );
536 }
537 
538 /* Find an existing buffer that encloses area and return a ref. Or NULL for no
539  * existing buffer.
540  */
541 static VipsBuffer *
buffer_find(VipsImage * im,VipsRect * r)542 buffer_find( VipsImage *im, VipsRect *r )
543 {
544 	VipsBufferCache *cache;
545 	VipsBuffer *buffer;
546 	GSList *p;
547 	VipsRect *area;
548 
549 	if( !(cache = buffer_cache_get( im )) )
550 		return( NULL );
551 
552 	/* This needs to be quick :-( don't use
553 	 * vips_slist_map2()/vips_rect_includesrect(), do the search
554 	 * inline.
555 	 *
556 	 * FIXME we return the first enclosing buffer, perhaps we should
557 	 * search for the largest?
558 	 */
559 	for( p = cache->buffers; p; p = p->next ) {
560 		buffer = (VipsBuffer *) p->data;
561 		area = &buffer->area;
562 
563 		if( area->left <= r->left &&
564 			area->top <= r->top &&
565 			area->left + area->width >= r->left + r->width &&
566 			area->top + area->height >= r->top + r->height ) {
567 			buffer->ref_count += 1;
568 
569 #ifdef DEBUG_VERBOSE
570 			printf( "buffer_find: left = %d, top = %d, "
571 				"width = %d, height = %d, count = %d (%p)\n",
572 				buffer->area.left, buffer->area.top,
573 				buffer->area.width, buffer->area.height,
574 				buffer->ref_count,
575 				buffer );
576 #endif /*DEBUG_VERBOSE*/
577 
578 			return( buffer );
579 		}
580 	}
581 
582 	return( NULL );
583 }
584 
585 /* Return a ref to a buffer that encloses area. The buffer we return might be
586  * done.
587  */
588 VipsBuffer *
vips_buffer_ref(VipsImage * im,VipsRect * area)589 vips_buffer_ref( VipsImage *im, VipsRect *area )
590 {
591 	VipsBuffer *buffer;
592 
593 	if( (buffer = buffer_find( im, area )) )
594 		return( buffer );
595 	else
596 		return( vips_buffer_new( im, area ) );
597 }
598 
599 /* Unref old, ref new, in a single operation. Reuse stuff if we can. The
600  * buffer we return might or might not be done.
601  */
602 VipsBuffer *
vips_buffer_unref_ref(VipsBuffer * old_buffer,VipsImage * im,VipsRect * area)603 vips_buffer_unref_ref( VipsBuffer *old_buffer, VipsImage *im, VipsRect *area )
604 {
605 	VipsBuffer *buffer;
606 
607 	g_assert( !old_buffer ||
608 		old_buffer->im == im );
609 
610 	/* Is the current buffer OK?
611 	 */
612 	if( old_buffer &&
613 		vips_rect_includesrect( &old_buffer->area, area ) )
614 		return( old_buffer );
615 
616 	/* Does the new area already have a buffer?
617 	 */
618 	if( (buffer = buffer_find( im, area )) ) {
619 		VIPS_FREEF( vips_buffer_unref, old_buffer );
620 		return( buffer );
621 	}
622 
623 	/* Is the current buffer unshared? We can just move it.
624 	 */
625 	if( old_buffer &&
626 		old_buffer->ref_count == 1 ) {
627 		if( buffer_move( old_buffer, area ) ) {
628 			vips_buffer_unref( old_buffer );
629 			return( NULL );
630 		}
631 
632 		return( old_buffer );
633 	}
634 
635 	/* Fallback ... unref the old one, make a new one.
636 	 */
637 	VIPS_FREEF( vips_buffer_unref, old_buffer );
638 	if( !(buffer = vips_buffer_new( im, area )) )
639 		return( NULL );
640 
641 	return( buffer );
642 }
643 
644 static void
buffer_thread_destroy_notify(VipsBufferThread * buffer_thread)645 buffer_thread_destroy_notify( VipsBufferThread *buffer_thread )
646 {
647 	/* We only come here if vips_thread_shutdown() was not called for this
648 	 * thread. Do our best to clean up.
649 	 *
650 	 * GPrivate has stopped working by this point in destruction, be
651 	 * careful not to touch that.
652 	 */
653 	buffer_thread_free( buffer_thread );
654 }
655 
656 /* Init the buffer cache system. This is called during vips_init.
657  */
658 void
vips__buffer_init(void)659 vips__buffer_init( void )
660 {
661 	static GPrivate private =
662 		G_PRIVATE_INIT( (GDestroyNotify) buffer_thread_destroy_notify );
663 
664 	buffer_thread_key = &private;
665 
666 	if( buffer_cache_max_reserve < 1 )
667 		printf( "vips__buffer_init: buffer reserve disabled\n" );
668 
669 #ifdef DEBUG
670 	printf( "vips__buffer_init: DEBUG enabled\n" );
671 #endif /*DEBUG*/
672 
673 #ifdef DEBUG_CREATE
674 	printf( "vips__buffer_init: DEBUG_CREATE enabled\n" );
675 #endif /*DEBUG_CREATE*/
676 }
677 
678 void
vips__buffer_shutdown(void)679 vips__buffer_shutdown( void )
680 {
681 	VipsBufferThread *buffer_thread;
682 
683 	if( (buffer_thread = g_private_get( buffer_thread_key )) ) {
684 		buffer_thread_free( buffer_thread );
685 		g_private_set( buffer_thread_key, NULL );
686 	}
687 }
688