url_cleaner_engine/types/
unthreader.rs

1//! Allows making requests, cache reads, etc. effectively single threaded to hide thread count.
2
3use parking_lot::{ReentrantMutex, ReentrantMutexGuard};
4
5use serde::{Serialize, Deserialize, ser::Serializer, de::{Visitor, Deserializer, Error}};
6
7/// Allows making requests, cache reads, etc. effectively single threaded to hide thread count.
8///
9/// In URL Cleaner Site Userscript, it's possible for a website to give you 1 redirect URL, then 2 of that same URL, then 3, then 4, and so on and so on until the time your instance takes to clean them suddenly doubles.
10///
11/// Unthreading means that long running operations can be forced to run sequentially, making N redirect URLs always take N times as long as 1, while keeping the benefits of parallelizing everything else.
12///
13/// It's not a perfect defence, websites can probably give you extremely expensive but non-redirect URLs and use the previously mentioned scheme to figure out your thread count, but that is very unlikely to give useful results in the vast majority of situations.
14#[derive(Debug, Default)]
15pub enum Unthreader {
16    /// Don't do any unthreading.
17    ///
18    /// The default variant.
19    #[default]
20    No,
21    /// Do unthreading.
22    Yes(ReentrantMutex<()>)
23}
24
25impl Unthreader {
26    /// [`Self::No`].
27    pub fn no() -> Self {
28        Self::No
29    }
30
31    /// [`Self::yes`].
32    pub fn yes() -> Self {
33        Self::Yes(Default::default())
34    }
35
36    /// If `x` is [`true`], [`Self::Yes`], otherwise [`Self::No`].
37    pub fn r#if(x: bool) -> Self {
38        match x {
39            false => Self::no(),
40            true  => Self::yes()
41        }
42    }
43
44    /// If `self` is [`Self::Yes`], return a [`ReentrantMutexGuard`].
45    ///
46    /// Assign this to variable and drop it when you want to rethread.
47    #[must_use]
48    pub fn unthread(&self) -> Option<ReentrantMutexGuard<'_, ()>> {
49        match self {
50            Self::No => None,
51            Self::Yes(x) => Some(x.lock())
52        }
53    }
54}
55
56/// Serde helper for deserializing [`Unthreader`].
57struct UnthreaderVisitor;
58
59impl<'de> Visitor<'de> for UnthreaderVisitor {
60    type Value = Unthreader;
61
62    fn visit_bool<E: Error>(self, v: bool) -> Result<Self::Value, E> {
63        Ok(Unthreader::r#if(v))
64    }
65
66    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        write!(formatter, "Expected a bool")
68    }
69}
70
71impl<'de> Deserialize<'de> for Unthreader {
72    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
73        deserializer.deserialize_any(UnthreaderVisitor)
74    }
75}
76
77impl Serialize for Unthreader {
78    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
79        serializer.serialize_bool(match self {
80            Self::No => false,
81            Self::Yes(_) => true
82        })
83    }
84}