History

lavu: fix memory leaks by using a mutex instead of atomics

The buffer pool has to atomically add and remove entries from the linked list of available buffers. This was done by removing the entire list with a CAS operation, working on it, and then setting it back again (using a retry-loop in case another thread was doing the same thing). This could effectively cause memory leaks: while a thread was working on the buffer list, other threads would allocate new buffers, increasing the pool's total size. There was no real leak, but since these extra buffers were not needed, but not free'd either (except when the buffer pool was destroyed), this had the same effects as a real leak. For some reason, growth was exponential, and could easily kill the process due to OOM in real-world uses. Fix this by using a mutex to protect the list operations. The fancy way atomics remove the whole list to work on it is not needed anymore, which also avoids the situation which was causing the leak.

wm4 committed 12 years ago

Commit fbd6c971 parent 2443e522 files · +29 −56Browse files

Unified · Split

2 files
  1. libavutil/buffer.c +25 −54
  2. libavutil/buffer_internal.h +4 −2
libavutil/buffer.c+25−54
@@ -23,6 +23,7 @@ · 22 unmodified lines
2323 #include "buffer_internal.h"
2424 #include "common.h"
2525 #include "mem.h"
26+#include "thread.h"
2627
2728 AVBufferRef *av_buffer_create(uint8_t *data, int size,
2829 void (*free)(void *opaque, uint8_t *data),
@@ -199,6 +200,8 @@ AVBufferPool *av_buffer_pool_init(int size, AVBufferRef* (*alloc)(int size)) · 170 unmodified lines
199200 if (!pool)
200201 return NULL;
201202
203+ ff_mutex_init(&pool->mutex, NULL);
204+
202205 pool->size = size;
203206 pool->alloc = alloc ? alloc : av_buffer_alloc;
204207
@@ -220,6 +223,7 @@ static void buffer_pool_free(AVBufferPool *pool) · 15 unmodified lines
220223 buf->free(buf->opaque, buf->data);
221224 av_freep(&buf);
222225 }
226+ ff_mutex_destroy(&pool->mutex);
223227 av_freep(&pool);
224228 }
225229
@@ -236,47 +240,16 @@ void av_buffer_pool_uninit(AVBufferPool **ppool) · 10 unmodified lines
236240 buffer_pool_free(pool);
237241 }
238242
239−/* remove the whole buffer list from the pool and return it */
240−static BufferPoolEntry *get_pool(AVBufferPool *pool)
241−{
242− BufferPoolEntry *cur = NULL, *last = NULL;
243−
244− do {
245− FFSWAP(BufferPoolEntry*, cur, last);
246− cur = avpriv_atomic_ptr_cas((void * volatile *)&pool->pool, last, NULL);
247− if (!cur)
248− return NULL;
249− } while (cur != last);
250−
251− return cur;
252−}
253−
254−static void add_to_pool(BufferPoolEntry *buf)
255−{
256− AVBufferPool *pool;
257− BufferPoolEntry *cur, *end = buf;
258−
259− if (!buf)
260− return;
261− pool = buf->pool;
262−
263− while (end->next)
264− end = end->next;
265−
266− while ((cur = avpriv_atomic_ptr_cas((void * volatile *)&pool->pool, NULL, buf))) {
267− /* pool is not empty, retrieve it and append it to our list */
268− cur = get_pool(pool);
269− end->next = cur;
270− while (end->next)
271− end = end->next;
272− }
273−}
274−
275243 static void pool_release_buffer(void *opaque, uint8_t *data)
276244 {
277245 BufferPoolEntry *buf = opaque;
278246 AVBufferPool *pool = buf->pool;
279− add_to_pool(buf);
247+
248+ ff_mutex_lock(&pool->mutex);
249+ buf->next = pool->pool;
250+ pool->pool = buf;
251+ ff_mutex_unlock(&pool->mutex);
252+
280253 if (!avpriv_atomic_int_add_and_fetch(&pool->refcount, -1))
281254 buffer_pool_free(pool);
282255 }
@@ -306,8 +279,6 @@ static AVBufferRef *pool_alloc_buffer(AVBufferPool *pool) · 23 unmodified lines
306279 ret->buffer->opaque = buf;
307280 ret->buffer->free = pool_release_buffer;
308281
309− avpriv_atomic_int_add_and_fetch(&pool->refcount, 1);
310−
311282 return ret;
312283 }
313284
@@ -316,22 +287,22 @@ AVBufferRef *av_buffer_pool_get(AVBufferPool *pool) · 2 unmodified lines
316287 AVBufferRef *ret;
317288 BufferPoolEntry *buf;
318289
319− /* check whether the pool is empty */
320− buf = get_pool(pool);
321− if (!buf)
322− return pool_alloc_buffer(pool);
323−
324− /* keep the first entry, return the rest of the list to the pool */
325− add_to_pool(buf->next);
326− buf->next = NULL;
327−
328− ret = av_buffer_create(buf->data, pool->size, pool_release_buffer,
329− buf, 0);
330− if (!ret) {
331− add_to_pool(buf);
332− return NULL;
290+ ff_mutex_lock(&pool->mutex);
291+ buf = pool->pool;
292+ if (buf) {
293+ ret = av_buffer_create(buf->data, pool->size, pool_release_buffer,
294+ buf, 0);
295+ if (ret) {
296+ pool->pool = buf->next;
297+ buf->next = NULL;
298+ }
299+ } else {
300+ ret = pool_alloc_buffer(pool);
333301 }
334− avpriv_atomic_int_add_and_fetch(&pool->refcount, 1);
302+ ff_mutex_unlock(&pool->mutex);
303+
304+ if (ret)
305+ avpriv_atomic_int_add_and_fetch(&pool->refcount, 1);
335306
336307 return ret;
337308 }
libavutil/buffer_internal.h+4−2
@@ -22,6 +22,7 @@ · 21 unmodified lines
2222 #include <stdint.h>
2323
2424 #include "buffer.h"
25+#include "thread.h"
2526
2627 /**
2728 * The buffer is always treated as read-only.
@@ -68,11 +69,12 @@ typedef struct BufferPoolEntry { · 40 unmodified lines
6869 void (*free)(void *opaque, uint8_t *data);
6970
7071 AVBufferPool *pool;
71− struct BufferPoolEntry * volatile next;
72+ struct BufferPoolEntry *next;
7273 } BufferPoolEntry;
7374
7475 struct AVBufferPool {
75− BufferPoolEntry * volatile pool;
76+ AVMutex mutex;
77+ BufferPoolEntry *pool;
7678
7779 /*
7880 * This is used to track when the pool is to be freed.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t