Complete first release candidate audit (#107)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
This commit is contained in:
@@ -12,10 +12,12 @@ use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::sync::{
|
||||
Arc, Mutex, Weak,
|
||||
Arc, Condvar, Mutex, Weak,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
};
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub trait Collection<T> {}
|
||||
|
||||
@@ -71,8 +73,9 @@ impl Hash for OpaqueObject {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum Object {
|
||||
#[default]
|
||||
Undefined,
|
||||
Boolean(bool),
|
||||
Integer(i32),
|
||||
@@ -202,6 +205,54 @@ impl From<&str> for Object {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Object {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Boolean(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Object {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::Integer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Object {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::UInteger(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Object {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::Long(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Object {
|
||||
fn from(value: u64) -> Self {
|
||||
Self::ULong(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Object {
|
||||
fn from(value: f32) -> Self {
|
||||
Self::Real(f64::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Object {
|
||||
fn from(value: f64) -> Self {
|
||||
Self::Real(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Object {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self::Bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<crate::Color4> for Object {
|
||||
fn from(value: crate::Color4) -> Self {
|
||||
Self::Color4(value)
|
||||
@@ -256,6 +307,14 @@ pub struct CultureInfo(pub String);
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct ExternalError(pub String);
|
||||
|
||||
impl fmt::Display for ExternalError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ExternalError {}
|
||||
|
||||
/// Type-erased enum name and optional [`crate::EnumInfoAttribute`] text.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct EnumValue {
|
||||
@@ -695,14 +754,214 @@ pub trait Close {
|
||||
///
|
||||
/// Returns the mapped disposal error, or an unimplemented error until the
|
||||
/// owning resource supplies its implementation.
|
||||
fn close(&mut self) -> Result<(), ExternalError> {
|
||||
Err(ExternalError("Close::close is not implemented".to_owned()))
|
||||
fn close(&mut self) -> Result<(), ExternalError>;
|
||||
}
|
||||
|
||||
struct ThreadState {
|
||||
name: Option<String>,
|
||||
alive: AtomicBool,
|
||||
panicked: AtomicBool,
|
||||
completed: Mutex<bool>,
|
||||
completed_changed: Condvar,
|
||||
handle: Mutex<Option<JoinHandle<()>>>,
|
||||
}
|
||||
|
||||
/// An owned, cloneable handle to a native worker thread.
|
||||
///
|
||||
/// Unlike the old API marker, this value tracks liveness and supports bounded
|
||||
/// joins without relying on platform-specific thread APIs.
|
||||
#[derive(Clone)]
|
||||
pub struct Thread(Arc<ThreadState>);
|
||||
|
||||
impl Thread {
|
||||
/// Starts a named native thread.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an external error when the operating system refuses to create
|
||||
/// the thread.
|
||||
pub fn spawn(
|
||||
name: Option<String>,
|
||||
action: impl FnOnce() + Send + 'static,
|
||||
) -> Result<Self, ExternalError> {
|
||||
let state = Arc::new(ThreadState {
|
||||
name: name.clone(),
|
||||
alive: AtomicBool::new(true),
|
||||
panicked: AtomicBool::new(false),
|
||||
completed: Mutex::new(false),
|
||||
completed_changed: Condvar::new(),
|
||||
handle: Mutex::new(None),
|
||||
});
|
||||
let worker_state = Arc::clone(&state);
|
||||
let mut builder = std::thread::Builder::new();
|
||||
if let Some(name) = name {
|
||||
builder = builder.name(name);
|
||||
}
|
||||
let handle = builder
|
||||
.spawn(move || {
|
||||
if std::panic::catch_unwind(std::panic::AssertUnwindSafe(action)).is_err() {
|
||||
worker_state.panicked.store(true, Ordering::Release);
|
||||
}
|
||||
worker_state.alive.store(false, Ordering::Release);
|
||||
*worker_state
|
||||
.completed
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = true;
|
||||
worker_state.completed_changed.notify_all();
|
||||
})
|
||||
.map_err(|error| ExternalError(error.to_string()))?;
|
||||
*state
|
||||
.handle
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Some(handle);
|
||||
Ok(Self(state))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.0.name.as_deref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.0.alive.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
/// Waits at most `timeout` for completion and joins a completed worker.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an external error if the worker panicked while running.
|
||||
pub fn join_timeout(&self, timeout: Duration) -> Result<bool, ExternalError> {
|
||||
let completed = self
|
||||
.0
|
||||
.completed
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let (completed, wait) = self
|
||||
.0
|
||||
.completed_changed
|
||||
.wait_timeout_while(completed, timeout, |completed| !*completed)
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if wait.timed_out() && !*completed {
|
||||
return Ok(false);
|
||||
}
|
||||
drop(completed);
|
||||
if let Some(handle) = self
|
||||
.0
|
||||
.handle
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.take()
|
||||
{
|
||||
handle
|
||||
.join()
|
||||
.map_err(|_| ExternalError("worker thread panicked".to_owned()))?;
|
||||
}
|
||||
if self.0.panicked.load(Ordering::Acquire) {
|
||||
return Err(ExternalError("worker thread panicked".to_owned()));
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Thread;
|
||||
impl fmt::Debug for Thread {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("Thread")
|
||||
.field("name", &self.name())
|
||||
.field("alive", &self.is_alive())
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Task<T>(pub PhantomData<fn() -> T>);
|
||||
struct CurrentThreadWake(std::thread::Thread);
|
||||
|
||||
impl std::task::Wake for CurrentThreadWake {
|
||||
fn wake(self: Arc<Self>) {
|
||||
self.0.unpark();
|
||||
}
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) {
|
||||
self.0.unpark();
|
||||
}
|
||||
}
|
||||
|
||||
/// A runtime-neutral owned task used by mapped .NET `Task<T>` signatures.
|
||||
///
|
||||
/// The task can be awaited normally or synchronously observed with a bounded
|
||||
/// wait. It deliberately does not create or require a Tokio runtime.
|
||||
pub struct Task<T> {
|
||||
future: Pin<Box<dyn Future<Output = Result<T, crate::Error>> + Send + 'static>>,
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> Task<T> {
|
||||
#[must_use]
|
||||
pub fn ready(value: T) -> Self {
|
||||
Self::from_future(async move { Ok(value) })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn failed(error: crate::Error) -> Self {
|
||||
Self::from_future(async move { Err(error) })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_future(
|
||||
future: impl Future<Output = Result<T, crate::Error>> + Send + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
future: Box::pin(future),
|
||||
}
|
||||
}
|
||||
|
||||
/// Drives the task on the current thread until it completes or times out.
|
||||
///
|
||||
/// `Ok(None)` denotes a timeout; task failures remain typed errors.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns the typed error produced by the task.
|
||||
pub fn wait_timeout(mut self, timeout: Duration) -> Result<Option<T>, crate::Error> {
|
||||
let deadline = Instant::now().checked_add(timeout);
|
||||
let wake = Arc::new(CurrentThreadWake(std::thread::current()));
|
||||
let waker = Waker::from(wake);
|
||||
let mut context = Context::from_waker(&waker);
|
||||
loop {
|
||||
if let Poll::Ready(result) = self.future.as_mut().poll(&mut context) {
|
||||
return result.map(Some);
|
||||
}
|
||||
let Some(deadline) = deadline else {
|
||||
return Ok(None);
|
||||
};
|
||||
let now = Instant::now();
|
||||
if now >= deadline {
|
||||
return Ok(None);
|
||||
}
|
||||
std::thread::park_timeout(deadline.saturating_duration_since(now));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for Task<T> {
|
||||
type Output = Result<T, crate::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.future.as_mut().poll(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Close for CancellationTokenSource {
|
||||
fn close(&mut self) -> Result<(), ExternalError> {
|
||||
self.cancel();
|
||||
self.0
|
||||
.linked_registrations
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clear();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskCompletionState<T> {
|
||||
next_id: AtomicU64,
|
||||
@@ -711,9 +970,14 @@ struct TaskCompletionState<T> {
|
||||
}
|
||||
|
||||
/// Cloneable completion source used by mapped task-returning APIs.
|
||||
#[derive(Clone)]
|
||||
pub struct TaskCompletionSource<T>(Arc<TaskCompletionState<T>>);
|
||||
|
||||
impl<T> Clone for TaskCompletionSource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(Arc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TaskCompletionSource<T> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
@@ -804,7 +1068,7 @@ pub struct TaskCompletionFuture<T> {
|
||||
waiter_id: Option<u64>,
|
||||
}
|
||||
|
||||
impl<T: Clone> Future for TaskCompletionFuture<T> {
|
||||
impl<T> Future for TaskCompletionFuture<T> {
|
||||
type Output = Result<T, crate::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
@@ -813,7 +1077,7 @@ impl<T: Clone> Future for TaskCompletionFuture<T> {
|
||||
.result
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
.take()
|
||||
{
|
||||
return Poll::Ready(result);
|
||||
}
|
||||
@@ -832,7 +1096,7 @@ impl<T: Clone> Future for TaskCompletionFuture<T> {
|
||||
.result
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
.take();
|
||||
if let Some(result) = ready {
|
||||
self.state
|
||||
.waiters
|
||||
@@ -973,7 +1237,92 @@ where
|
||||
|
||||
pub struct IEnumerator;
|
||||
|
||||
pub struct WaitHandle;
|
||||
struct WaitHandleState {
|
||||
signalled: Mutex<bool>,
|
||||
changed: Condvar,
|
||||
}
|
||||
|
||||
/// Cloneable, cross-platform manual-reset wait handle.
|
||||
#[derive(Clone)]
|
||||
pub struct WaitHandle(Arc<WaitHandleState>);
|
||||
|
||||
impl WaitHandle {
|
||||
#[must_use]
|
||||
pub fn new(signalled: bool) -> Self {
|
||||
Self(Arc::new(WaitHandleState {
|
||||
signalled: Mutex::new(signalled),
|
||||
changed: Condvar::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn set(&self) {
|
||||
*self
|
||||
.0
|
||||
.signalled
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = true;
|
||||
self.0.changed.notify_all();
|
||||
}
|
||||
|
||||
pub fn reset(&self) {
|
||||
*self
|
||||
.0
|
||||
.signalled
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = false;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_signalled(&self) -> bool {
|
||||
*self
|
||||
.0
|
||||
.signalled
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn wait_timeout(&self, timeout: Option<Duration>) -> bool {
|
||||
let signalled = self
|
||||
.0
|
||||
.signalled
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if *signalled {
|
||||
return true;
|
||||
}
|
||||
match timeout {
|
||||
Some(timeout) => {
|
||||
let (signalled, _) = self
|
||||
.0
|
||||
.changed
|
||||
.wait_timeout_while(signalled, timeout, |signalled| !*signalled)
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
*signalled
|
||||
}
|
||||
None => *self
|
||||
.0
|
||||
.changed
|
||||
.wait_while(signalled, |signalled| !*signalled)
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WaitHandle {
|
||||
fn default() -> Self {
|
||||
Self::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for WaitHandle {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter
|
||||
.debug_struct("WaitHandle")
|
||||
.field("signalled", &self.is_signalled())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FrozenDictionary<TKey, TValue>(pub std::collections::HashMap<TKey, TValue>);
|
||||
@@ -1001,7 +1350,54 @@ pub struct TypeId(pub &'static str);
|
||||
|
||||
pub struct Span<T>(pub PhantomData<fn(T)>);
|
||||
|
||||
pub struct RandomSource;
|
||||
/// Thread-safe pseudo-random source for mapped `System.Random` parameters.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RandomSource(Arc<Mutex<u64>>);
|
||||
|
||||
impl RandomSource {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
let mut bytes = [0_u8; 8];
|
||||
if getrandom::fill(&mut bytes).is_err() {
|
||||
bytes = 0x9e37_79b9_7f4a_7c15_u64.to_le_bytes();
|
||||
}
|
||||
Self(Arc::new(Mutex::new(u64::from_le_bytes(bytes))))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_seed(seed: i32) -> Self {
|
||||
let seed = u64::from(seed.unsigned_abs()).wrapping_add(0x9e37_79b9_7f4a_7c15);
|
||||
Self(Arc::new(Mutex::new(seed)))
|
||||
}
|
||||
|
||||
/// Returns a pseudo-random value below `exclusive_max`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an argument error when the exclusive maximum is not positive.
|
||||
pub fn next(&self, exclusive_max: i32) -> Result<i32, crate::Error> {
|
||||
let maximum = u64::try_from(exclusive_max).map_err(|_| crate::Error::Argument)?;
|
||||
if maximum == 0 {
|
||||
return Err(crate::Error::Argument);
|
||||
}
|
||||
let mut state = self
|
||||
.0
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let mut value = *state;
|
||||
value ^= value << 13;
|
||||
value ^= value >> 7;
|
||||
value ^= value << 17;
|
||||
*state = value;
|
||||
i32::try_from(value % maximum).map_err(|_| crate::Error::IndexOutOfRange)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RandomSource {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
struct AsyncEnumerableState<T> {
|
||||
values: std::collections::VecDeque<T>,
|
||||
|
||||
Reference in New Issue
Block a user