Date: Mon, 11 Dec 2023 17:46:00 +0200
On Mon, Dec 11, 2023 at 4:54 PM Jonathan Wakely via Std-Proposals <
std-proposals_at_[hidden]> wrote:
>
>
> On Mon, 11 Dec 2023 at 14:43, Andrey Semashev via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On 12/11/23 15:00, Valentin Palade via Std-Proposals wrote:
>> > Hello Everyone,
>> >
>> > I would like your opinion on a small addition to the std::shared_ptr:
>> >
>> > bool std::shared_ptr::resurrect();
>> >
>> > with the following behavior:
>> >
>> > * will atomically decrement the use counter and
>> > o if use counter != 0 will reset the shared_ptr instance and
>> > return false
>> > o otherwise, increment the use counter, leave the shared_ptr
>> > instance unchanged and return true (thus resurrecting the
>> > shared_ptr instance).
>> >
>> > Usage scenario:
>> >
>> > * producer creates a shared_ptr message to be sent to multiple
>> > consumers - threads/actors/connections
>> > * on consumers, we want to be able to return the shared_ptr message to
>> > the producer (e.g. to be reused), once consumed by all.
>>
>> Why can't you use use_count()? If it returns 1, your shared_ptr is the
>> last instance and you may reuse it, as if your proposed resurrect()
>> returned true.
>>
>> Note that this is thread safe since use_count() == 1 means there are no
>> other threads that may concurrently modify the reference counter.
>>
>
> That's not true. If the same shared_ptr object is visible in other
> threads, they could make a copy of it and increase the refcount. That could
> happen between calling use_count() and reusing it, a TOCTTOU race. Making
> the copy and calling use_count() are both const members, so the standard
> says you're allowed to do that concurrently.
>
> The proposed resurrect() function "solves" this by being a non-const
> member, so if another thread concurrently makes a copy of that object they
> have a data race and so UB. So they're not allowed to make that copy.
>
>
> But another way to do the same is:
>
> std::shared_ptr<T> sm2(std::move(sm));
> if (sm2.use_count())
>
> Here we have a new local object which we know is not visible to any other
> threads, so if its use_count() is 1 then this really is a unique reference.
> Since moving from sm is a non-const operation, other threads can't do
> anything with sm concurrently.
>
std::shared_ptr<T> sm2(std::move(sm));
will preserve the use count in the control block attached to the object
referenced by sm so the if (sm2.use_count()) will be similar to if
(sm.use_count()).
The idea is that the validation that we are the last instance referencing a
certain object, must be made atomically - i.e., in terms of std::atomic, we
can have something like:
if(pcontrol_block_->use_counter_.fetch_sub(1) == 1){
//do the resurrection:
pcontrol_block_->use_counter_ = 1;
return true;
}
Valentin
std-proposals_at_[hidden]> wrote:
>
>
> On Mon, 11 Dec 2023 at 14:43, Andrey Semashev via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> On 12/11/23 15:00, Valentin Palade via Std-Proposals wrote:
>> > Hello Everyone,
>> >
>> > I would like your opinion on a small addition to the std::shared_ptr:
>> >
>> > bool std::shared_ptr::resurrect();
>> >
>> > with the following behavior:
>> >
>> > * will atomically decrement the use counter and
>> > o if use counter != 0 will reset the shared_ptr instance and
>> > return false
>> > o otherwise, increment the use counter, leave the shared_ptr
>> > instance unchanged and return true (thus resurrecting the
>> > shared_ptr instance).
>> >
>> > Usage scenario:
>> >
>> > * producer creates a shared_ptr message to be sent to multiple
>> > consumers - threads/actors/connections
>> > * on consumers, we want to be able to return the shared_ptr message to
>> > the producer (e.g. to be reused), once consumed by all.
>>
>> Why can't you use use_count()? If it returns 1, your shared_ptr is the
>> last instance and you may reuse it, as if your proposed resurrect()
>> returned true.
>>
>> Note that this is thread safe since use_count() == 1 means there are no
>> other threads that may concurrently modify the reference counter.
>>
>
> That's not true. If the same shared_ptr object is visible in other
> threads, they could make a copy of it and increase the refcount. That could
> happen between calling use_count() and reusing it, a TOCTTOU race. Making
> the copy and calling use_count() are both const members, so the standard
> says you're allowed to do that concurrently.
>
> The proposed resurrect() function "solves" this by being a non-const
> member, so if another thread concurrently makes a copy of that object they
> have a data race and so UB. So they're not allowed to make that copy.
>
>
> But another way to do the same is:
>
> std::shared_ptr<T> sm2(std::move(sm));
> if (sm2.use_count())
>
> Here we have a new local object which we know is not visible to any other
> threads, so if its use_count() is 1 then this really is a unique reference.
> Since moving from sm is a non-const operation, other threads can't do
> anything with sm concurrently.
>
std::shared_ptr<T> sm2(std::move(sm));
will preserve the use count in the control block attached to the object
referenced by sm so the if (sm2.use_count()) will be similar to if
(sm.use_count()).
The idea is that the validation that we are the last instance referencing a
certain object, must be made atomically - i.e., in terms of std::atomic, we
can have something like:
if(pcontrol_block_->use_counter_.fetch_sub(1) == 1){
//do the resurrection:
pcontrol_block_->use_counter_ = 1;
return true;
}
Valentin
Received on 2023-12-11 15:46:12