task_local context over context objects

- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
This commit is contained in:
2026-01-01 14:54:29 +00:00
parent 06debb3636
commit 32d6237dc5
92 changed files with 2507 additions and 2223 deletions

View File

@@ -11,6 +11,7 @@ orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
futures = { version = "0.3.31", features = ["std"], default-features = false }
itertools = "0.14.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[dev-dependencies]
test_executors = "0.3.5"

67
orchid-api/src/binary.rs Normal file
View File

@@ -0,0 +1,67 @@
//! # Binary extension definition
//!
//! A binary extension is a DLL / shared object / dylib with a symbol called
//! `orchid_extension_main` which accepts a single argument of type [ExtCtx].
//! Once that is received, communication continuees through the channel with the
//! same protocol outlined in [crate::proto]
use unsync_pipe::{Reader, Writer};
/// !Send !Sync owned waker
#[repr(C)]
pub struct OwnedWakerVT {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `&self`
wake: extern "C" fn(*const ()),
}
/// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`.
/// When received in [FutureVT::poll], it must not outlive the call.
#[repr(C)]
pub struct FutureContextVT {
data: *const (),
/// `&self`
waker: extern "C" fn(*const ()) -> OwnedWakerVT,
}
/// ABI-stable `Poll<()>`
#[repr(C)]
pub enum UnitPoll {
Pending,
Ready,
}
/// ABI-stable `Pin<Box<dyn Future<Output = ()>>>`
#[repr(C)]
pub struct FutureVT {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `&mut self` Equivalent to [Future::poll]
poll: extern "C" fn(*const (), FutureContextVT) -> UnitPoll,
}
/// Owned extension context.
///
/// When an extension starts, this is passed to
#[repr(C)]
pub struct ExtensionContext {
data: *const (),
/// `self`
drop: extern "C" fn(*const ()),
/// `self` upgrade to a later version of this struct. May only be called if
/// none of the other elements have been used yet. If a newer version isn't
/// supported, the server must return null, otherwise the the return value is
/// a pointer to the immediate next version of this struct
next: extern "C" fn(*const ()) -> *const (),
/// `&self` Add a future to this extension's task
spawn: extern "C" fn(*const (), FutureVT),
/// serialized [crate::HostExtChannel]
input: Reader,
/// serialized [crate::ExtHostChannel]
output: Writer,
/// UTF-8 log stream directly to log service.
log: Writer,
}

View File

@@ -1,3 +1,4 @@
pub mod binary;
mod lexer;
pub use lexer::*;
mod format;

View File

@@ -22,51 +22,54 @@
//! be preserved. Toolkits must ensure that the client code is able to observe
//! the ordering of messages.
use std::io;
use std::pin::Pin;
use futures::{AsyncRead, AsyncWrite};
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt};
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact, write_exact};
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact};
use crate::{Sweeped, atom, expr, interner, lexer, logging, parser, system, tree};
static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n";
#[derive(Clone, Debug)]
pub struct HostHeader {
pub log_strategy: logging::LogStrategy,
pub msg_logs: logging::LogStrategy,
}
impl Decode for HostHeader {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
read_exact(read.as_mut(), HOST_INTRO).await;
Self {
log_strategy: logging::LogStrategy::decode(read.as_mut()).await,
msg_logs: logging::LogStrategy::decode(read.as_mut()).await,
}
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
read_exact(read.as_mut(), HOST_INTRO).await?;
Ok(Self {
log_strategy: logging::LogStrategy::decode(read.as_mut()).await?,
msg_logs: logging::LogStrategy::decode(read.as_mut()).await?,
})
}
}
impl Encode for HostHeader {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write_exact(write.as_mut(), HOST_INTRO).await;
self.log_strategy.encode(write.as_mut()).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(HOST_INTRO).await?;
self.log_strategy.encode(write.as_mut()).await?;
self.msg_logs.encode(write.as_mut()).await
}
}
static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n";
#[derive(Clone, Debug)]
pub struct ExtensionHeader {
pub name: String,
pub systems: Vec<system::SystemDecl>,
}
impl Decode for ExtensionHeader {
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
read_exact(read.as_mut(), EXT_INTRO).await;
Self { name: String::decode(read.as_mut()).await, systems: Vec::decode(read).await }
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
read_exact(read.as_mut(), EXT_INTRO).await?;
Ok(Self { name: String::decode(read.as_mut()).await?, systems: Vec::decode(read).await? })
}
}
impl Encode for ExtensionHeader {
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
write_exact(write.as_mut(), EXT_INTRO).await;
self.name.encode(write.as_mut()).await;
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
write.write_all(EXT_INTRO).await?;
self.name.encode(write.as_mut()).await?;
self.systems.encode(write).await
}
}
@@ -169,9 +172,9 @@ mod tests {
log_strategy: logging::LogStrategy::File("SomeFile".to_string()),
msg_logs: logging::LogStrategy::File("SomeFile".to_string()),
};
let mut enc = &enc_vec(&hh).await[..];
let mut enc = &enc_vec(&hh)[..];
eprintln!("Encoded to {enc:?}");
HostHeader::decode(Pin::new(&mut enc)).await;
HostHeader::decode(Pin::new(&mut enc)).await.unwrap();
assert_eq!(enc, []);
})
}
@@ -188,9 +191,9 @@ mod tests {
priority: NotNan::new(1f64).unwrap(),
}],
};
let mut enc = &enc_vec(&eh).await[..];
let mut enc = &enc_vec(&eh)[..];
eprintln!("Encoded to {enc:?}");
ExtensionHeader::decode(Pin::new(&mut enc)).await;
ExtensionHeader::decode(Pin::new(&mut enc)).await.unwrap();
assert_eq!(enc, [])
})
}