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}