diff --git a/Cargo.lock b/Cargo.lock index 025f4e4116..0942f4b481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,6 +1009,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boxcar" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6740c6e2fc6360fa57c35214c7493826aee95993926092606f27c983b40837be" +dependencies = [ + "loom", +] + [[package]] name = "bstr" version = "1.5.0" @@ -1242,14 +1251,14 @@ checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "clashmap" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bd59c81e2bd87a775ae2de75f070f7e2bfe97363a6ad652f46824564c23e4d" +checksum = "e8a055b1f1bf558eae4959f6dd77cf2d7d50ae1483928e60ef21ca5a24fd4321" dependencies = [ "crossbeam-utils", "hashbrown 0.15.2", "lock_api", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", "polonius-the-crab", "replace_with", ] @@ -1763,7 +1772,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -1777,7 +1786,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -2343,6 +2352,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -2498,6 +2513,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "304de19db7028420975a296ab0fcbbc8e69438c4ed254a1e41e2a7f37d5f0e0a" +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.58.0", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2798,7 +2826,7 @@ checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ "cfg-if", "libc", - "windows", + "windows 0.52.0", ] [[package]] @@ -3069,7 +3097,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -3620,6 +3648,19 @@ version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.12.3" @@ -4571,6 +4612,21 @@ dependencies = [ "seize", ] +[[package]] +name = "paracord" +version = "0.1.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4d20c42ae2826a91463e015d3d76064b07b222d94d3646e854071aae25043b" +dependencies = [ + "boxcar", + "clashmap", + "foldhash", + "hashbrown 0.15.2", + "serde", + "sync_wrapper 1.0.1", + "typed-arena", +] + [[package]] name = "parking" version = "2.1.1" @@ -4595,7 +4651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.8", + "parking_lot_core 0.9.10", ] [[package]] @@ -4614,15 +4670,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall 0.5.10", "smallvec", - "windows-targets 0.48.0", + "windows-targets 0.52.6", ] [[package]] @@ -5335,6 +5391,7 @@ dependencies = [ "opentelemetry", "p256 0.13.2", "papaya", + "paracord", "parking_lot 0.12.1", "parquet", "parquet_derive", @@ -5637,6 +5694,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "regex" version = "1.10.2" @@ -6301,6 +6367,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -8017,6 +8089,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typed-json" version = "0.1.1" @@ -8519,7 +8597,17 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core", + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -8532,6 +8620,60 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index ce8610be24..149accc44a 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -114,6 +114,7 @@ ed25519-dalek = { version = "2", default-features = false, features = ["rand_cor rsa = "0.9" workspace_hack.workspace = true +paracord = { version = "0.1.0-rc.5", features = ["serde"] } [dev-dependencies] assert-json-diff.workspace = true diff --git a/proxy/src/auth/backend/console_redirect.rs b/proxy/src/auth/backend/console_redirect.rs index f561df9202..e890f1de1a 100644 --- a/proxy/src/auth/backend/console_redirect.rs +++ b/proxy/src/auth/backend/console_redirect.rs @@ -203,7 +203,7 @@ async fn authenticate( let user: RoleName = db_info.user.into(); let user_info = ComputeUserInfo { - endpoint: db_info.aux.endpoint_id.as_str().into(), + endpoint: db_info.aux.endpoint_id.resolve().into(), user: user.clone(), options: NeonOptions::default(), }; diff --git a/proxy/src/auth/backend/jwt.rs b/proxy/src/auth/backend/jwt.rs index a716890a00..4fa51f0611 100644 --- a/proxy/src/auth/backend/jwt.rs +++ b/proxy/src/auth/backend/jwt.rs @@ -87,7 +87,7 @@ impl JwkCacheEntry { self.key_sets .values() // make sure our requested role has access to the key set - .filter(|key_set| key_set.role_names.iter().any(|role| **role == **role_name)) + .filter(|key_set| key_set.role_names.iter().any(|role| *role.resolve() == **role_name)) // try and find the requested key-id in the key set .find_map(|key_set| { key_set diff --git a/proxy/src/auth/backend/local.rs b/proxy/src/auth/backend/local.rs index 2224f492b8..7081904405 100644 --- a/proxy/src/auth/backend/local.rs +++ b/proxy/src/auth/backend/local.rs @@ -12,7 +12,7 @@ use crate::context::RequestContext; use crate::control_plane::NodeInfo; use crate::control_plane::messages::{ColdStartInfo, EndpointJwksResponse, MetricsAuxInfo}; use crate::http; -use crate::intern::{BranchIdTag, EndpointIdTag, InternId, ProjectIdTag}; +use crate::intern::{BranchIdInt, EndpointIdInt, ProjectIdInt}; use crate::types::EndpointId; use crate::url::ApiUrl; @@ -38,9 +38,9 @@ impl LocalBackend { }, // TODO(conrad): make this better reflect compute info rather than endpoint info. aux: MetricsAuxInfo { - endpoint_id: EndpointIdTag::get_interner().get_or_intern("local"), - project_id: ProjectIdTag::get_interner().get_or_intern("local"), - branch_id: BranchIdTag::get_interner().get_or_intern("local"), + endpoint_id: EndpointIdInt::get_or_intern("local"), + project_id: ProjectIdInt::get_or_intern("local"), + branch_id: BranchIdInt::get_or_intern("local"), compute_id: "local".into(), cold_start_info: ColdStartInfo::WarmCached, }, diff --git a/proxy/src/context/mod.rs b/proxy/src/context/mod.rs index 3a8828e70c..13b2066c19 100644 --- a/proxy/src/context/mod.rs +++ b/proxy/src/context/mod.rs @@ -210,7 +210,7 @@ impl RequestContext { pub(crate) fn set_project(&self, x: MetricsAuxInfo) { let mut this = self.0.try_lock().expect("should not deadlock"); if this.endpoint_id.is_none() { - this.set_endpoint_id(x.endpoint_id.as_str().into()); + this.set_endpoint_id(x.endpoint_id.resolve().into()); } this.branch = Some(x.branch_id); this.project = Some(x.project_id); diff --git a/proxy/src/intern.rs b/proxy/src/intern.rs index d7e39ebaf4..9016fb420a 100644 --- a/proxy/src/intern.rs +++ b/proxy/src/intern.rs @@ -1,272 +1,103 @@ -use std::hash::BuildHasherDefault; -use std::marker::PhantomData; -use std::num::NonZeroUsize; -use std::ops::Index; -use std::sync::OnceLock; +use core::fmt; -use lasso::{Capacity, MemoryLimits, Spur, ThreadedRodeo}; -use rustc_hash::FxHasher; +use paracord::custom_key; use crate::types::{AccountId, BranchId, EndpointId, ProjectId, RoleName}; -pub trait InternId: Sized + 'static { - fn get_interner() -> &'static StringInterner; -} +custom_key!(pub struct RoleNameInt); +custom_key!(pub struct EndpointIdInt); +custom_key!(pub struct BranchIdInt); +custom_key!(pub struct ProjectIdInt); +custom_key!(pub struct AccountIdInt); -pub struct StringInterner { - inner: ThreadedRodeo>, - _id: PhantomData, -} - -#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash)] -pub struct InternedString { - inner: Spur, - _id: PhantomData, -} - -impl std::fmt::Display for InternedString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_str().fmt(f) - } -} - -impl InternedString { - pub(crate) fn as_str(&self) -> &'static str { - Id::get_interner().inner.resolve(&self.inner) - } - pub(crate) fn get(s: &str) -> Option { - Id::get_interner().get(s) - } -} - -impl AsRef for InternedString { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl std::ops::Deref for InternedString { - type Target = str; - fn deref(&self) -> &str { - self.as_str() - } -} - -impl<'de, Id: InternId> serde::de::Deserialize<'de> for InternedString { - fn deserialize>(d: D) -> Result { - struct Visitor(PhantomData); - impl serde::de::Visitor<'_> for Visitor { - type Value = InternedString; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Ok(Id::get_interner().get_or_intern(v)) - } - } - d.deserialize_str(Visitor::(PhantomData)) - } -} - -impl serde::Serialize for InternedString { - fn serialize(&self, s: S) -> Result { - self.as_str().serialize(s) - } -} - -impl StringInterner { - pub(crate) fn new() -> Self { - StringInterner { - inner: ThreadedRodeo::with_capacity_memory_limits_and_hasher( - Capacity::new(2500, NonZeroUsize::new(1 << 16).expect("value is nonzero")), - // unbounded - MemoryLimits::for_memory_usage(usize::MAX), - BuildHasherDefault::::default(), - ), - _id: PhantomData, - } - } - - #[cfg(test)] - fn len(&self) -> usize { - self.inner.len() - } - - #[cfg(test)] - fn current_memory_usage(&self) -> usize { - self.inner.current_memory_usage() - } - - pub(crate) fn get_or_intern(&self, s: &str) -> InternedString { - InternedString { - inner: self.inner.get_or_intern(s), - _id: PhantomData, - } - } - - pub(crate) fn get(&self, s: &str) -> Option> { - Some(InternedString { - inner: self.inner.get(s)?, - _id: PhantomData, - }) - } -} - -impl Index> for StringInterner { - type Output = str; - - fn index(&self, index: InternedString) -> &Self::Output { - self.inner.resolve(&index.inner) - } -} - -impl Default for StringInterner { - fn default() -> Self { - Self::new() - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct RoleNameTag; -impl InternId for RoleNameTag { - fn get_interner() -> &'static StringInterner { - static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } -} -pub type RoleNameInt = InternedString; impl From<&RoleName> for RoleNameInt { fn from(value: &RoleName) -> Self { - RoleNameTag::get_interner().get_or_intern(value) + RoleNameInt::get_or_intern(value) + } +} +impl fmt::Display for RoleNameInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.resolve()) } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct EndpointIdTag; -impl InternId for EndpointIdTag { - fn get_interner() -> &'static StringInterner { - static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } -} -pub type EndpointIdInt = InternedString; impl From<&EndpointId> for EndpointIdInt { fn from(value: &EndpointId) -> Self { - EndpointIdTag::get_interner().get_or_intern(value) + EndpointIdInt::get_or_intern(value) } } impl From for EndpointIdInt { fn from(value: EndpointId) -> Self { - EndpointIdTag::get_interner().get_or_intern(&value) + EndpointIdInt::get_or_intern(&value) + } +} +impl fmt::Display for EndpointIdInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.resolve()) } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct BranchIdTag; -impl InternId for BranchIdTag { - fn get_interner() -> &'static StringInterner { - static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } -} -pub type BranchIdInt = InternedString; impl From<&BranchId> for BranchIdInt { fn from(value: &BranchId) -> Self { - BranchIdTag::get_interner().get_or_intern(value) + BranchIdInt::get_or_intern(value) } } impl From for BranchIdInt { fn from(value: BranchId) -> Self { - BranchIdTag::get_interner().get_or_intern(&value) + BranchIdInt::get_or_intern(&value) + } +} +impl AsRef for BranchIdInt { + fn as_ref(&self) -> &str { + self.resolve() + } +} +impl std::ops::Deref for BranchIdInt { + type Target = str; + fn deref(&self) -> &str { + self.resolve() } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct ProjectIdTag; -impl InternId for ProjectIdTag { - fn get_interner() -> &'static StringInterner { - static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } -} -pub type ProjectIdInt = InternedString; impl From<&ProjectId> for ProjectIdInt { fn from(value: &ProjectId) -> Self { - ProjectIdTag::get_interner().get_or_intern(value) + ProjectIdInt::get_or_intern(value) } } impl From for ProjectIdInt { fn from(value: ProjectId) -> Self { - ProjectIdTag::get_interner().get_or_intern(&value) + ProjectIdInt::get_or_intern(&value) + } +} +impl AsRef for ProjectIdInt { + fn as_ref(&self) -> &str { + self.resolve() + } +} +impl std::ops::Deref for ProjectIdInt { + type Target = str; + fn deref(&self) -> &str { + self.resolve() + } +} +impl fmt::Display for ProjectIdInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.resolve()) } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct AccountIdTag; -impl InternId for AccountIdTag { - fn get_interner() -> &'static StringInterner { - static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } -} -pub type AccountIdInt = InternedString; impl From<&AccountId> for AccountIdInt { fn from(value: &AccountId) -> Self { - AccountIdTag::get_interner().get_or_intern(value) + AccountIdInt::get_or_intern(value) } } impl From for AccountIdInt { fn from(value: AccountId) -> Self { - AccountIdTag::get_interner().get_or_intern(&value) + AccountIdInt::get_or_intern(&value) } } - -#[cfg(test)] -mod tests { - use std::sync::OnceLock; - - use super::InternId; - use crate::intern::StringInterner; - - struct MyId; - impl InternId for MyId { - fn get_interner() -> &'static StringInterner { - pub(crate) static ROLE_NAMES: OnceLock> = OnceLock::new(); - ROLE_NAMES.get_or_init(Default::default) - } - } - - #[test] - fn push_many_strings() { - use rand::rngs::StdRng; - use rand::{Rng, SeedableRng}; - use rand_distr::Zipf; - - let endpoint_dist = Zipf::new(500000, 0.8).unwrap(); - let endpoints = StdRng::seed_from_u64(272488357).sample_iter(endpoint_dist); - - let interner = MyId::get_interner(); - - const N: usize = 100_000; - let mut verify = Vec::with_capacity(N); - for endpoint in endpoints.take(N) { - let endpoint = format!("ep-string-interning-{endpoint}"); - let key = interner.get_or_intern(&endpoint); - verify.push((endpoint, key)); - } - - for (s, key) in verify { - assert_eq!(interner[key], s); - } - - // 2031616/59861 = 34 bytes per string - assert_eq!(interner.len(), 59_861); - // will have other overhead for the internal hashmaps that are not accounted for. - assert_eq!(interner.current_memory_usage(), 2_031_616); +impl fmt::Display for AccountIdInt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.resolve()) } } diff --git a/proxy/src/types.rs b/proxy/src/types.rs index 43b8dc5b29..e97139d847 100644 --- a/proxy/src/types.rs +++ b/proxy/src/types.rs @@ -1,4 +1,4 @@ -use crate::intern::{EndpointIdInt, EndpointIdTag, InternId}; +use crate::intern::EndpointIdInt; macro_rules! smol_str_wrapper { ($name:ident) => { @@ -85,7 +85,7 @@ impl EndpointId { #[must_use] pub fn normalize_intern(&self) -> EndpointIdInt { - EndpointIdTag::get_interner().get_or_intern(self.normalize_str()) + EndpointIdInt::get_or_intern(self.normalize_str()) } }