diff mbox series

[FFmpeg-devel,24/42] avcodec/refstruct: Allow to share pools

Message ID AS8P250MB074434D09B045AD8BEEB38A38FFAA@AS8P250MB0744.EURP250.PROD.OUTLOOK.COM
State New
Headers show
Series New API for reference counting and ThreadFrames | expand

Commit Message

Andreas Rheinhardt Sept. 19, 2023, 7:57 p.m. UTC
To do this, make FFRefStructPool itself refcounted according
to the RefStruct API.

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
---
 libavcodec/refstruct.c | 29 ++++++++++++++++-------------
 libavcodec/refstruct.h |  5 ++++-
 2 files changed, 20 insertions(+), 14 deletions(-)

Comments

Anton Khirnov Oct. 12, 2023, 1:04 p.m. UTC | #1
Quoting Andreas Rheinhardt (2023-09-19 21:57:16)
> To do this, make FFRefStructPool itself refcounted according
> to the RefStruct API.
> 
> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
> ---
>  libavcodec/refstruct.c | 29 ++++++++++++++++-------------
>  libavcodec/refstruct.h |  5 ++++-
>  2 files changed, 20 insertions(+), 14 deletions(-)
> 
> diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
> index f8d040874d..2108ff8163 100644
> --- a/libavcodec/refstruct.c
> +++ b/libavcodec/refstruct.c
> @@ -187,7 +187,7 @@ static void pool_free(FFRefStructPool *pool)
>      pthread_mutex_destroy(&pool->mutex);
>      if (pool->free_cb)
>          pool->free_cb(pool->opaque);
> -    av_free(pool);
> +    av_free(get_refcount(pool));
>  }
>  
>  static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
> @@ -278,13 +278,17 @@ void *ff_refstruct_pool_get(FFRefStructPool *pool)
>      return ret;
>  }
>  
> -void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
> +static void pool_unref(void *ref)
>  {
> -    FFRefStructPool *pool = *poolp;
> -    RefCount *entry;
> +    FFRefStructPool *pool = get_userdata(ref);
> +    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)

