Skip to main content

pijul_core/
pin.rs

1use crate::pristine::*;
2
3pub enum ReorderError<T: ChannelMutTxnT> {
4    Txn(TxnErr<T::GraphError>),
5    /// A tag exists at a position that would need to be moved.
6    TaggedPosition(ApplyTimestamp),
7}
8
9impl<T: ChannelMutTxnT> From<TxnErr<T::GraphError>> for ReorderError<T> {
10    fn from(e: TxnErr<T::GraphError>) -> Self {
11        ReorderError::Txn(e)
12    }
13}
14
15impl<T: ChannelMutTxnT> std::fmt::Debug for ReorderError<T> {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        match self {
18            ReorderError::Txn(e) => write!(f, "ReorderError::Txn({e:?})"),
19            ReorderError::TaggedPosition(t) => write!(f, "ReorderError::TaggedPosition({t})"),
20        }
21    }
22}
23
24impl<T: ChannelMutTxnT> std::fmt::Display for ReorderError<T> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            ReorderError::Txn(e) => write!(f, "Transaction error: {e}"),
28            ReorderError::TaggedPosition(t) => {
29                write!(f, "Cannot reorder: position {t} is tagged")
30            }
31        }
32    }
33}
34
35impl<T: ChannelMutTxnT + 'static> std::error::Error for ReorderError<T> {}
36
37/// After applying a new patch, ensure pinned patches stay at the top of the
38/// channel order.
39///
40/// If `new_change` does not depend on any pinned patch, it is moved to just
41/// before the first explicitly pinned patch, and the pinned patches shift up
42/// by one. If it does depend on a pinned patch it stays where it is (it is
43/// implicitly pinned).
44///
45/// Returns `Err(ReorderError::TaggedPosition)` if any position in the range
46/// that would be shuffled carries a tag.
47pub fn reorder_pinned<T>(
48    txn: &mut T,
49    channel: &mut T::Channel,
50    new_change: ChangeId,
51    is_pinned: impl Fn(Hash) -> bool,
52) -> Result<(), ReorderError<T>>
53where
54    T: ChannelMutTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError> + GraphTxnT,
55{
56    let first_pinned = find_first_pinned(txn, channel, &is_pinned)?;
57    let first_pinned = match first_pinned {
58        Some(p) => p,
59        None => return Ok(()),
60    };
61
62    let new_pos = match txn.get_changeset(txn.changes(channel), &new_change)? {
63        Some(t) => u64::from(*t),
64        None => return Ok(()),
65    };
66
67    if new_pos < first_pinned {
68        return Ok(());
69    }
70
71    // If new_change depends on anything at or above the pinned watermark, it
72    // is implicitly pinned — leave it in place.
73    for dep in txn.iter_dep(&new_change)? {
74        let (dep_id, _) = dep?;
75        if let Some(dep_pos) = txn.get_changeset(txn.changes(channel), dep_id)? {
76            if u64::from(*dep_pos) >= first_pinned {
77                return Ok(());
78            }
79        }
80    }
81
82    // Refuse to shuffle over a tagged position.
83    for pos in first_pinned..=new_pos {
84        if txn.is_tagged(txn.tags(channel), pos)? {
85            return Err(ReorderError::TaggedPosition(pos));
86        }
87    }
88
89    txn.move_change(channel, new_change, new_pos, first_pinned)?;
90    Ok(())
91}
92
93fn find_first_pinned<T>(
94    txn: &T,
95    channel: &T::Channel,
96    is_pinned: &impl Fn(Hash) -> bool,
97) -> Result<Option<ApplyTimestamp>, TxnErr<T::GraphError>>
98where
99    T: ChannelTxnT + GraphTxnT,
100{
101    let cursor = T::cursor_revchangeset_ref(txn, txn.rev_changes(channel), None)?;
102    for x in cursor {
103        let (t, pair) = x?;
104        if let Some(h) = txn.get_external(&pair.a).optional()? {
105            if is_pinned(h.into()) {
106                return Ok(Some(u64::from(*t)));
107            }
108        }
109    }
110    Ok(None)
111}
112
113/// Returns the position of the first explicitly pinned patch in the channel
114/// order, or `None` if no pins are configured on this channel. All positions
115/// at or above this watermark are implicitly pinned and must not be pushed or
116/// tagged.
117pub fn pinned_watermark<T>(
118    txn: &T,
119    channel: &T::Channel,
120    is_pinned: impl Fn(Hash) -> bool,
121) -> Result<Option<ApplyTimestamp>, TxnErr<T::GraphError>>
122where
123    T: ChannelTxnT + GraphTxnT,
124{
125    find_first_pinned(txn, channel, &is_pinned)
126}
127
128/// Returns true if the channel state at position `pos` includes any explicitly
129/// pinned patch (i.e., `pos` is at or after the pinned watermark). Used to
130/// prevent tagging a state that contains pinned patches.
131pub fn state_includes_pinned<T>(
132    txn: &T,
133    channel: &T::Channel,
134    pos: ApplyTimestamp,
135    is_pinned: impl Fn(Hash) -> bool,
136) -> Result<bool, TxnErr<T::GraphError>>
137where
138    T: ChannelTxnT + GraphTxnT,
139{
140    match find_first_pinned(txn, channel, &is_pinned)? {
141        Some(fp) => Ok(pos >= fp),
142        None => Ok(false),
143    }
144}