1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! Call site cache for invokedynamic instruction.
//!
//! This module provides a thread-safe cache that tracks the resolution state of invokedynamic call
//! sites. When multiple threads attempt to resolve the same call site concurrently, only the first
//! thread performs the resolution while others wait for the result.
use crate::Result;
use dashmap::DashMap;
use ristretto_classloader::Value;
use std::sync::Arc;
use tokio::sync::Notify;
/// Unique identifier for an invokedynamic call site
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CallSiteKey {
/// Class name containing the invokedynamic instruction
pub class_name: String,
/// Constant pool index of the invokedynamic instruction
pub instruction_index: u16,
}
impl CallSiteKey {
/// Create a new call site key
pub fn new(class_name: String, instruction_index: u16) -> Self {
Self {
class_name,
instruction_index,
}
}
}
/// State of an invokedynamic call site resolution
#[derive(Debug, Clone)]
pub enum CallSiteState {
/// Call site resolution is currently in progress; waiters are notified when done
InProgress(Arc<Notify>),
/// Call site has been successfully resolved
Resolved(Value),
}
/// Thread-safe cache for invokedynamic call site resolution
#[derive(Debug)]
pub struct CallSiteCache {
/// Maps call site keys to their resolution states
states: DashMap<CallSiteKey, CallSiteState>,
}
impl CallSiteCache {
/// Create a new empty call site cache
pub fn new() -> Self {
Self {
states: DashMap::new(),
}
}
/// Resolves a call site given a key and a resolver function.
///
/// This method checks if the call site is already being resolved or has been resolved
/// (returning cached result). If another thread is currently resolving this call site,
/// this method waits for that resolution to complete and returns the cached result.
///
/// # Arguments
///
/// * `key` - Unique identifier for the call site
/// * `resolver` - Function that performs the actual call site resolution
///
/// # Returns
///
/// The resolved call site value
///
/// # Errors
///
/// Returns an error if the resolver function fails
pub async fn resolve_with_cache<F, Fut>(&self, key: CallSiteKey, resolver: F) -> Result<Value>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<Value>>,
{
use tracing::debug;
debug!("CallSiteCache: Checking cache for key: {key:?}");
// Check current state
loop {
let wait_notify = {
if let Some(ref state) = self.states.get(&key) {
match &**state {
CallSiteState::InProgress(notify) => {
// Another thread is resolving this call site; wait for it
debug!("CallSiteCache: Another thread resolving, waiting: {key:?}");
Some(notify.clone())
}
CallSiteState::Resolved(value) => {
debug!("CallSiteCache: Returning cached result for key: {key:?}");
return Ok(value.clone());
}
}
} else {
debug!("CallSiteCache: Key not found in cache, will resolve: {key:?}");
None
}
}; // DashMap guard is dropped here
if let Some(notify) = wait_notify {
notify.notified().await;
} else {
break;
}
}
// Mark as in progress with a Notify so other threads can wait
let notify = Arc::new(Notify::new());
debug!("CallSiteCache: Marking as InProgress: {key:?}");
self.states
.insert(key.clone(), CallSiteState::InProgress(notify.clone()));
// Perform resolution
debug!("CallSiteCache: Starting resolution for key: {key:?}");
let result = resolver().await;
debug!(
"CallSiteCache: Resolution completed for key: {key:?}, success: {}",
result.is_ok()
);
// Update cache based on result
if let Ok(value) = &result {
// Store successful resolution
debug!("CallSiteCache: Caching successful result for key: {key:?}",);
self.states
.insert(key, CallSiteState::Resolved(value.clone()));
} else {
// Remove in-progress marker on failure to allow retry
debug!("CallSiteCache: Removing failed resolution from cache for key: {key:?}");
self.states.remove_if(&key, |_, state| {
matches!(state, CallSiteState::InProgress(_))
});
}
// Notify all waiters that resolution is complete (or failed)
notify.notify_waiters();
result
}
/// Clear all cached call sites
pub fn clear(&self) {
self.states.clear();
}
/// Get the number of cached call sites
pub fn len(&self) -> usize {
self.states.len()
}
/// Check if the cache is empty
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
}
impl Default for CallSiteCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[tokio::test]
async fn test_call_site_cache_basic_resolution() -> Result<()> {
let cache = CallSiteCache::new();
let key = CallSiteKey::new("TestClass".to_string(), 42);
let expected_value = Value::Object(None);
let result = cache
.resolve_with_cache(key.clone(), || async { Ok(expected_value.clone()) })
.await?;
assert_eq!(result, expected_value);
Ok(())
}
#[tokio::test]
async fn test_call_site_cache_concurrent_resolution() -> Result<()> {
let cache = Arc::new(CallSiteCache::new());
let key = CallSiteKey::new("TestClass".to_string(), 42);
let expected_value = Value::Object(None);
// Spawn two tasks that try to resolve the same key concurrently
let cache1 = cache.clone();
let key1 = key.clone();
let expected1 = expected_value.clone();
let handle1 = tokio::spawn(async move {
cache1
.resolve_with_cache(key1, || async { Ok(expected1) })
.await
});
let cache2 = cache.clone();
let key2 = key.clone();
let expected2 = expected_value.clone();
let handle2 = tokio::spawn(async move {
cache2
.resolve_with_cache(key2, || async { Ok(expected2) })
.await
});
let result1 = handle1.await.unwrap()?;
let result2 = handle2.await.unwrap()?;
assert_eq!(result1, expected_value);
assert_eq!(result2, expected_value);
Ok(())
}
#[tokio::test]
async fn test_call_site_cache_returns_cached_result() -> Result<()> {
let cache = CallSiteCache::new();
let key = CallSiteKey::new("TestClass".to_string(), 42);
let expected_value = Value::Object(None);
// First resolution
let result1 = cache
.resolve_with_cache(key.clone(), || async { Ok(expected_value.clone()) })
.await?;
// Second resolution should return cached result
let result2 = cache
.resolve_with_cache(key, || async {
panic!("Resolver should not be called for cached result")
})
.await?;
assert_eq!(result1, result2);
Ok(())
}
}