fix: release Dever 0.1.6 with shared runtime assets and unbounded workers

This commit is contained in:
Codex committed 2026-10-12 00:13:27 +00:00
1 parent a900d7e8db
commit 5589b657db
17 files changed
+357 -49

No files matched your search

@@ -541,7 +541,7 @@ typedef struct {
uint64_t capability_count;
const DeverRtWireName *operations;
uint64_t operation_count;
uint64_t timeout_ms;
uint64_t protocol_timeout_ms;
} DeverRtExternalDefinition;
typedef struct {
DeverRtWireName path;
+2 -2
View File
@@ -4373,7 +4373,7 @@ mod runtime {
capability_count: u64,
operations: *const Name,
operation_count: u64,
timeout_ms: u64,
protocol_timeout_ms: u64,
}
#[repr(C)]
@@ -4490,7 +4490,7 @@ mod runtime {
} else {
Some(unsafe { managed::borrowed::<String>(setting) }?.clone())
},
timeout_ms: row.timeout_ms,
protocol_timeout_ms: row.protocol_timeout_ms,
})
})(
);
+2 -2
View File
@@ -858,7 +858,7 @@ impl InstalledRegistry {
fn signed_artifact(&self, relative: &Path) -> Result<Vec<u8>, String> {
let name = relative.to_str().ok_or("runtime path is not UTF-8")?;
self.resources.read(name)
self.resources.read_shared(name)
}
fn process_runtime_resources(
@@ -868,7 +868,7 @@ impl InstalledRegistry {
let prefix = format!("process-runtime/{}/", target.platform());
let mut resources = Vec::new();
let mut files = Vec::new();
for artifact in self.resources.artifacts(&prefix)? {
for artifact in self.resources.catalog_artifacts(&prefix) {
let Some(relative) = artifact.path.strip_prefix(&prefix) else {
continue;
};
@@ -270,6 +270,92 @@ impl SignedResources {
Ok(bytes)
}
pub fn read_shared(&self, logical_path: &str) -> Result<Vec<u8>, String> {
if self
.manifest
.artifacts
.iter()
.any(|artifact| artifact.path == logical_path)
{
return self.read(logical_path);
}
let artifact = self
.manifest
.artifacts
.iter()
.chain(
self.manifest
.extensions
.iter()
.flat_map(|extension| &extension.artifacts),
)
.find(|artifact| artifact.path == logical_path)
.ok_or_else(|| format!("signed release does not declare resource '{logical_path}'"))?;
super::service::resource_get(
&self.layout,
&self.manifest.version,
&self.catalog,
self.manifest
.extensions
.iter()
.find(|extension| {
extension
.artifacts
.iter()
.any(|artifact| artifact.path == logical_path)
})
.map(Extension::id),
&super::ArtifactReceipt {
sha256: artifact.sha256.clone(),
bytes: artifact.bytes,
},
)
}
pub(super) fn open_requested(
&self,
catalog: &str,
extension: Option<&ExtensionId>,
expected: &super::ArtifactReceipt,
) -> Result<fs::File, String> {
if catalog != self.catalog {
return Err("signed resource catalog differs from the installed release".into());
}
let artifacts = match extension {
Some(id) => &self.extension(id)?.artifacts,
None => &self.manifest.artifacts,
};
let artifact = artifacts
.iter()
.find(|artifact| artifact.sha256 == expected.sha256 && artifact.bytes == expected.bytes)
.ok_or("signed release does not declare the requested resource identity")?;
let path = self.path(&artifact.path)?;
let file = fs::File::open(path)
.map_err(|error| format!("cannot open signed resource: {error}"))?;
let metadata = file
.metadata()
.map_err(|error| format!("cannot inspect signed resource: {error}"))?;
if !metadata.is_file() || metadata.len() != artifact.bytes {
return Err("signed resource changed while opening".into());
}
Ok(file)
}
pub fn catalog_artifacts(&self, prefix: &str) -> Vec<Artifact> {
self.manifest
.artifacts
.iter()
.chain(
self.manifest
.extensions
.iter()
.flat_map(|extension| &extension.artifacts),
)
.filter(|artifact| artifact.path.starts_with(prefix))
.cloned()
.collect()
}
pub fn artifacts(&self, prefix: &str) -> Result<Vec<Artifact>, String> {
let mut artifacts = Vec::new();
for artifact in self
+90 -8
View File
@@ -105,6 +105,12 @@ enum Operation {
version: Version,
extension: super::ExtensionId,
},
ResourceGet {
version: Version,
catalog: String,
extension: Option<super::ExtensionId>,
artifact: ArtifactReceipt,
},
Compile {
bytes: u64,
},
@@ -361,14 +367,7 @@ pub fn artifact_get_optional(
return Ok(None);
}
require_artifact(response, &expected)?;
let mut bytes = vec![0; expected.bytes as usize];
stream
.read_exact(&mut bytes)
.map_err(|error| format!("deverd artifact download is incomplete: {error}"))?;
if hex(&Sha256::digest(&bytes)) != expected.sha256 {
return Err("deverd artifact download failed SHA-256 verification".into());
}
Ok(Some(bytes))
read_artifact(&mut stream, &expected).map(Some)
}
#[cfg(not(unix))]
{
@@ -401,6 +400,59 @@ pub fn ensure_extension(
}
}
pub(super) fn resource_get(
layout: &Layout,
version: &Version,
catalog: &str,
extension: Option<super::ExtensionId>,
expected: &ArtifactReceipt,
) -> Result<Vec<u8>, String> {
#[cfg(unix)]
{
let (mut stream, token) = connect(layout)?;
stream
.set_read_timeout(Some(std::time::Duration::from_secs(30)))
.map_err(|error| format!("cannot configure signed resource IPC timeout: {error}"))?;
write_frame(
&mut stream,
&Request {
token,
operation: Operation::ResourceGet {
version: version.clone(),
catalog: catalog.into(),
extension,
artifact: expected.clone(),
},
installed: Vec::new(),
},
)?;
require_artifact(read_frame(&mut stream)?, expected)?;
read_artifact(&mut stream, expected)
}
#[cfg(not(unix))]
{
let _ = (layout, version, catalog, extension, expected);
Err("deverd signed resource IPC is not implemented for this platform".into())
}
}
#[cfg(unix)]
fn read_artifact(
stream: &mut std::os::unix::net::UnixStream,
expected: &ArtifactReceipt,
) -> Result<Vec<u8>, String> {
let length = usize::try_from(expected.bytes)
.map_err(|_| "deverd artifact is too large for this platform")?;
let mut bytes = vec![0; length];
stream
.read_exact(&mut bytes)
.map_err(|error| format!("deverd artifact download is incomplete: {error}"))?;
if hex(&Sha256::digest(&bytes)) != expected.sha256 {
return Err("deverd artifact download failed SHA-256 verification".into());
}
Ok(bytes)
}
fn require_artifact(response: Response, expected: &ArtifactReceipt) -> Result<(), String> {
if !response.ok {
return Err(response
@@ -597,6 +649,36 @@ fn handle(
Err(error) => failure(error),
};
}
Operation::ResourceGet {
version,
catalog,
extension,
artifact,
} => {
let file = match super::SignedResources::load(layout, &version).and_then(|resources| {
resources.open_requested(&catalog, extension.as_ref(), &artifact)
}) {
Ok(file) => file,
Err(error) => return failure(error),
};
let length = artifact.bytes;
if let Err(error) = write_frame(stream, &artifact_response(artifact)) {
return failure(error);
}
match std::io::copy(&mut file.take(length), stream) {
Ok(copied) if copied == length => {}
Ok(_) => return failure("deverd signed resource transfer is truncated".into()),
Err(error) => {
return failure(format!("deverd signed resource transfer failed: {error}"));
}
}
return Response {
ok: true,
status: None,
artifact: None,
error: None,
};
}
Operation::Compile { bytes } => {
return match compile::serve(layout, stream, bytes) {
Ok(()) => Response {
+1 -1
View File
@@ -222,7 +222,7 @@ pub(super) fn initialize(
} else {
"None"
};
writeln!(output, "dever_runtime::component::start(dever_runtime::component::Definition {{ key: {}.into(), ecosystem: {}.into(), entry: {}.into(), port: {}.into(), adapter: {}.into(), schema: {}.into(), capabilities: vec![{capabilities}], operations: vec![{operations}], setting: {setting}, timeout_ms: 30_000 }}).await?;", rust_string(&external_key(adapter)), rust_string(external.ecosystem.keyword()), rust_string(&external.entry), rust_string(&adapter.identity), rust_string(&adapter.name), rust_string(&external.schema)).expect("string formatting");
writeln!(output, "dever_runtime::component::start(dever_runtime::component::Definition {{ key: {}.into(), ecosystem: {}.into(), entry: {}.into(), port: {}.into(), adapter: {}.into(), schema: {}.into(), capabilities: vec![{capabilities}], operations: vec![{operations}], setting: {setting}, protocol_timeout_ms: 30_000 }}).await?;", rust_string(&external_key(adapter)), rust_string(external.ecosystem.keyword()), rust_string(&external.entry), rust_string(&adapter.identity), rust_string(&adapter.name), rust_string(&external.schema)).expect("string formatting");
}
for (port, implementation) in &adapter.operations {
if instances.iter().any(|instance| instance.function == *port) {
+4 -11
View File
@@ -33,7 +33,7 @@ pub struct Definition {
pub capabilities: Vec<String>,
pub operations: Vec<String>,
pub setting: Option<String>,
pub timeout_ms: u64,
pub protocol_timeout_ms: u64,
}
#[derive(Debug)]
@@ -318,7 +318,7 @@ fn validate_definition(definition: &Definition) -> Result<(), String> {
|| definition.adapter.is_empty()
|| definition.schema.is_empty()
|| definition.operations.is_empty()
|| definition.timeout_ms == 0
|| definition.protocol_timeout_ms == 0
{
return Err("incomplete external Adapter definition".into());
}
@@ -468,10 +468,8 @@ async fn protocol_session(
let _ = request.response.send(Err("external Adapter worker stopped".into()));
return Err("external Adapter worker stopped while writing a call".into());
}
result = write_frame_timeout(input, &message, Duration::from_millis(definition.timeout_ms)) => result?,
result = write_frame_timeout(input, &message, Duration::from_millis(definition.protocol_timeout_ms)) => result?,
}
let timeout = tokio::time::sleep(Duration::from_millis(definition.timeout_ms));
tokio::pin!(timeout);
let result = tokio::select! {
biased;
_ = &mut *stopping => {
@@ -484,11 +482,6 @@ async fn protocol_session(
cancel_request(input, output, id).await?;
continue;
}
_ = &mut timeout => {
let _ = request.response.send(Err("external Adapter call timed out".into()));
cancel_request(input, output, id).await?;
continue;
}
};
match result {
Ok(reply) => {
@@ -564,7 +557,7 @@ async fn handshake(
input: &mut ChildStdin,
output: &mut ChildStdout,
) -> Result<(), String> {
let timeout = Duration::from_millis(definition.timeout_ms);
let timeout = Duration::from_millis(definition.protocol_timeout_ms);
write_frame_timeout(input, &hello_message(definition)?, timeout).await?;
let ready = read_frame_timeout(output, timeout).await?;
validate_ready(definition, &ready)?;