Is there a reason you cannot fold pool->refcount into the pool's
containing RefStruct?
Andreas Rheinhardt Oct. 12, 2023, 1:51 p.m. UTC | #2
Anton Khirnov:
> Quoting Andreas Rheinhardt (2023-09-19 21:57:16)
>> To do this, make FFRefStructPool itself refcounted according
>> to the RefStruct API.
>>
>> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
>> ---
>>  libavcodec/refstruct.c | 29 ++++++++++++++++-------------
>>  libavcodec/refstruct.h |  5 ++++-
>>  2 files changed, 20 insertions(+), 14 deletions(-)
>>
>> diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
>> index f8d040874d..2108ff8163 100644
>> --- a/libavcodec/refstruct.c
>> +++ b/libavcodec/refstruct.c
>> @@ -187,7 +187,7 @@ static void pool_free(FFRefStructPool *pool)
>>      pthread_mutex_destroy(&pool->mutex);
>>      if (pool->free_cb)
>>          pool->free_cb(pool->opaque);
>> -    av_free(pool);
>> +    av_free(get_refcount(pool));
>>  }
>>  
>>  static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
>> @@ -278,13 +278,17 @@ void *ff_refstruct_pool_get(FFRefStructPool *pool)
>>      return ret;
>>  }
>>  
>> -void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
>> +static void pool_unref(void *ref)
>>  {
>> -    FFRefStructPool *pool = *poolp;
>> -    RefCount *entry;
>> +    FFRefStructPool *pool = get_userdata(ref);
>> +    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
> 
> Is there a reason you cannot fold pool->refcount into the pool's
> containing RefStruct?
> 

If I simply incremented the pool's refcount for every entry currently in
use by users, then the entries would only be freed when the last entry
has been returned and all the references to the pool unreferenced.

In fact, when I did this, I pondered two things: Shall I make
ff_refstruct_pool_uninit() free all the currently available buffers and
then unreference the caller's reference or shall I just make it a
wrapper to ff_refstruct_unref() to decrement the pool's refcount? The
latter is very simple and I did it; the former could be advantageous in
particular in case of frame-threading in case the dimensions change. (In
this scenario, no user will ever create new entries after the first user
unreferences a pool.)

- Andreas
Anton Khirnov Oct. 12, 2023, 2:04 p.m. UTC | #3
Quoting Andreas Rheinhardt (2023-10-12 15:51:18)
> Anton Khirnov:
> > Quoting Andreas Rheinhardt (2023-09-19 21:57:16)
> >> To do this, make FFRefStructPool itself refcounted according
> >> to the RefStruct API.
> >>
> >> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
> >> ---
> >>  libavcodec/refstruct.c | 29 ++++++++++++++++-------------
> >>  libavcodec/refstruct.h |  5 ++++-
> >>  2 files changed, 20 insertions(+), 14 deletions(-)
> >>
> >> diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
> >> index f8d040874d..2108ff8163 100644
> >> --- a/libavcodec/refstruct.c
> >> +++ b/libavcodec/refstruct.c
> >> @@ -187,7 +187,7 @@ static void pool_free(FFRefStructPool *pool)
> >>      pthread_mutex_destroy(&pool->mutex);
> >>      if (pool->free_cb)
> >>          pool->free_cb(pool->opaque);
> >> -    av_free(pool);
> >> +    av_free(get_refcount(pool));
> >>  }
> >>  
> >>  static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
> >> @@ -278,13 +278,17 @@ void *ff_refstruct_pool_get(FFRefStructPool *pool)
> >>      return ret;
> >>  }
> >>  
> >> -void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
> >> +static void pool_unref(void *ref)
> >>  {
> >> -    FFRefStructPool *pool = *poolp;
> >> -    RefCount *entry;
> >> +    FFRefStructPool *pool = get_userdata(ref);
> >> +    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
> > 
> > Is there a reason you cannot fold pool->refcount into the pool's
> > containing RefStruct?
> > 
> 
> If I simply incremented the pool's refcount for every entry currently in
> use by users, then the entries would only be freed when the last entry
> has been returned and all the references to the pool unreferenced.

Ok, can you please mention this in a comment somewhere? It's quite
non-obvious why do both pool_unref() and refstruct_pool_uninit() exist.

> In fact, when I did this, I pondered two things: Shall I make
> ff_refstruct_pool_uninit() free all the currently available buffers and
> then unreference the caller's reference or shall I just make it a
> wrapper to ff_refstruct_unref() to decrement the pool's refcount? The
> latter is very simple and I did it; the former could be advantageous in
> particular in case of frame-threading in case the dimensions change. (In
> this scenario, no user will ever create new entries after the first user
> unreferences a pool.)

But in other scenarios you might want to get rid of some pool references
while still using the others, so maybe that should be a flag if
anything.
Andreas Rheinhardt Oct. 12, 2023, 2:10 p.m. UTC | #4
Anton Khirnov:
> Quoting Andreas Rheinhardt (2023-10-12 15:51:18)
>> Anton Khirnov:
>>> Quoting Andreas Rheinhardt (2023-09-19 21:57:16)
>>>> To do this, make FFRefStructPool itself refcounted according
>>>> to the RefStruct API.
>>>>
>>>> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
>>>> ---
>>>>  libavcodec/refstruct.c | 29 ++++++++++++++++-------------
>>>>  libavcodec/refstruct.h |  5 ++++-
>>>>  2 files changed, 20 insertions(+), 14 deletions(-)
>>>>
>>>> diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
>>>> index f8d040874d..2108ff8163 100644
>>>> --- a/libavcodec/refstruct.c
>>>> +++ b/libavcodec/refstruct.c
>>>> @@ -187,7 +187,7 @@ static void pool_free(FFRefStructPool *pool)
>>>>      pthread_mutex_destroy(&pool->mutex);
>>>>      if (pool->free_cb)
>>>>          pool->free_cb(pool->opaque);
>>>> -    av_free(pool);
>>>> +    av_free(get_refcount(pool));
>>>>  }
>>>>  
>>>>  static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
>>>> @@ -278,13 +278,17 @@ void *ff_refstruct_pool_get(FFRefStructPool *pool)
>>>>      return ret;
>>>>  }
>>>>  
>>>> -void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
>>>> +static void pool_unref(void *ref)
>>>>  {
>>>> -    FFRefStructPool *pool = *poolp;
>>>> -    RefCount *entry;
>>>> +    FFRefStructPool *pool = get_userdata(ref);
>>>> +    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
>>>
>>> Is there a reason you cannot fold pool->refcount into the pool's
>>> containing RefStruct?
>>>
>>
>> If I simply incremented the pool's refcount for every entry currently in
>> use by users, then the entries would only be freed when the last entry
>> has been returned and all the references to the pool unreferenced.
> 
> Ok, can you please mention this in a comment somewhere? It's quite
> non-obvious why do both pool_unref() and refstruct_pool_uninit() exist.
> 
>> In fact, when I did this, I pondered two things: Shall I make
>> ff_refstruct_pool_uninit() free all the currently available buffers and
>> then unreference the caller's reference or shall I just make it a
>> wrapper to ff_refstruct_unref() to decrement the pool's refcount? The
>> latter is very simple and I did it; the former could be advantageous in
>> particular in case of frame-threading in case the dimensions change. (In
>> this scenario, no user will ever create new entries after the first user
>> unreferences a pool.)
> 
> But in other scenarios you might want to get rid of some pool references
> while still using the others, so maybe that should be a flag if
> anything.
> 

If I added a non-inlined ff_refstruct_pool_uninit(), I could simply
still allow to call ff_refstruct_unref() on pool-references, so that one
can get both behaviours. (Notice that this allows a malicious owner of a
pool-reference to drain the pool ad libitum, but you can do way worse
stuff with a reference than this.)

- Andreas
Andreas Rheinhardt Oct. 12, 2023, 5:09 p.m. UTC | #5
Anton Khirnov:
> Quoting Andreas Rheinhardt (2023-10-12 15:51:18)
>> Anton Khirnov:
>>> Quoting Andreas Rheinhardt (2023-09-19 21:57:16)
>>>> To do this, make FFRefStructPool itself refcounted according
>>>> to the RefStruct API.
>>>>
>>>> Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
>>>> ---
>>>>  libavcodec/refstruct.c | 29 ++++++++++++++++-------------
>>>>  libavcodec/refstruct.h |  5 ++++-
>>>>  2 files changed, 20 insertions(+), 14 deletions(-)
>>>>
>>>> diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
>>>> index f8d040874d..2108ff8163 100644
>>>> --- a/libavcodec/refstruct.c
>>>> +++ b/libavcodec/refstruct.c
>>>> @@ -187,7 +187,7 @@ static void pool_free(FFRefStructPool *pool)
>>>>      pthread_mutex_destroy(&pool->mutex);
>>>>      if (pool->free_cb)
>>>>          pool->free_cb(pool->opaque);
>>>> -    av_free(pool);
>>>> +    av_free(get_refcount(pool));
>>>>  }
>>>>  
>>>>  static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
>>>> @@ -278,13 +278,17 @@ void *ff_refstruct_pool_get(FFRefStructPool *pool)
>>>>      return ret;
>>>>  }
>>>>  
>>>> -void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
>>>> +static void pool_unref(void *ref)
>>>>  {
>>>> -    FFRefStructPool *pool = *poolp;
>>>> -    RefCount *entry;
>>>> +    FFRefStructPool *pool = get_userdata(ref);
>>>> +    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
>>>
>>> Is there a reason you cannot fold pool->refcount into the pool's
>>> containing RefStruct?
>>>
>>
>> If I simply incremented the pool's refcount for every entry currently in
>> use by users, then the entries would only be freed when the last entry
>> has been returned and all the references to the pool unreferenced.
> 
> Ok, can you please mention this in a comment somewhere? It's quite
> non-obvious why do both pool_unref() and refstruct_pool_uninit() exist.
> 

Right now I could move everything from pool_unref() at the end of
refstruct_pool_uninit(), leaving pool_unref() empty, but existing. The
actual reason that pool_unref() and refstruct_pool_uninit() are
different are:
1. The pool must not be freed when its refcount goes to zero, hence I
need to override its free callback.
2. I pondered adding support for weak references in RefStruct (in this
case the unref callback would be called when there is no strong
reference and the free function when there is no reference at all any
more). In this case I could give every pool entry in use a weak
reference to the pool; FFRefStructPool.refcount would then be
unnecessary, as the pool's weak refcount would take over its function.

- Andreas
diff mbox series

Patch

diff --git a/libavcodec/refstruct.c b/libavcodec/refstruct.c
index f8d040874d..2108ff8163 100644
--- a/libavcodec/refstruct.c
+++ b/libavcodec/refstruct.c
@@ -187,7 +187,7 @@  static void pool_free(FFRefStructPool *pool)
     pthread_mutex_destroy(&pool->mutex);
     if (pool->free_cb)
         pool->free_cb(pool->opaque);
-    av_free(pool);
+    av_free(get_refcount(pool));
 }
 
 static void pool_free_entry(FFRefStructPool *pool, RefCount *ref)
