iroh_blobs/util/
temp_tag.rs

1#![allow(dead_code)]
2use std::{
3    collections::HashMap,
4    sync::{Arc, Mutex, Weak},
5};
6
7use serde::{Deserialize, Serialize};
8use tracing::{trace, warn};
9
10use crate::{api::proto::Scope, BlobFormat, Hash, HashAndFormat};
11
12/// An ephemeral, in-memory tag that protects content while the process is running.
13///
14/// If format is raw, this will protect just the blob
15/// If format is collection, this will protect the collection and all blobs in it
16#[derive(Debug, Serialize, Deserialize)]
17#[must_use = "TempTag is a temporary tag that should be used to protect content while the process is running. \
18       If you want to keep the content alive, use TempTag::leak()"]
19pub struct TempTag {
20    /// The hash and format we are pinning
21    inner: HashAndFormat,
22    /// optional callback to call on drop
23    #[serde(skip)]
24    on_drop: Option<Weak<dyn TagDrop>>,
25}
26
27impl AsRef<Hash> for TempTag {
28    fn as_ref(&self) -> &Hash {
29        &self.inner.hash
30    }
31}
32
33/// A trait for things that can track liveness of blobs and collections.
34///
35/// This trait works together with [TempTag] to keep track of the liveness of a
36/// blob or collection.
37///
38/// It is important to include the format in the liveness tracking, since
39/// protecting a collection means protecting the blob and all its children,
40/// whereas protecting a raw blob only protects the blob itself.
41pub trait TagCounter: TagDrop + Sized {
42    /// Called on creation of a temp tag
43    fn on_create(&self, inner: &HashAndFormat);
44
45    /// Get this as a weak reference for use in temp tags
46    fn as_weak(self: &Arc<Self>) -> Weak<dyn TagDrop> {
47        let on_drop: Arc<dyn TagDrop> = self.clone();
48        Arc::downgrade(&on_drop)
49    }
50
51    /// Create a new temp tag for the given hash and format
52    fn temp_tag(self: &Arc<Self>, inner: HashAndFormat) -> TempTag {
53        self.on_create(&inner);
54        TempTag::new(inner, Some(self.as_weak()))
55    }
56}
57
58/// Trait used from temp tags to notify an abstract store that a temp tag is
59/// being dropped.
60pub trait TagDrop: std::fmt::Debug + Send + Sync + 'static {
61    /// Called on drop
62    fn on_drop(&self, inner: &HashAndFormat);
63}
64
65impl From<&TempTag> for HashAndFormat {
66    fn from(val: &TempTag) -> Self {
67        val.inner
68    }
69}
70
71impl From<TempTag> for HashAndFormat {
72    fn from(val: TempTag) -> Self {
73        val.inner
74    }
75}
76
77impl TempTag {
78    /// Create a new temp tag for the given hash and format
79    ///
80    /// This should only be used by store implementations.
81    ///
82    /// The caller is responsible for increasing the refcount on creation and to
83    /// make sure that temp tags that are created between a mark phase and a sweep
84    /// phase are protected.
85    pub fn new(inner: HashAndFormat, on_drop: Option<Weak<dyn TagDrop>>) -> Self {
86        Self { inner, on_drop }
87    }
88
89    /// The empty temp tag. We don't track the empty blob since we always have it.
90    pub fn leaking_empty(format: BlobFormat) -> Self {
91        Self {
92            inner: HashAndFormat {
93                hash: Hash::EMPTY,
94                format,
95            },
96            on_drop: None,
97        }
98    }
99
100    /// The hash of the pinned item
101    pub fn hash(&self) -> Hash {
102        self.inner.hash
103    }
104
105    /// The format of the pinned item
106    pub fn format(&self) -> BlobFormat {
107        self.inner.format
108    }
109
110    /// The hash and format of the pinned item
111    pub fn hash_and_format(&self) -> HashAndFormat {
112        self.inner
113    }
114
115    /// Keep the item alive until the end of the process
116    pub fn leak(&mut self) {
117        // set the liveness tracker to None, so that the refcount is not decreased
118        // during drop. This means that the refcount will never reach 0 and the
119        // item will not be gced until the end of the process.
120        self.on_drop = None;
121    }
122}
123
124impl Drop for TempTag {
125    fn drop(&mut self) {
126        if let Some(on_drop) = self.on_drop.take() {
127            if let Some(on_drop) = on_drop.upgrade() {
128                on_drop.on_drop(&self.inner);
129            }
130        }
131    }
132}
133
134#[derive(Debug, Default, Clone)]
135struct TempCounters {
136    /// number of raw temp tags for a hash
137    raw: u64,
138    /// number of hash seq temp tags for a hash
139    hash_seq: u64,
140}
141
142impl TempCounters {
143    fn counter(&self, format: BlobFormat) -> u64 {
144        match format {
145            BlobFormat::Raw => self.raw,
146            BlobFormat::HashSeq => self.hash_seq,
147        }
148    }
149
150    fn counter_mut(&mut self, format: BlobFormat) -> &mut u64 {
151        match format {
152            BlobFormat::Raw => &mut self.raw,
153            BlobFormat::HashSeq => &mut self.hash_seq,
154        }
155    }
156
157    fn inc(&mut self, format: BlobFormat) {
158        let counter = self.counter_mut(format);
159        *counter = counter.checked_add(1).unwrap();
160    }
161
162    fn dec(&mut self, format: BlobFormat) {
163        let counter = self.counter_mut(format);
164        *counter = counter.saturating_sub(1);
165    }
166
167    fn is_empty(&self) -> bool {
168        self.raw == 0 && self.hash_seq == 0
169    }
170}
171
172#[derive(Debug, Default)]
173pub(crate) struct TempTags {
174    scopes: HashMap<Scope, Arc<TempTagScope>>,
175    next_scope: u64,
176}
177
178impl TempTags {
179    pub fn create_scope(&mut self) -> (Scope, Arc<TempTagScope>) {
180        self.next_scope += 1;
181        let id = Scope(self.next_scope);
182        let scope = self.scopes.entry(id).or_default();
183        (id, scope.clone())
184    }
185
186    pub fn end_scope(&mut self, scope: Scope) {
187        self.scopes.remove(&scope);
188    }
189
190    pub fn list(&self) -> Vec<HashAndFormat> {
191        self.scopes
192            .values()
193            .flat_map(|scope| scope.list())
194            .collect()
195    }
196
197    pub fn create(&mut self, scope: Scope, content: HashAndFormat) -> TempTag {
198        let scope = self.scopes.entry(scope).or_default();
199
200        scope.temp_tag(content)
201    }
202
203    pub fn contains(&self, hash: Hash) -> bool {
204        self.scopes
205            .values()
206            .any(|scope| scope.0.lock().unwrap().contains(&HashAndFormat::raw(hash)))
207    }
208}
209
210#[derive(Debug, Default)]
211pub(crate) struct TempTagScope(Mutex<TempCounterMap>);
212
213impl TempTagScope {
214    pub fn list(&self) -> impl Iterator<Item = HashAndFormat> + 'static {
215        let guard = self.0.lock().unwrap();
216        let res = guard.keys();
217        drop(guard);
218        res.into_iter()
219    }
220}
221
222impl TagDrop for TempTagScope {
223    fn on_drop(&self, inner: &HashAndFormat) {
224        trace!("Dropping temp tag {:?}", inner);
225        self.0.lock().unwrap().dec(inner);
226    }
227}
228
229impl TagCounter for TempTagScope {
230    fn on_create(&self, inner: &HashAndFormat) {
231        trace!("Creating temp tag {:?}", inner);
232        self.0.lock().unwrap().inc(*inner);
233    }
234}
235
236#[derive(Debug, Clone, Default)]
237pub(crate) struct TempCounterMap(HashMap<Hash, TempCounters>);
238
239impl TempCounterMap {
240    pub fn inc(&mut self, value: HashAndFormat) {
241        self.0.entry(value.hash).or_default().inc(value.format)
242    }
243
244    pub fn dec(&mut self, value: &HashAndFormat) {
245        let HashAndFormat { hash, format } = value;
246        let Some(counters) = self.0.get_mut(hash) else {
247            warn!("Decrementing non-existent temp tag");
248            return;
249        };
250        counters.dec(*format);
251        if counters.is_empty() {
252            self.0.remove(hash);
253        }
254    }
255
256    pub fn contains(&self, haf: &HashAndFormat) -> bool {
257        let Some(entry) = self.0.get(&haf.hash) else {
258            return false;
259        };
260        entry.counter(haf.format) > 0
261    }
262
263    pub fn keys(&self) -> Vec<HashAndFormat> {
264        let mut res = Vec::new();
265        for (k, v) in self.0.iter() {
266            if v.raw > 0 {
267                res.push(HashAndFormat::raw(*k));
268            }
269            if v.hash_seq > 0 {
270                res.push(HashAndFormat::hash_seq(*k));
271            }
272        }
273        res
274    }
275}