Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions crossbeam-skiplist/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,10 @@ where
}

/// Finds an entry with the specified key, or inserts a new `key`-`value` pair if none exist.
///
/// <b>Note:</b> 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)
}
Expand Down Expand Up @@ -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<F, CompareF>(
&self,
key: K,
Expand Down Expand Up @@ -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<F>(
&self,
key: K,
Expand Down
63 changes: 54 additions & 9 deletions crossbeam-skiplist/src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ where
/// This function returns an [`Entry`] which
/// can be used to access the key's associated value.
///
/// <b>Note:</b> 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;
Expand Down Expand Up @@ -405,26 +409,67 @@ 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
/// ```
/// use crossbeam_skiplist::SkipMap;
///
/// 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<F>(&self, key: K, value: V, compare_fn: F) -> Entry<'_, K, V, C>
Expand Down