You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rationale: Writing an optimal CAS loop is time-consuming and risks errors (e.g.
mis-setting Orderings). On the other hand, there are two general operations: fetch_and_update which is useful for things like random number generators,
and update_and_fetch which is the usual operation when you want to e.g. keep
a maximum stat over a series of concurrently produced values.
Adding those two operations as methods has little cost. Conversely, requiring
an extra crate means that the methods would have to either be implemented as
functions instead of methods, or that an extension trait would have to be
imported to use them. Both of those options are suboptimal.
Prior art: Java has an getAndUpdate / updateAndGet method on its AtomicInteger class since version 1.8. Due to the fact that Java lambdas are
more restricted w.r.t. mutability of their arguments than Rust's, there's also accumulateAndGet / getAndAccumulate.
Reacted by Kornel, Tyler Neely, Andreas Fuchs, Harry Barber and Stanislav Tkach
The ordering argument names are also not entirely correct -- set_order is also used for the the successful load. The two modes should probably be called success and failure, with failure also used for the load.
EDIT: Oh, the argument order is the wrong way around compared to compare_exchange. Dang :/
Is it worth mentioning that for the case where f always returns Some and never panics, there really is no reason not to use Relaxed as load ordering? A final successful CAS is going to happen, which is sufficient for synchronization. No need to use anything stronger for all the loads and failed CASes.
Is Result the correct return type? Unlike the compare_exchange functions here an Err result doesn't indicate that the function failed to do something, it indicates that f returned None.
My concern is that Result is marked as #[must_use] and here a common use case will be that you are not interested in the result value. So you will write code like let _ = x.fetch_update(...);.
Maybe a custom enum with Updated and NotUpdated variants can be used. But then you will have to import an additional type if you are interested in the result and want to pattern match it.
Stumbled across this method today, and figured I'd prod this issue & drop in my bikeshed 2¢.
I pulled the Weak::upgrade method as an example from the stdlib, and translated it roughly to the new API, formatted with rustfmt, as an example:
// We use a CAS loop to increment the strong count instead of a// fetch_add because once the count hits 0 it must never be above 0.let inner = self.inner()?;// Relaxed load because any write of 0 that we can observe leaves the// field in a permanently zero state (so a "stale" read of 0 is fine),// and any other value is confirmed via the CAS.
inner
.strong.fetch_update(
|n| {if n == 0{returnNone;}// See comments in `Arc::clone` for why we do this (for `mem::forget`).if n > MAX_REFCOUNT{unsafe{abort();}}Some(n + 1)},Relaxed,Relaxed,).ok().map(|_| Arc{// null checked aboveptr:self.ptr,phantom:PhantomData,})
A few things I've noticed:
This particular usecase has a fairly textually long update parameter, which forces the method into the multi-line format used by rustfmt. If we moved the ordering parameters before the lambda parameter, it would end up looking a bit cleaner:
inner
.strong.fetch_update(Relaxed,Relaxed, |n| {if n == 0{returnNone;}// See comments in `Arc::clone` for why we do this (for `mem::forget`).if n > MAX_REFCOUNT{unsafe{abort();}}Some(n + 1)})
The ordering parameters are in the opposite order to compare_exchange (although this doesn't come up in this particular case). It might be more straightforward for code to move over if the order was consistent.
The return value being a Result doesn't bother me, as I don't have any use cases currently which wouldn't want to match on it in some way. If we'd like to make it non-essential to match on, a tuple return type could potentially be used, like (usize, Option<usize>), where the first is the previous value, and the second is the new value returned by the last call to the passed-in lambda.
I also noticed that this method doesn't appear to exist for AtomicBool or AtomicPtr. It may be nice to add this method to those types too before stabilization.
In summary, I think I'd move the lambda parameter after the two ordering parameters, and make them match the parameter order used by the compare_exchange method.
Other than that, this seems like a handy API which makes writing CAS loops much cleaner, and I'd be happy to see it stabilized.
Activity
7 remaining items
nagisa commentedon Jul 21, 2018
Documentation for the functions should specify which orderings are valid or not (e.g.
fetch_orderforfetch_updatedoes not permitAcqRelorRelease).RalfJung commentedon Aug 7, 2018
The ordering argument names are also not entirely correct --
set_orderis also used for the the successful load. The two modes should probably be calledsuccessandfailure, withfailurealso used for the load.EDIT: Oh, the argument order is the wrong way around compared to
compare_exchange. Dang :/EDIT2: Also see #53106.
RalfJung commentedon Aug 7, 2018
Is it worth mentioning that for the case where
falways returnsSomeand never panics, there really is no reason not to useRelaxedas load ordering? A final successful CAS is going to happen, which is sufficient for synchronization. No need to use anything stronger for all the loads and failed CASes.nikolads commentedon Sep 3, 2018
Is
Resultthe correct return type? Unlike thecompare_exchangefunctions here anErrresult doesn't indicate that the function failed to do something, it indicates thatfreturnedNone.My concern is that
Resultis marked as#[must_use]and here a common use case will be that you are not interested in the result value. So you will write code likelet _ = x.fetch_update(...);.Maybe a custom enum with
UpdatedandNotUpdatedvariants can be used. But then you will have to import an additional type if you are interested in the result and want to pattern match it.mystor commentedon May 26, 2019
Stumbled across this method today, and figured I'd prod this issue & drop in my bikeshed 2¢.
I pulled the
Weak::upgrademethod as an example from the stdlib, and translated it roughly to the new API, formatted with rustfmt, as an example:A few things I've noticed:
inner .strong .fetch_update(Relaxed, Relaxed, |n| { if n == 0 { return None; } // See comments in `Arc::clone` for why we do this (for `mem::forget`). if n > MAX_REFCOUNT { unsafe { abort(); } } Some(n + 1) })The ordering parameters are in the opposite order to
compare_exchange(although this doesn't come up in this particular case). It might be more straightforward for code to move over if the order was consistent.The return value being a
Resultdoesn't bother me, as I don't have any use cases currently which wouldn't want to match on it in some way. If we'd like to make it non-essential to match on, a tuple return type could potentially be used, like(usize, Option<usize>), where the first is the previous value, and the second is the new value returned by the last call to the passed-in lambda.I also noticed that this method doesn't appear to exist for
AtomicBoolorAtomicPtr. It may be nice to add this method to those types too before stabilization.In summary, I think I'd move the lambda parameter after the two ordering parameters, and make them match the parameter order used by the
compare_exchangemethod.Other than that, this seems like a handy API which makes writing CAS loops much cleaner, and I'd be happy to see it stabilized.
sfackler commentedon May 3, 2020
I've opened a PR to stabilize fetch_update with @mystor's suggested tweaks: #71843.
Rollup merge of rust-lang#71843 - sfackler:cas-loop-cleanup, r=dtolnay
Rollup merge of rust-lang#71843 - sfackler:cas-loop-cleanup, r=dtolnay
jplatte commentedon Jun 19, 2020
fetch_updatehas been stabilized in #71843,fetch_minandfetch_maxin #72324. This tracking issue can be closed, right?Remove redundant `nightly` feature for fetch_min, fetch_max and fetch…
Remove redundant `nightly` feature for fetch_min, fetch_max and fetch…
Remove redundant `nightly` feature for fetch_min, fetch_max and fetch…