diff --git a/crossbeam-skiplist/src/base.rs b/crossbeam-skiplist/src/base.rs
index 92561b4be..b29909dc0 100644
--- a/crossbeam-skiplist/src/base.rs
+++ b/crossbeam-skiplist/src/base.rs
@@ -627,6 +627,10 @@ where
}
/// Finds an entry with the specified key, or inserts a new `key`-`value` pair if none exist.
+ ///
+ /// Note: Another thread may insert the same key first. In that case
+ /// this call returns the entry that won the race and `value` is dropped
+ /// without being inserted.
pub fn get_or_insert(&self, key: K, value: V, guard: &Guard) -> RefEntry<'_, K, V, C> {
self.insert_internal(key, || value, |_| false, guard)
}
@@ -1009,7 +1013,14 @@ where
/// Inserts an entry with the specified `key` and `value`.
///
- /// If `replace` is `true`, then any existing entry with this key will first be removed.
+ /// `replace` is consulted whenever an entry with the same key is found. If
+ /// it returns `true`, that entry is replaced by linking a new node and
+ /// unlinking the old one after the new node is published; if it returns
+ /// `false`, the existing entry is returned and `value` is not inserted.
+ ///
+ /// On contention the search is retried, so `replace` may run more than once
+ /// for a single call. A successful publish never leaves the key absent
+ /// between the old and new entries.
fn insert_internal(
&self,
key: K,
@@ -1248,11 +1259,44 @@ where
self.insert_internal(key, || value, |_| true, guard)
}
- /// Inserts a `key`-`value` pair into the skip list and returns the new entry.
+ /// Inserts a `key`-`value` pair if the key is absent, or replaces the
+ /// existing entry when `compare_fn` approves the replacement.
+ ///
+ /// The `compare_fn` closure is given a reference to the current value for
+ /// `key` and should return `true` if that entry may be replaced by `value`.
+ /// It is **not** called when `key` is absent; in that case the pair is
+ /// always inserted.
+ ///
+ /// Returns a [`RefEntry`] pointing at the resulting mapping for `key`:
+ /// - the newly inserted entry, if the key was absent or was replaced; or
+ /// - the existing entry, if `compare_fn` returned `false` and the list was
+ /// left unchanged.
+ ///
+ /// # Concurrency
+ ///
+ /// A successful replacement is installed as one lock-free update. Other
+ /// threads looking up `key` do not observe a gap in which the key is
+ /// missing between the old entry and the new one.
+ ///
+ /// This is **not** an in-place compare-and-swap of the stored value.
+ /// Replacement links a new node and unlinks the old one rather than
+ /// mutating the previous value through a shared reference.
+ ///
+ /// Concurrent callers may still race with each other and with other
+ /// mutating operations on the same key:
+ /// - `compare_fn` may run more than once if the list changes between the
+ /// comparison and the attempt to publish the update; each call sees a
+ /// value that was current at the time of that attempt.
+ /// - When several threads call `compare_insert` on the same key, their
+ /// updates are ordered by the lock-free insertion. A thread whose
+ /// compare no longer applies after a lost race either retries against
+ /// the newer value or returns the entry that remained.
+ /// - `value` is moved into the list only if this call's insert or replace
+ /// succeeds; otherwise it is dropped.
///
- /// If there is an existing entry with this key and compare(entry.value) returns true,
- /// it will be removed before inserting the new one.
- /// The closure will not be called if the key is not present.
+ /// Because of these races, `compare_insert` is not a substitute for a
+ /// per-entry atomic primitive (for example, it cannot implement a
+ /// contention-free atomic counter by itself).
pub fn compare_insert(
&self,
key: K,
diff --git a/crossbeam-skiplist/src/map.rs b/crossbeam-skiplist/src/map.rs
index 10c22b36a..d04cc153c 100644
--- a/crossbeam-skiplist/src/map.rs
+++ b/crossbeam-skiplist/src/map.rs
@@ -151,6 +151,10 @@ where
/// This function returns an [`Entry`] which
/// can be used to access the key's associated value.
///
+ /// Note: Another thread may insert the same key first. In that case
+ /// this call returns the entry that won the race and `value` is dropped
+ /// without being inserted.
+ ///
/// # Example
/// ```
/// use crossbeam_skiplist::SkipMap;
@@ -405,14 +409,49 @@ where
Entry::new(self.inner.insert(key, value, guard))
}
- /// Inserts a `key`-`value` pair into the skip list and returns the new entry.
+ /// Inserts a `key`-`value` pair if the key is absent, or replaces the
+ /// existing entry when `compare_fn` approves the replacement.
///
- /// If there is an existing entry with this key and compare(entry.value) returns true,
- /// it will be removed before inserting the new one.
- /// The closure will not be called if the key is not present.
+ /// The `compare_fn` closure is given a reference to the current value for
+ /// `key` and should return `true` if that entry may be replaced by `value`.
+ /// It is **not** called when `key` is absent; in that case the pair is
+ /// always inserted.
///
- /// This function returns an [`Entry`] which
- /// can be used to access the inserted key's associated value.
+ /// Returns an [`Entry`] pointing at the resulting mapping for `key`:
+ /// - the newly inserted entry, if the key was absent or was replaced; or
+ /// - the existing entry, if `compare_fn` returned `false` and the map was
+ /// left unchanged.
+ ///
+ /// There is no separate status flag: inspect the returned entry's value
+ /// (or compare keys/values you already hold) to tell whether a replacement
+ /// took place.
+ ///
+ /// # Concurrency
+ ///
+ /// A successful replacement is installed as one lock-free update. Other
+ /// threads looking up `key` do not observe a gap in which the key is
+ /// missing between the old entry and the new one.
+ ///
+ /// This is **not** an in-place compare-and-swap of the stored value. The
+ /// map links a new node (and unlinks the old one on replacement) rather
+ /// than mutating the previous value through a shared reference.
+ ///
+ /// Concurrent callers may still race with each other and with other
+ /// mutating operations on the same key:
+ /// - `compare_fn` may run more than once if the map changes between the
+ /// comparison and the attempt to publish the update; each call sees a
+ /// value that was current at the time of that attempt.
+ /// - When several threads call `compare_insert` on the same key, their
+ /// updates are ordered by the lock-free insertion. A thread whose
+ /// compare no longer applies after a lost race either retries against
+ /// the newer value or returns the entry that remained.
+ /// - `value` is moved into the map only if this call's insert or replace
+ /// succeeds; otherwise it is dropped.
+ ///
+ /// Because of these races, `compare_insert` is not a substitute for a
+ /// per-entry atomic primitive (for example, it cannot implement a
+ /// contention-free atomic counter by itself). Compose it carefully when
+ /// the closure or the surrounding code relies on side effects.
///
/// # Example
/// ```
@@ -420,11 +459,17 @@ where
///
/// let map = SkipMap::new();
/// map.insert("key", 1);
- /// map.compare_insert("key", 0, |x| x < &0);
+ /// // Replacement rejected: existing value is not less than 0.
+ /// let entry = map.compare_insert("key", 0, |x| x < &0);
+ /// assert_eq!(*entry.value(), 1);
/// assert_eq!(*map.get("key").unwrap().value(), 1);
- /// map.compare_insert("key", 2, |x| x < &2);
+ /// // Replacement accepted: existing value is less than 2.
+ /// let entry = map.compare_insert("key", 2, |x| x < &2);
+ /// assert_eq!(*entry.value(), 2);
/// assert_eq!(*map.get("key").unwrap().value(), 2);
- /// map.compare_insert("absent_key", 0, |_| false);
+ /// // Absent key: `compare_fn` is not called; the value is inserted.
+ /// let entry = map.compare_insert("absent_key", 0, |_| false);
+ /// assert_eq!(*entry.value(), 0);
/// assert_eq!(*map.get("absent_key").unwrap().value(), 0);
/// ```
pub fn compare_insert(&self, key: K, value: V, compare_fn: F) -> Entry<'_, K, V, C>