///|
// IDs remain stable while reachable from an import. Unreachable slots release
// their values and are reused; repeated entry evaluations cannot grow the arena
// merely by leaving dead thunks/objects behind. No Jsonnet code runs here.
fn Vm::retain_imports(self : Vm) -> Unit {
// Go retries failed evaluations instead of retaining their exception contexts.
for id, original in self.retries {
self.thunks[id] = original
}
self.retries.clear()
for obj in self.objects {
let failed = obj.cache
.keys()
.filter(key => {
self.thunks[obj.cache[key]] is Failed(_) ||
self.thunks[obj.cache[key]] is Busy ||
self.thunks[obj.cache[key]] is Running(_) ||
self.thunks[obj.cache[key]] is RunningParameter(_, _)
})
.collect()
for key in failed {
ignore(obj.cache.remove(key))
}
}
self.ext.clear()
self.binary_imports.clear()
let thunks = Array::make(self.thunks.length(), false)
let objects = Array::make(self.objects.length(), false)
let pending : Array[(Bool, Int)] = self.imports
.values()
.map(id => (false, id))
.collect()
while pending.pop() is Some((is_object, id)) {
if is_object {
if objects[id] {
continue
}
objects[id] = true
let obj = self.objects[id]
for _, id in obj.cache {
pending.push((false, id))
}
for _, env in obj.scopes {
retain_environment(env, pending)
}
for layer in obj.layers {
retain_environment(layer.env, pending)
for _, field in layer.fields {
retain_environment(field.env, pending)
}
}
} else {
if thunks[id] {
continue
}
thunks[id] = true
match self.thunks[id] {
Pending(_, env) => retain_environment(env, pending)
GeneratedCall(function, arguments, _) => {
for id in arguments {
pending.push((false, id))
}
match function {
Closure(_, _, env) => retain_environment(env, pending)
_ => ()
}
}
Ready(ArrayValue(items)) =>
for id in items {
pending.push((false, id))
}
Ready(ObjectValue(id, _)) => pending.push((true, id))
Ready(Closure(_, _, env)) => retain_environment(env, pending)
_ => ()
}
}
}
self.free_thunks.clear()
self.free_objects.clear()
for id, retained in thunks {
if !retained {
self.thunks[id] = Ready(Nil)
self.free_thunks.push(id)
}
}
for id, retained in objects {
if !retained {
self.objects[id] = {
layers: [],
cache: Map([]),
scopes: Map([]),
assertion_state: 0,
assertion_error: None,
standard: false,
}
self.free_objects.push(id)
}
}
}
///|
// Import callers append context frames. Cached assertion errors own a snapshot,
// so later callers cannot append into the retained error or earlier results.
fn copy_evaluation_error(error : Error) -> Error {
match error {
Error(d) =>
Error({ ..d, context: d.context.copy(), trace: d.trace.copy(), })
_ => error
}
}
///|
fn retain_environment(env : Env, pending : Array[(Bool, Int)]) -> Unit {
let mut next = Some(env)
while next is Some(current) {
for _, id in current.names {
pending.push((false, id))
}
next = current.parent
}
}
///|
fn Vm::account_import(
self : Vm,
from : String,
path : String,
binary : Bool,
size : Int,
loc : Location,
) -> Unit raise {
let key = (from, path, binary)
if self.accessed_sources.contains(key) {
return
}
if self.accessed_sources.length() >= self.limits.imports {
raise problem(Limit, loc, "import count limit exceeded")
}
if size > self.limits.source_bytes ||
size > self.limits.import_bytes - self.imported_bytes {
raise problem(Limit, loc, "import byte limit exceeded")
}
self.imported_bytes += size
self.accessed_sources[key] = ()
}