@@ -278,13 +278,17 @@  void *ff_refstruct_pool_get(FFRefStructPool *pool)
     return ret;
 }
 
-void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
+static void pool_unref(void *ref)
 {
-    FFRefStructPool *pool = *poolp;
-    RefCount *entry;
+    FFRefStructPool *pool = get_userdata(ref);
+    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
+        pool_free(pool);
+}
 
-    if (!pool)
-        return;
+static void refstruct_pool_uninit(FFRefStructOpaque unused, void *obj)
+{
+    FFRefStructPool *pool = obj;
+    RefCount *entry;
 
     pthread_mutex_lock(&pool->mutex);
     av_assert1(!pool->uninited);
@@ -298,11 +302,6 @@  void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
         pool_free_entry(pool, entry);
         entry = next;
     }
-
-    if (atomic_fetch_sub_explicit(&pool->refcount, 1, memory_order_acq_rel) == 1)
-        pool_free(pool);
-
-    *poolp = NULL;
 }
 
 FFRefStructPool *ff_refstruct_pool_alloc(size_t size, unsigned flags)
@@ -317,11 +316,13 @@  FFRefStructPool *ff_refstruct_pool_alloc_ext_c(size_t size, unsigned flags,
                                                void (*free_entry_cb)(FFRefStructOpaque opaque, void *obj),
                                                void (*free_cb)(FFRefStructOpaque opaque))
 {
-    FFRefStructPool *pool = av_mallocz(sizeof(*pool));
+    FFRefStructPool *pool = ff_refstruct_alloc_ext(sizeof(*pool), 0, NULL,
+                                                   refstruct_pool_uninit);
     int err;
 
     if (!pool)
         return NULL;
+    get_refcount(pool)->free = pool_unref;
 
     pool->size          = size;
     pool->opaque        = opaque;
@@ -348,7 +349,9 @@  FFRefStructPool *ff_refstruct_pool_alloc_ext_c(size_t size, unsigned flags,
 
     err = pthread_mutex_init(&pool->mutex, NULL);
     if (err) {
-        av_free(pool);
+        // Don't call ff_refstruct_uninit() on pool, as it hasn't been properly
+        // set up and is just a POD right now.
+        av_free(get_refcount(pool));
         return NULL;
     }
     return pool;
diff --git a/libavcodec/refstruct.h b/libavcodec/refstruct.h
index ce3830977f..d525be61e8 100644
--- a/libavcodec/refstruct.h
+++ b/libavcodec/refstruct.h
@@ -285,6 +285,9 @@  void *ff_refstruct_pool_get(FFRefStructPool *pool);
  * @param poolp pointer to a pointer to either NULL or a pool to be freed.
  *              `*poolp` will be set to NULL.
  */
-void ff_refstruct_pool_uninit(FFRefStructPool **poolp);
+static inline void ff_refstruct_pool_uninit(FFRefStructPool **poolp)
+{
+    ff_refstruct_unref(poolp);
+}
 
 #endif /* AVCODEC_REFSTRUCT_H */