Skip to content

Commit c6b80e9

Browse files
committed
perf: Fewer Value clones
Signed-off-by: Dmitry Dygalo <[email protected]>
1 parent 620b1b9 commit c6b80e9

File tree

3 files changed

+113
-54
lines changed

3 files changed

+113
-54
lines changed

crates/jsonschema/src/compiler.rs

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@ use crate::{
1515
};
1616
use ahash::{AHashMap, AHashSet};
1717
use referencing::{
18-
Draft, List, Registry, Resolved, Resolver, Resource, ResourceRef, Uri, Vocabulary,
19-
VocabularySet,
18+
Draft, List, Registry, Resolved, Resolver, ResourceRef, Uri, Vocabulary, VocabularySet,
2019
};
2120
use serde_json::Value;
22-
use std::{borrow::Cow, cell::RefCell, iter::once, rc::Rc, sync::Arc};
21+
use std::{
22+
borrow::Cow,
23+
iter::once,
24+
rc::Rc,
25+
sync::{Arc, Mutex},
26+
};
2327

2428
const DEFAULT_SCHEME: &str = "json-schema";
2529
pub(crate) const DEFAULT_ROOT_URL: &str = "json-schema:///";
2630
type BaseUri = Uri<String>;
27-
type ResolverComponents = (Arc<BaseUri>, List<BaseUri>, Resource);
31+
type ResolverComponents<'a> = (Arc<BaseUri>, List<BaseUri>, Resolved<'a>);
2832

2933
/// Container for information required to build a tree.
3034
///
@@ -37,7 +41,7 @@ pub(crate) struct Context<'a> {
3741
vocabularies: VocabularySet,
3842
location: Location,
3943
pub(crate) draft: Draft,
40-
seen: Rc<RefCell<AHashSet<Arc<Uri<String>>>>>,
44+
seen: Arc<Mutex<AHashSet<Arc<Uri<String>>>>>,
4145
}
4246

