Skip to content

Commit 03d98b2

Browse files
committed
Merge origin/dev into root-resolution.
Keep the generation-guarded resolution cache and public invalidate_space so PR 10 and the workspace-routing work coexist. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
2 parents 1ca8878 + f4cf81c commit 03d98b2

9 files changed

Lines changed: 205 additions & 53 deletions

File tree

apps/desktop/src-tauri/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,9 @@ pub fn run() {
900900
tauri::async_runtime::spawn(async move {
901901
#[cfg(unix)]
902902
{
903-
crate::unix_signal::wait_for_term().await;
903+
if !crate::unix_signal::wait_for_term().await {
904+
return;
905+
}
904906
}
905907
#[cfg(windows)]
906908
{

apps/desktop/src-tauri/src/unix_signal.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,22 @@ static PIPE_WRITE_FD: AtomicI32 = AtomicI32::new(-1);
1717

1818
/// Install the recorder and wait until SIGTERM or SIGINT arrives.
1919
///
20-
/// Returns after the first termination signal. Caller should then exit.
21-
pub async fn wait_for_term() {
20+
/// Returns `true` after the first termination signal. Returns `false` if the
21+
/// handler could not be installed — the caller must not treat that as exit.
22+
pub async fn wait_for_term() -> bool {
2223
let read_end = match install() {
2324
Ok(fd) => fd,
2425
Err(e) => {
2526
tracing::warn!("[Signal] Failed to install SA_SIGINFO handler: {e}");
26-
return;
27+
return false;
2728
}
2829
};
2930

3031
let mut reader = match tokio::net::UnixStream::from_std(read_end) {
3132
Ok(s) => s,
3233
Err(e) => {
3334
tracing::warn!("[Signal] Failed to wrap self-pipe: {e}");
34-
return;
35+
return false;
3536
}
3637
};
3738
let mut buf = [0u8; 1];
@@ -58,6 +59,7 @@ pub async fn wait_for_term() {
5859
si_code = code,
5960
"[Signal] {name} — requesting exit"
6061
);
62+
true
6163
}
6264

6365
fn install() -> std::io::Result<UnixStream> {

crates/mcpmux-gateway/src/oauth/flow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ impl OAuthFlow {
187187
fn generate_state() -> String {
188188
use rand::Rng;
189189
let mut rng = rand::thread_rng();
190-
let bytes: Vec<u8> = (0..16).map(|_| rng.gen()).collect();
190+
let bytes: Vec<u8> = (0..16).map(|_| rng.gen::<u8>()).collect();
191191
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
192192
URL_SAFE_NO_PAD.encode(&bytes)
193193
}

crates/mcpmux-gateway/src/oauth/pkce.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ impl PkceChallenge {
2222
pub fn generate() -> Self {
2323
// Generate 32 random bytes for the verifier
2424
let mut rng = rand::thread_rng();
25-
let random_bytes: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
25+
let random_bytes: Vec<u8> = (0..32).map(|_| rng.gen::<u8>()).collect();
2626

2727
// Base64-URL encode to create verifier (43-128 characters)
2828
let verifier = URL_SAFE_NO_PAD.encode(&random_bytes);

crates/mcpmux-gateway/src/pool/features/resolution.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,21 @@ fn apply_mode_to_set(
3737
/// the hit so tools/prompts/resources share one entry.
3838
type ResolutionCacheKey = (String, Vec<String>);
3939

40+
/// Resolved features plus an invalidation generation so a miss cannot
41+
/// republish a pre-event snapshot after the listener has already cleared.
42+
struct ResolutionCache {
43+
generation: u64,
44+
entries: HashMap<ResolutionCacheKey, Vec<ServerFeature>>,
45+
}
46+
4047
/// Handles feature set resolution and permission evaluation
4148
pub struct FeatureResolutionService {
4249
feature_repo: Arc<dyn ServerFeatureRepository>,
4350
feature_set_repo: Arc<dyn FeatureSetRepository>,
4451
prefix_cache: Arc<PrefixCacheService>,
4552
/// Resolved (allow/exclude + prefix) features, invalidated when
4653
/// [`DomainEvent::affects_mcp_capabilities`] is true.
47-
cache: Arc<RwLock<HashMap<ResolutionCacheKey, Vec<ServerFeature>>>>,
54+
cache: Arc<RwLock<ResolutionCache>>,
4855
}
4956

5057
impl FeatureResolutionService {
@@ -57,7 +64,10 @@ impl FeatureResolutionService {
5764
feature_repo,
5865
feature_set_repo,
5966
prefix_cache,
60-
cache: Arc::new(RwLock::new(HashMap::new())),
67+
cache: Arc::new(RwLock::new(ResolutionCache {
68+
generation: 0,
69+
entries: HashMap::new(),
70+
})),
6171
}
6272
}
6373

@@ -105,14 +115,17 @@ impl FeatureResolutionService {
105115
/// and from `FeatureService::mark_unavailable` so a disconnect does not
106116
/// keep serving a pre-disconnect tool list.
107117
pub async fn invalidate_space(&self, space_id: &str) {
108-
self.cache
109-
.write()
110-
.await
118+
let mut cache = self.cache.write().await;
119+
cache.generation = cache.generation.wrapping_add(1);
120+
cache
121+
.entries
111122
.retain(|(cached_space, _), _| cached_space != space_id);
112123
}
113124

114125
async fn invalidate_all(&self) {
115-
self.cache.write().await.clear();
126+
let mut cache = self.cache.write().await;
127+
cache.generation = cache.generation.wrapping_add(1);
128+
cache.entries.clear();
116129
}
117130

118131
/// Get all available features for a space (optionally filtered by type)
@@ -155,16 +168,25 @@ impl FeatureResolutionService {
155168
sorted_ids.sort();
156169
let key = (space_id.to_string(), sorted_ids);
157170

158-
if let Some(cached) = self.cache.read().await.get(&key).cloned() {
159-
return Ok(Self::apply_type_filter(cached, filter_type));
160-
}
171+
let generation = {
172+
let cache = self.cache.read().await;
173+
if let Some(cached) = cache.entries.get(&key).cloned() {
174+
return Ok(Self::apply_type_filter(cached, filter_type));
175+
}
176+
cache.generation
177+
};
161178

162179
// ponytail: concurrent misses recompute; single-flight if cold-start
163180
// stampede shows up.
164181
let resolved = self
165182
.resolve_feature_sets_uncached(space_id, feature_set_ids)
166183
.await?;
167-
self.cache.write().await.insert(key, resolved.clone());
184+
{
185+
let mut cache = self.cache.write().await;
186+
if cache.generation == generation {
187+
cache.entries.insert(key, resolved.clone());
188+
}
189+
}
168190
Ok(Self::apply_type_filter(resolved, filter_type))
169191
}
170192

crates/mcpmux-storage/src/repositories/embedding_repository.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ fn decode_vector(blob: &[u8]) -> Result<Vec<f32>> {
4848
}
4949

5050
Ok(blob
51-
.chunks_exact(std::mem::size_of::<f32>())
52-
.map(|chunk| f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
51+
.as_chunks::<{ std::mem::size_of::<f32>() }>()
52+
.0
53+
.iter()
54+
.map(|chunk| f32::from_le_bytes(*chunk))
5355
.collect())
5456
}
5557

0 commit comments

Comments
 (0)