greentic_session/
inmemory.rs1use crate::model::{Cas, Session, SessionKey};
2use crate::store::SessionStore;
3use dashmap::DashMap;
4use greentic_types::GResult;
5use parking_lot::Mutex;
6use time::{Duration, OffsetDateTime};
7
8struct Entry {
9 session: Session,
10 cas: Cas,
11 expires_at: Option<OffsetDateTime>,
12}
13
14impl Entry {
15 fn new(session: Session, cas: Cas) -> Self {
16 let expires_at = session.expires_at();
17 Self {
18 session,
19 cas,
20 expires_at,
21 }
22 }
23
24 fn is_expired(&self, now: OffsetDateTime) -> bool {
25 match self.expires_at {
26 Some(exp) => now >= exp,
27 None => false,
28 }
29 }
30}
31
32pub struct InMemorySessionStore {
34 entries: DashMap<SessionKey, Entry>,
35 cleanup_hint: Mutex<OffsetDateTime>,
36}
37
38impl Default for InMemorySessionStore {
39 fn default() -> Self {
40 Self {
41 entries: DashMap::new(),
42 cleanup_hint: Mutex::new(OffsetDateTime::now_utc()),
43 }
44 }
45}
46
47impl InMemorySessionStore {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 fn now() -> OffsetDateTime {
54 OffsetDateTime::now_utc()
55 }
56
57 fn sanitize_for_write(session: &mut Session, now: OffsetDateTime) {
58 session.updated_at = now;
59 session.normalize();
60 }
61
62 fn maybe_cleanup(&self, now: OffsetDateTime) {
63 let mut guard = self.cleanup_hint.lock();
64 if now - *guard < Duration::seconds(60) {
65 return;
66 }
67
68 let stale_keys: Vec<_> = self
69 .entries
70 .iter()
71 .filter_map(|entry| {
72 if entry.value().is_expired(now) {
73 Some(entry.key().clone())
74 } else {
75 None
76 }
77 })
78 .collect();
79
80 for key in stale_keys {
81 self.entries.remove(&key);
82 }
83
84 *guard = now;
85 }
86}
87
88impl SessionStore for InMemorySessionStore {
89 fn get(&self, key: &SessionKey) -> GResult<Option<(Session, Cas)>> {
90 let now = Self::now();
91 self.maybe_cleanup(now);
92 if let Some(entry) = self.entries.get(key) {
93 if entry.is_expired(now) {
94 drop(entry);
95 self.entries.remove(key);
96 return Ok(None);
97 }
98 return Ok(Some((entry.session.clone(), entry.cas)));
99 }
100 Ok(None)
101 }
102
103 fn put(&self, mut session: Session) -> GResult<Cas> {
104 let key = session.key.clone();
105 let now = Self::now();
106 self.maybe_cleanup(now);
107 if let Some(existing) = self.entries.get(&key) {
108 if existing.is_expired(now) {
109 drop(existing);
110 self.entries.remove(&key);
111 }
112 }
113
114 Self::sanitize_for_write(&mut session, now);
115
116 let mut cas = Cas::initial();
117 match self.entries.entry(key) {
118 dashmap::mapref::entry::Entry::Occupied(mut occ) => {
119 cas = occ.get().cas.next();
120 occ.insert(Entry::new(session, cas));
121 }
122 dashmap::mapref::entry::Entry::Vacant(vac) => {
123 vac.insert(Entry::new(session, cas));
124 }
125 }
126 Ok(cas)
127 }
128
129 fn update_cas(&self, mut session: Session, expected: Cas) -> GResult<Result<Cas, Cas>> {
130 let key = session.key.clone();
131 let now = Self::now();
132 self.maybe_cleanup(now);
133 Self::sanitize_for_write(&mut session, now);
134
135 if let Some(mut guard) = self.entries.get_mut(&key) {
136 if guard.is_expired(now) {
137 drop(guard);
138 self.entries.remove(&key);
139 return Ok(Err(Cas::none()));
140 }
141
142 let current = guard.cas;
143 if current != expected {
144 return Ok(Err(current));
145 }
146
147 let next = current.next();
148 guard.cas = next;
149 guard.session = session;
150 guard.expires_at = guard.session.expires_at();
151 return Ok(Ok(next));
152 }
153 Ok(Err(Cas::none()))
154 }
155
156 fn delete(&self, key: &SessionKey) -> GResult<bool> {
157 Ok(self.entries.remove(key).is_some())
158 }
159
160 fn touch(&self, key: &SessionKey, ttl_secs: Option<u32>) -> GResult<bool> {
161 let now = Self::now();
162 self.maybe_cleanup(now);
163 if let Some(mut guard) = self.entries.get_mut(key) {
164 if guard.is_expired(now) {
165 drop(guard);
166 self.entries.remove(key);
167 return Ok(false);
168 }
169
170 if let Some(ttl) = ttl_secs {
171 guard.session.ttl_secs = ttl;
172 }
173 guard.session.updated_at = now;
174 guard.session.normalize();
175 guard.expires_at = guard.session.expires_at();
176 return Ok(true);
177 }
178 Ok(false)
179 }
180}