4347
impl<'a> Context<'a> {
@@ -48,6 +52,7 @@ impl<'a> Context<'a> {
4852
vocabularies: VocabularySet,
4953
draft: Draft,
5054
location: Location,
55+
seen: Arc<Mutex<AHashSet<Arc<Uri<String>>>>>,
5156
) -> Self {
5257
Context {
5358
config,
@@ -56,7 +61,7 @@ impl<'a> Context<'a> {
5661
location,
5762
vocabularies,
5863
draft,
59-
seen: Rc::new(RefCell::new(AHashSet::new())),
64+
seen,
6065
}
6166
}
6267
pub(crate) fn draft(&self) -> Draft {
@@ -79,7 +84,7 @@ impl<'a> Context<'a> {
7984
vocabularies: self.vocabularies.clone(),
8085
draft: resource.draft(),
8186
location: self.location.clone(),
82-
seen: Rc::clone(&self.seen),
87+
seen: Arc::clone(&self.seen),
8388
})
8489
}
8590
pub(crate) fn as_resource_ref<'r>(&'a self, contents: &'r Value) -> ResourceRef<'r> {
@@ -99,7 +104,7 @@ impl<'a> Context<'a> {
99104
vocabularies: self.vocabularies.clone(),
100105
location,
101106
draft: self.draft,
102-
seen: Rc::clone(&self.seen),
107+
seen: Arc::clone(&self.seen),
103108
}
104109
}
105110

@@ -110,7 +115,9 @@ impl<'a> Context<'a> {
110115
pub(crate) fn scopes(&self) -> List<Uri<String>> {
111116
self.resolver.dynamic_scope()
112117
}
113-
118+
pub(crate) fn full_base_uri(&self) -> Arc<Uri<String>> {
119+
self.resolver.base_uri()
120+
}
114121
pub(crate) fn base_uri(&self) -> Option<Uri<String>> {
115122
let base_uri = self.resolver.base_uri();
116123
if base_uri.scheme().as_str() == DEFAULT_SCHEME {
@@ -151,7 +158,7 @@ impl<'a> Context<'a> {
151158
draft,
152159
vocabularies,
153160
location,
154-
seen: Rc::clone(&self.seen),
161+
seen: Arc::clone(&self.seen),
155162
}
156163
}
157164
pub(crate) fn get_content_media_type_check(
@@ -186,13 +193,13 @@ impl<'a> Context<'a> {
186193
let uri = self
187194
.resolver
188195
.resolve_against(&self.resolver.base_uri().borrow(), reference)?;
189-
Ok(self.seen.borrow().contains(&*uri))
196+
Ok(self.seen.lock().unwrap().contains(&*uri))
190197
}
191198
pub(crate) fn mark_seen(&self, reference: &str) -> Result<(), referencing::Error> {
192199
let uri = self
193200
.resolver
194201
.resolve_against(&self.resolver.base_uri().borrow(), reference)?;
195-
self.seen.borrow_mut().insert(uri);
202+
self.seen.lock().unwrap().insert(uri);
196203
Ok(())
197204
}
198205

@@ -204,25 +211,23 @@ impl<'a> Context<'a> {
204211
pub(crate) fn lookup_maybe_recursive(
205212
&self,
206213
reference: &str,
207-
is_recursive: bool,
214+
maybe_recursive: bool,
208215
) -> Result<Option<ResolverComponents>, ValidationError<'static>> {
209216
let resolved = if self.is_circular_reference(reference)? {
210217
// Otherwise we need to manually check whether this location has already been explored
211218
self.resolver.lookup(reference)?
212219
} else {
213220
// This is potentially recursive, but it is unknown yet
214-
if !is_recursive {
221+
if !maybe_recursive {
215222
self.mark_seen(reference)?;
216223
}
217224
return Ok(None);
218225
};
219-
let resource = self.draft().create_resource(resolved.contents().clone());
220-
let mut base_uri = resolved.resolver().base_uri();
221-
let scopes = resolved.resolver().dynamic_scope();
222-
if let Some(id) = resource.id() {
223-
base_uri = self.registry.resolve_against(&base_uri.borrow(), id)?;
224-
};
225-
Ok(Some((base_uri, scopes, resource)))
226+
Ok(Some((
227+
self.resolver.base_uri(),
228+
self.resolver.dynamic_scope(),
229+
resolved,
230+
)))
226231
}
227232

228233
pub(crate) fn location(&self) -> &Location {
@@ -240,6 +245,10 @@ impl<'a> Context<'a> {
240245
self.vocabularies.contains(vocabulary)
241246
}
242247
}
248+
249+
pub(crate) fn seen(&self) -> Arc<Mutex<AHashSet<Arc<Uri<String>>>>> {
250+
Arc::clone(&self.seen)
251+
}
243252
}
244253

245254
pub(crate) fn build_validator(
@@ -275,6 +284,7 @@ pub(crate) fn build_validator(
275284
vocabularies,
276285
draft,
277286
Location::new(),
287+
Arc::new(Mutex::new(AHashSet::new())),
278288
);
279289

280290
// Validate the schema itself

crates/jsonschema/src/keywords/ref_.rs

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::{rc::Rc, sync::Arc};
1+
use std::{
2+
rc::Rc,
3+
sync::{Arc, Mutex},
4+
};
25

36
use crate::{
47
compiler,
@@ -10,8 +13,9 @@ use crate::{
1013
validator::{PartialApplication, Validate},
1114
ValidationError, ValidationOptions,
1215
};
16+
use ahash::AHashSet;
1317
use once_cell::sync::OnceCell;
14-
use referencing::{Draft, List, Registry, Resource, Uri, VocabularySet};
18+
use referencing::{Draft, List, Registry, Uri, VocabularySet};
1519
use serde_json::{Map, Value};
1620

1721
pub(crate) enum RefValidator {
@@ -24,31 +28,34 @@ impl RefValidator {
2428
pub(crate) fn compile<'a>(
2529
ctx: &compiler::Context,
2630
reference: &str,
27-
is_recursive: bool,
31+
maybe_recursive: bool,
2832
keyword: &str,
2933
) -> Option<CompilationResult<'a>> {
3034
let location = ctx.location().join(keyword);
3135
Some(
32-
if let Some((base_uri, scopes, resource)) = {
33-
match ctx.lookup_maybe_recursive(reference, is_recursive) {
36+
if let Some((base_uri, scopes, resolved)) = {
37+
match ctx.lookup_maybe_recursive(reference, maybe_recursive) {
3438
Ok(resolved) => resolved,
3539
Err(error) => return Some(Err(error)),
3640
}
3741
} {
3842
// NOTE: A better approach would be to compare the absolute locations
39-
if let Value::Object(contents) = resource.contents() {
43+
if let Value::Object(contents) = resolved.contents() {
4044
if let Some(Some(resolved)) = contents.get(keyword).map(Value::as_str) {
4145
if resolved == reference {
4246
return None;
4347
}
4448
}
4549
}
4650
Ok(Box::new(RefValidator::Lazy(LazyRefValidator {
47-
resource,
51+
reference: Reference::Default {
52+
reference: reference.to_string(),
53+
},
4854
config: Arc::clone(ctx.config()),
4955
registry: Arc::clone(&ctx.registry),
5056
base_uri,
5157
scopes,
58+
seen: ctx.seen(),
5259
location,
5360
vocabularies: ctx.vocabularies().clone(),
5461
draft: ctx.draft(),
@@ -79,6 +86,11 @@ impl RefValidator {
7986
}
8087
}
8188

89+
enum Reference {
90+
Default { reference: String },
91+
Recursive,
92+
}
93+
8294
/// Lazily evaluated validator used for recursive references.
8395
///
8496
/// The validator tree nodes can't be arbitrary looked up in the current
@@ -87,11 +99,12 @@ impl RefValidator {
8799
/// representation for the validation tree may allow building cycles easier and
88100
/// lazy evaluation won't be needed.
89101
pub(crate) struct LazyRefValidator {
90-
resource: Resource,
102+
reference: Reference,
91103
config: Arc<ValidationOptions>,
92104
registry: Arc<Registry>,
93105
scopes: List<Uri<String>>,
94106
base_uri: Arc<Uri<String>>,
107+
seen: Arc<Mutex<AHashSet<Arc<Uri<String>>>>>,
95108
vocabularies: VocabularySet,
96109
location: Location,
97110
draft: Draft,
@@ -101,20 +114,15 @@ pub(crate) struct LazyRefValidator {
101114
impl LazyRefValidator {
102115
#[inline]
103116
pub(crate) fn compile<'a>(ctx: &compiler::Context) -> CompilationResult<'a> {
104-
let scopes = ctx.scopes();
105-
let resolved = ctx.lookup_recursive_reference()?;
106-
let resource = ctx.draft().create_resource(resolved.contents().clone());
107-
let resolver = resolved.resolver();
108-
let mut base_uri = resolver.base_uri();
109-
if let Some(id) = resource.id() {
110-
base_uri = resolver.resolve_against(&base_uri.borrow(), id)?;
111-
};
117+
// Verify that the reference is resolvable
118+
ctx.lookup_recursive_reference()?;
112119
Ok(Box::new(LazyRefValidator {
113-
resource,
120+
reference: Reference::Recursive,
114121
config: Arc::clone(ctx.config()),
115122
registry: Arc::clone(&ctx.registry),
116-
base_uri,
117-
scopes,
123+
base_uri: ctx.full_base_uri(),
124+
scopes: ctx.scopes(),
125+
seen: ctx.seen(),
118126
vocabularies: ctx.vocabularies().clone(),
119127
location: ctx.location().join("$recursiveRef"),
120128
draft: ctx.draft(),
@@ -123,21 +131,58 @@ impl LazyRefValidator {
123131
}
124132
fn lazy_compile(&self) -> &SchemaNode {
125133
self.inner.get_or_init(|| {
126-
let resolver = self
127-
.registry
128-
.resolver_from_raw_parts(self.base_uri.clone(), self.scopes.clone());
129-
130-
let ctx = compiler::Context::new(
131-
Arc::clone(&self.config),
132-
Arc::clone(&self.registry),
133-
Rc::new(resolver),
134-
self.vocabularies.clone(),
135-
self.draft,
136-
self.location.clone(),
137-
);
138134
// INVARIANT: This schema was already used during compilation before detecting a
139135
// reference cycle that lead to building this validator.
140-
compiler::compile(&ctx, self.resource.as_ref()).expect("Invalid schema")
136+
match &self.reference {
137+
Reference::Default { reference } => {
138+
let resolver = self
139+
.registry
140+
.resolver_from_raw_parts(self.base_uri.clone(), self.scopes.clone());
141+
let resolved = resolver.lookup(reference).unwrap();
142+
let resource = self.draft.create_resource_ref(resolved.contents());
143+
let mut base_uri = resolved.resolver().base_uri();
144+
let scopes = resolved.resolver().dynamic_scope();
145+
if let Some(id) = resource.id() {
146+
base_uri = self
147+
.registry
148+
.resolve_against(&base_uri.borrow(), id)
149+
.unwrap();
150+
};
151+
152+
let resolver = self.registry.resolver_from_raw_parts(base_uri, scopes);
153+
154+
let ctx = compiler::Context::new(
155+
Arc::clone(&self.config),
156+
Arc::clone(&self.registry),
157+
Rc::new(resolver),
158+
self.vocabularies.clone(),
159+
self.draft,
160+
self.location.clone(),
161+
self.seen.clone(),
162+
);
163+
164+
compiler::compile(&ctx, resource).expect("Invalid schema")
165+
}
166+
Reference::Recursive => {
167+
let resolver = self
168+
.registry
169+
.resolver_from_raw_parts(self.base_uri.clone(), self.scopes.clone());
170+
let resolved = resolver
171+
.lookup_recursive_ref()
172+
.expect("Failed to resolve a recursive reference");
173+
let ctx = compiler::Context::new(
174+
Arc::clone(&self.config),
175+
Arc::clone(&self.registry),
176+
Rc::new(resolver),
177+
self.vocabularies.clone(),
178+
self.draft,
179+
self.location.clone(),
180+
self.seen.clone(),
181+
);
182+
let resource = ctx.draft().create_resource_ref(resolved.contents());
183+
compiler::compile(&ctx, resource).expect("Invalid schema")
184+
}
185+
}
141186
})
142187
}
143188
}

crates/jsonschema/src/keywords/unevaluated_properties.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::{rc::Rc, sync::Arc};
1+
use std::{
2+
rc::Rc,
3+
sync::{Arc, Mutex},
4+
};
25

36
use ahash::AHashSet;
47
use fancy_regex::Regex;
@@ -185,6 +188,7 @@ impl<T: PropertiesFilter> LazyReference<T> {
185188
self.vocabularies.clone(),
186189
self.draft,
187190
self.location.clone(),
191+
Arc::new(Mutex::new(AHashSet::new())),
188192
);
189193

190194
Box::new(

0 commit comments

Comments
 (0)