Compare commits

..

20 commits
v0.9.1 ... main

Author SHA1 Message Date
Filip Tibell
df56cd58e7
Github does not like requests from github to github, remove it from https test 2025-05-02 22:33:28 +02:00
Filip Tibell
66e3b58cd7
Url and uri are not the same 2025-05-02 21:57:13 +02:00
Filip Tibell
fb33d1812d
Remove old unused app data 2025-05-02 12:31:58 +02:00
Filip Tibell
0ddaaaefb5
Update changelog 2025-05-02 12:29:46 +02:00
Filip Tibell
2e5b3bb5eb
Fix panicking during require because of long lived require context borrow 2025-05-02 12:27:20 +02:00
Filip Tibell
6645631c46
Properly store process args and env as part of runtime initialization instead of in std-process 2025-05-02 12:18:53 +02:00
Filip Tibell
120048ae95
Update changelog 2025-05-01 21:12:54 +02:00
Filip Tibell
2d8e58b028
Revamp handling of process args and env with fully featured newtypes 2025-05-01 21:08:58 +02:00
Filip Tibell
b1fc60023d
Add release date to changelog 2025-04-30 15:40:39 +02:00
Filip Tibell
1429450a64
Version 0.9.2 2025-04-30 15:40:08 +02:00
Filip Tibell
d2a89f41c8
Fixed https support in net client + update changelog 2025-04-30 15:28:29 +02:00
Filip Tibell
9c9b90d70d
Final optimizations and dependency cleanup for mlua-luau-scheduler 2025-04-30 14:54:10 +02:00
Filip Tibell
d425d2568a
Final optimizations for future and thread queues 2025-04-30 14:47:16 +02:00
Filip Tibell
4c2bbcf425
Optimize thread queue storage for spawned and deferred threads 2025-04-30 14:21:48 +02:00
Filip Tibell
461ca24c33
Implement optimized event listener for thread queues 2025-04-30 14:10:35 +02:00
Filip Tibell
7fd390dead
Organize queue-related files a bit better 2025-04-30 13:53:43 +02:00
Filip Tibell
c35eaa7899
Organize thread-related files a bit better 2025-04-30 13:39:35 +02:00
Filip Tibell
b57fa6fad3
Optimize tracking of thread results in mlua-luau-scheduler 2025-04-30 13:35:42 +02:00
Filip Tibell
3e80a0a1c4
Reduce overhead of lua result tracking 2025-04-29 23:31:10 +02:00
Filip Tibell
ac8c809a20
Implement zero-copy hyper body type that wraps over lua values 2025-04-29 23:00:03 +02:00
63 changed files with 1744 additions and 660 deletions

View file

@ -8,6 +8,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added support for non-UTF8 strings in arguments to `process.exec` and `process.spawn`
### Changed
- Improved cross-platform compatibility and correctness for values in `process.args` and `process.env`, especially on Windows
### Fixed
- Fixed various crashes during require that had the error `cannot mutably borrow app data container`
## `0.9.2` - April 30th, 2025
### Changed
- Improved performance of `net.request` and `net.serve` when handling large request bodies
- Improved performance and memory usage of `task.spawn`, `task.defer`, and `task.delay`
### Fixed
- Fixed accidental breakage of `net.request` in version `0.9.1`
## `0.9.1` - April 29th, 2025
### Added

36
Cargo.lock generated
View file

@ -1655,7 +1655,7 @@ dependencies = [
[[package]]
name = "lune"
version = "0.9.1"
version = "0.9.2"
dependencies = [
"anyhow",
"async-fs",
@ -1670,6 +1670,7 @@ dependencies = [
"lune-utils",
"mlua",
"mlua-luau-scheduler",
"rustls",
"rustyline",
"serde",
"serde_json",
@ -1682,7 +1683,7 @@ dependencies = [
[[package]]
name = "lune-roblox"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"glam",
"lune-utils",
@ -1698,7 +1699,7 @@ dependencies = [
[[package]]
name = "lune-std"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-channel",
"async-fs",
@ -1722,7 +1723,7 @@ dependencies = [
[[package]]
name = "lune-std-datetime"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"chrono",
"chrono_lc",
@ -1733,7 +1734,7 @@ dependencies = [
[[package]]
name = "lune-std-fs"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-fs",
"bstr",
@ -1745,7 +1746,7 @@ dependencies = [
[[package]]
name = "lune-std-luau"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"lune-utils",
"mlua",
@ -1753,7 +1754,7 @@ dependencies = [
[[package]]
name = "lune-std-net"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-channel",
"async-executor",
@ -1763,6 +1764,7 @@ dependencies = [
"async-tungstenite",
"blocking",
"bstr",
"form_urlencoded",
"futures",
"futures-lite",
"futures-rustls",
@ -1783,7 +1785,7 @@ dependencies = [
[[package]]
name = "lune-std-process"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-channel",
"async-lock",
@ -1797,13 +1799,12 @@ dependencies = [
"lune-utils",
"mlua",
"mlua-luau-scheduler",
"os_str_bytes",
"pin-project",
]
[[package]]
name = "lune-std-regex"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"lune-utils",
"mlua",
@ -1813,7 +1814,7 @@ dependencies = [
[[package]]
name = "lune-std-roblox"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"lune-roblox",
"lune-utils",
@ -1825,7 +1826,7 @@ dependencies = [
[[package]]
name = "lune-std-serde"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-compression",
"blake3",
@ -1849,7 +1850,7 @@ dependencies = [
[[package]]
name = "lune-std-stdio"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-io",
"async-lock",
@ -1863,7 +1864,7 @@ dependencies = [
[[package]]
name = "lune-std-task"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"async-io",
"futures-lite",
@ -1874,11 +1875,12 @@ dependencies = [
[[package]]
name = "lune-utils"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"console",
"dunce",
"mlua",
"os_str_bytes",
"parking_lot",
"path-clean",
"pathdiff",
@ -1986,14 +1988,12 @@ dependencies = [
[[package]]
name = "mlua-luau-scheduler"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"async-executor",
"async-fs",
"async-io",
"blocking",
"concurrent-queue",
"event-listener",
"futures-lite",
"mlua",
"rustc-hash 2.1.1",

View file

@ -1,6 +1,6 @@
[package]
name = "lune-roblox"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -25,4 +25,4 @@ rbx_reflection = "5.0"
rbx_reflection_database = "1.0"
rbx_xml = "1.0"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-datetime"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -19,4 +19,4 @@ thiserror = "2.0"
chrono = "0.4.38"
chrono_lc = "0.1.6"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-fs"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -19,5 +19,5 @@ async-fs = "2.1"
bstr = "1.9"
futures-lite = "2.6"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-std-datetime = { version = "0.2.1", path = "../lune-std-datetime" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-std-datetime = { version = "0.2.2", path = "../lune-std-datetime" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-luau"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -15,4 +15,4 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau", "luau-jit"] }
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-net"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -14,7 +14,7 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
async-channel = "2.3"
async-executor = "1.13"
@ -24,6 +24,7 @@ async-net = "2.0"
async-tungstenite = "0.29"
blocking = "1.6"
bstr = "1.9"
form_urlencoded = "1.2"
futures = { version = "0.3", default-features = false, features = ["std"] }
futures-lite = "2.6"
futures-rustls = "0.26"
@ -37,5 +38,5 @@ urlencoding = "2.1"
webpki = "0.22"
webpki-roots = "0.26"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-std-serde = { version = "0.2.1", path = "../lune-std-serde" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-std-serde = { version = "0.2.2", path = "../lune-std-serde" }

View file

@ -0,0 +1,59 @@
use hyper::body::{Buf, Bytes};
use super::inner::ReadableBodyInner;
/**
The cursor keeping track of inner data and its position for a readable body.
*/
#[derive(Debug, Clone)]
pub struct ReadableBodyCursor {
inner: ReadableBodyInner,
start: usize,
}
impl ReadableBodyCursor {
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn as_slice(&self) -> &[u8] {
&self.inner.as_slice()[self.start..]
}
pub fn advance(&mut self, cnt: usize) {
self.start += cnt;
if self.start > self.inner.len() {
self.start = self.inner.len();
}
}
pub fn into_bytes(self) -> Bytes {
self.inner.into_bytes()
}
}
impl Buf for ReadableBodyCursor {
fn remaining(&self) -> usize {
self.len().saturating_sub(self.start)
}
fn chunk(&self) -> &[u8] {
self.as_slice()
}
fn advance(&mut self, cnt: usize) {
self.advance(cnt);
}
}
impl<T> From<T> for ReadableBodyCursor
where
T: Into<ReadableBodyInner>,
{
fn from(value: T) -> Self {
Self {
inner: value.into(),
start: 0,
}
}
}

View file

@ -1,4 +1,4 @@
use http_body_util::{BodyExt, Full};
use http_body_util::BodyExt;
use hyper::{
body::{Bytes, Incoming},
header::CONTENT_ENCODING,
@ -33,11 +33,3 @@ pub async fn handle_incoming_body(
Ok((body, was_decompressed))
}
pub fn bytes_to_full(bytes: Bytes) -> Full<Bytes> {
if bytes.is_empty() {
Full::default()
} else {
Full::new(bytes)
}
}

View file

@ -0,0 +1,110 @@
use hyper::body::{Buf as _, Bytes};
use mlua::{prelude::*, Buffer as LuaBuffer};
/**
The inner data for a readable body.
*/
#[derive(Debug, Clone)]
pub enum ReadableBodyInner {
Bytes(Bytes),
String(String),
LuaString(LuaString),
LuaBuffer(LuaBuffer),
}
impl ReadableBodyInner {
pub fn len(&self) -> usize {
match self {
Self::Bytes(b) => b.len(),
Self::String(s) => s.len(),
Self::LuaString(s) => s.as_bytes().len(),
Self::LuaBuffer(b) => b.len(),
}
}
pub fn as_slice(&self) -> &[u8] {
/*
SAFETY: Reading lua strings and lua buffers as raw slices is safe while we can
guarantee that the inner Lua value + main lua struct has not yet been dropped
1. Buffers are fixed-size and guaranteed to never resize
2. We do not expose any method for writing to the body, only reading
3. We guarantee that net.request and net.serve futures are only driven forward
while we also know that the Lua + scheduler pair have not yet been dropped
4. Any writes from within lua to a buffer, are considered user error,
and are not unsafe, since the only possible outcome with the above
guarantees is invalid / mangled contents in request / response bodies
*/
match self {
Self::Bytes(b) => b.chunk(),
Self::String(s) => s.as_bytes(),
Self::LuaString(s) => unsafe {
// BorrowedBytes would not let us return a plain slice here,
// which is what the Buf implementation below needs - we need to
// do a little hack here to re-create the slice without a lifetime
let b = s.as_bytes();
let ptr = b.as_ptr();
let len = b.len();
std::slice::from_raw_parts(ptr, len)
},
Self::LuaBuffer(b) => unsafe {
// Similar to above, we need to get the raw slice for the buffer,
// which is a bit trickier here because Buffer has a read + write
// interface instead of using slices for some unknown reason
let v = LuaValue::Buffer(b.clone());
let ptr = v.to_pointer().cast::<u8>();
let len = b.len();
std::slice::from_raw_parts(ptr, len)
},
}
}
pub fn into_bytes(self) -> Bytes {
match self {
Self::Bytes(b) => b,
Self::String(s) => Bytes::from(s),
Self::LuaString(s) => Bytes::from(s.as_bytes().to_vec()),
Self::LuaBuffer(b) => Bytes::from(b.to_vec()),
}
}
}
impl From<&'static str> for ReadableBodyInner {
fn from(value: &'static str) -> Self {
Self::Bytes(Bytes::from(value))
}
}
impl From<Vec<u8>> for ReadableBodyInner {
fn from(value: Vec<u8>) -> Self {
Self::Bytes(Bytes::from(value))
}
}
impl From<Bytes> for ReadableBodyInner {
fn from(value: Bytes) -> Self {
Self::Bytes(value)
}
}
impl From<String> for ReadableBodyInner {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<LuaString> for ReadableBodyInner {
fn from(value: LuaString) -> Self {
Self::LuaString(value)
}
}
impl From<LuaBuffer> for ReadableBodyInner {
fn from(value: LuaBuffer) -> Self {
Self::LuaBuffer(value)
}
}

View file

@ -0,0 +1,11 @@
#![allow(unused_imports)]
mod cursor;
mod incoming;
mod inner;
mod readable;
pub use self::cursor::ReadableBodyCursor;
pub use self::incoming::handle_incoming_body;
pub use self::inner::ReadableBodyInner;
pub use self::readable::ReadableBody;

View file

@ -0,0 +1,105 @@
use std::convert::Infallible;
use std::pin::Pin;
use std::task::{Context, Poll};
use hyper::body::{Body, Bytes, Frame, SizeHint};
use mlua::prelude::*;
use super::cursor::ReadableBodyCursor;
/**
Zero-copy wrapper for a readable body.
Provides methods to read bytes that can be safely used if, and only
if, the respective Lua struct for the body has not yet been dropped.
If the body was created from a `Vec<u8>`, `Bytes`, or a `String`, reading
bytes is always safe and does not go through any additional indirections.
*/
#[derive(Debug, Clone)]
pub struct ReadableBody {
cursor: Option<ReadableBodyCursor>,
}
impl ReadableBody {
pub const fn empty() -> Self {
Self { cursor: None }
}
pub fn as_slice(&self) -> &[u8] {
match self.cursor.as_ref() {
Some(cursor) => cursor.as_slice(),
None => &[],
}
}
pub fn into_bytes(self) -> Bytes {
match self.cursor {
Some(cursor) => cursor.into_bytes(),
None => Bytes::new(),
}
}
}
impl Body for ReadableBody {
type Data = ReadableBodyCursor;
type Error = Infallible;
fn poll_frame(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
Poll::Ready(self.cursor.take().map(|d| Ok(Frame::data(d))))
}
fn is_end_stream(&self) -> bool {
self.cursor.is_none()
}
fn size_hint(&self) -> SizeHint {
self.cursor.as_ref().map_or_else(
|| SizeHint::with_exact(0),
|c| SizeHint::with_exact(c.len() as u64),
)
}
}
impl<T> From<T> for ReadableBody
where
T: Into<ReadableBodyCursor>,
{
fn from(value: T) -> Self {
Self {
cursor: Some(value.into()),
}
}
}
impl<T> From<Option<T>> for ReadableBody
where
T: Into<ReadableBodyCursor>,
{
fn from(value: Option<T>) -> Self {
Self {
cursor: value.map(Into::into),
}
}
}
impl FromLua for ReadableBody {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
match value {
LuaValue::Nil => Ok(Self::empty()),
LuaValue::String(str) => Ok(Self::from(str)),
LuaValue::Buffer(buf) => Ok(Self::from(buf)),
v => Err(LuaError::FromLuaConversionError {
from: v.type_name(),
to: "Body".to_string(),
message: Some(format!(
"Invalid body - expected string or buffer, got {}",
v.type_name()
)),
}),
}
}
}

View file

@ -1,5 +1,6 @@
use http_body_util::Full;
use hyper::{
body::{Bytes, Incoming},
body::Incoming,
client::conn::http1::handshake,
header::{HeaderValue, ACCEPT, CONTENT_LENGTH, HOST, LOCATION, USER_AGENT},
Method, Request as HyperRequest, Response as HyperResponse, Uri,
@ -9,6 +10,7 @@ use mlua::prelude::*;
use url::Url;
use crate::{
body::ReadableBody,
client::{http_stream::HttpStream, ws_stream::WsStream},
shared::{
headers::create_user_agent_header,
@ -45,7 +47,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
.uri()
.to_string()
.parse::<Url>()
.expect("uri is valid");
.into_lua_err()?;
// Some headers are required by most if not
// all servers, make sure those are present...
@ -61,7 +63,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
request.inner.headers_mut().insert(USER_AGENT, ua);
}
if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {
let len = request.inner.body().len().to_string();
let len = request.body().len().to_string();
let len = HeaderValue::from_str(&len).into_lua_err()?;
request.inner.headers_mut().insert(CONTENT_LENGTH, len);
}
@ -78,18 +80,19 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
HyperExecutor::execute(lua.clone(), conn);
let incoming = sender
.send_request(request.as_full())
.await
.into_lua_err()?;
let (parts, body) = request.clone_inner().into_parts();
let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));
let incoming = sender.send_request(data).await.into_lua_err()?;
if let Some((new_method, new_uri)) = check_redirect(&request.inner, &incoming) {
if let Some((new_method, new_uri)) =
check_redirect(request.inner.method().clone(), &incoming)
{
if request.redirects.is_some_and(|r| r >= MAX_REDIRECTS) {
return Err(LuaError::external("Too many redirects"));
}
if new_method == Method::GET {
*request.inner.body_mut() = Bytes::new();
*request.inner.body_mut() = ReadableBody::empty();
}
*request.inner.method_mut() = new_method;
@ -104,10 +107,7 @@ pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response>
}
}
fn check_redirect(
request: &HyperRequest<Bytes>,
response: &HyperResponse<Incoming>,
) -> Option<(Method, Uri)> {
fn check_redirect(method: Method, response: &HyperResponse<Incoming>) -> Option<(Method, Uri)> {
if !response.status().is_redirection() {
return None;
}
@ -118,7 +118,7 @@ fn check_redirect(
let method = match response.status().as_u16() {
301..=303 => Method::GET,
_ => request.method().clone(),
_ => method,
};
Some((method, location))

View file

@ -1,8 +1,22 @@
use std::sync::{Arc, LazyLock};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, LazyLock,
};
use rustls::ClientConfig;
use rustls::{crypto::ring, ClientConfig};
static PROVIDER_INITIALIZED: AtomicBool = AtomicBool::new(false);
pub fn initialize_provider() {
if !PROVIDER_INITIALIZED.load(Ordering::Relaxed) {
PROVIDER_INITIALIZED.store(true, Ordering::Relaxed);
// Only errors if already installed, which is fine
ring::default_provider().install_default().ok();
}
}
pub static CLIENT_CONFIG: LazyLock<Arc<ClientConfig>> = LazyLock::new(|| {
initialize_provider();
rustls::ClientConfig::builder()
.with_root_certificates(rustls::RootCertStore {
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),

View file

@ -3,6 +3,7 @@
use lune_utils::TableBuilder;
use mlua::prelude::*;
pub(crate) mod body;
pub(crate) mod client;
pub(crate) mod server;
pub(crate) mod shared;
@ -32,6 +33,8 @@ pub fn typedefs() -> String {
Errors when out of memory.
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
// No initial rustls setup is necessary, the respective
// functions lazily initialize anything there as needed
TableBuilder::new(lua)?
.with_async_function("request", net_request)?
.with_async_function("socket", net_socket)?

View file

@ -1,17 +1,16 @@
use std::{future::Future, net::SocketAddr, pin::Pin};
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
use http_body_util::Full;
use hyper::{
body::{Bytes, Incoming},
service::Service as HyperService,
Request as HyperRequest, Response as HyperResponse, StatusCode,
body::Incoming, service::Service as HyperService, Request as HyperRequest,
Response as HyperResponse, StatusCode,
};
use mlua::prelude::*;
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
use crate::{
body::ReadableBody,
server::{
config::ServeConfig,
upgrade::{is_upgrade_request, make_upgrade_response},
@ -27,7 +26,7 @@ pub(super) struct Service {
}
impl HyperService<HyperRequest<Incoming>> for Service {
type Response = HyperResponse<Full<Bytes>>;
type Response = HyperResponse<ReadableBody>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
@ -41,7 +40,7 @@ impl HyperService<HyperRequest<Incoming>> for Service {
Err(err) => {
return Ok(HyperResponse::builder()
.status(StatusCode::BAD_REQUEST)
.body(Full::new(Bytes::from(err.to_string())))
.body(ReadableBody::from(err.to_string()))
.unwrap())
}
};
@ -70,7 +69,7 @@ impl HyperService<HyperRequest<Incoming>> for Service {
// TODO: Propagate the error somehow?
Ok(HyperResponse::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Full::new(Bytes::from("Lune: Internal server error")))
.body(ReadableBody::from("Lune: Internal server error"))
.unwrap())
}
}
@ -83,7 +82,7 @@ async fn handle_request(
handler: LuaFunction,
request: HyperRequest<Incoming>,
address: SocketAddr,
) -> LuaResult<HyperResponse<Full<Bytes>>> {
) -> LuaResult<HyperResponse<ReadableBody>> {
let request = Request::from_incoming(request, true)
.await?
.with_address(address);
@ -97,7 +96,7 @@ async fn handle_request(
.expect("Missing handler thread result")?;
let response = Response::from_lua_multi(thread_res, &lua)?;
Ok(response.into_full())
Ok(response.into_inner())
}
async fn handle_websocket(

View file

@ -1,12 +1,13 @@
use async_tungstenite::tungstenite::{error::ProtocolError, handshake::derive_accept_key};
use http_body_util::Full;
use hyper::{
body::{Bytes, Incoming},
body::Incoming,
header::{HeaderName, CONNECTION, UPGRADE},
HeaderMap, Request as HyperRequest, Response as HyperResponse, StatusCode,
};
use crate::body::ReadableBody;
const SEC_WEBSOCKET_VERSION: HeaderName = HeaderName::from_static("sec-websocket-version");
const SEC_WEBSOCKET_KEY: HeaderName = HeaderName::from_static("sec-websocket-key");
const SEC_WEBSOCKET_ACCEPT: HeaderName = HeaderName::from_static("sec-websocket-accept");
@ -31,7 +32,7 @@ pub fn is_upgrade_request(request: &HyperRequest<Incoming>) -> bool {
pub fn make_upgrade_response(
request: &HyperRequest<Incoming>,
) -> Result<HyperResponse<Full<Bytes>>, ProtocolError> {
) -> Result<HyperResponse<ReadableBody>, ProtocolError> {
let key = request
.headers()
.get(SEC_WEBSOCKET_KEY)
@ -50,6 +51,6 @@ pub fn make_upgrade_response(
.header(CONNECTION, "upgrade")
.header(UPGRADE, "websocket")
.header(SEC_WEBSOCKET_ACCEPT, derive_accept_key(key.as_bytes()))
.body(Full::new(Bytes::from("switching to websocket protocol")))
.body(ReadableBody::from("switching to websocket protocol"))
.unwrap())
}

View file

@ -1,25 +1,9 @@
use hyper::{
body::Bytes,
header::{HeaderName, HeaderValue},
HeaderMap, Method,
};
use mlua::prelude::*;
pub fn lua_value_to_bytes(value: &LuaValue) -> LuaResult<Bytes> {
match value {
LuaValue::Nil => Ok(Bytes::new()),
LuaValue::Buffer(buf) => Ok(Bytes::from(buf.to_vec())),
LuaValue::String(str) => Ok(Bytes::copy_from_slice(&str.as_bytes())),
v => Err(LuaError::FromLuaConversionError {
from: v.type_name(),
to: "Bytes".to_string(),
message: Some(format!(
"Invalid body - expected string or buffer, got {}",
v.type_name()
)),
}),
}
}
use mlua::prelude::*;
pub fn lua_value_to_method(value: &LuaValue) -> LuaResult<Method> {
match value {

View file

@ -1,4 +1,3 @@
pub mod body;
pub mod futures;
pub mod headers;
pub mod hyper;

View file

@ -1,19 +1,17 @@
use std::{collections::HashMap, net::SocketAddr};
use http_body_util::Full;
use url::Url;
use hyper::{
body::{Bytes, Incoming},
HeaderMap, Method, Request as HyperRequest,
};
use hyper::{body::Incoming, HeaderMap, Method, Request as HyperRequest};
use mlua::prelude::*;
use crate::shared::{
body::{bytes_to_full, handle_incoming_body},
headers::{hash_map_to_table, header_map_to_table},
lua::{lua_table_to_header_map, lua_value_to_bytes, lua_value_to_method},
use crate::{
body::{handle_incoming_body, ReadableBody},
shared::{
headers::{hash_map_to_table, header_map_to_table},
lua::{lua_table_to_header_map, lua_value_to_method},
},
};
#[derive(Debug, Clone)]
@ -57,9 +55,7 @@ impl FromLua for RequestOptions {
#[derive(Debug, Clone)]
pub struct Request {
// NOTE: We use Bytes instead of Full<Bytes> to avoid
// needing async when getting a reference to the body
pub(crate) inner: HyperRequest<Bytes>,
pub(crate) inner: HyperRequest<ReadableBody>,
pub(crate) address: Option<SocketAddr>,
pub(crate) redirects: Option<usize>,
pub(crate) decompress: bool,
@ -78,7 +74,7 @@ impl Request {
let (body, decompress) = handle_incoming_body(&parts.headers, body, decompress).await?;
Ok(Self {
inner: HyperRequest::from_parts(parts, body),
inner: HyperRequest::from_parts(parts, ReadableBody::from(body)),
address: None,
redirects: None,
decompress,
@ -114,15 +110,18 @@ impl Request {
*/
pub fn query(&self) -> HashMap<String, Vec<String>> {
let uri = self.inner.uri();
let url = uri.to_string().parse::<Url>().expect("uri is valid");
let mut result = HashMap::<String, Vec<String>>::new();
for (key, value) in url.query_pairs() {
result
.entry(key.into_owned())
.or_default()
.push(value.into_owned());
if let Some(query) = uri.query() {
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
result
.entry(key.to_string())
.or_default()
.push(value.to_string());
}
}
result
}
@ -137,37 +136,23 @@ impl Request {
Returns the body of the request.
*/
pub fn body(&self) -> &[u8] {
self.inner.body()
self.inner.body().as_slice()
}
/**
Clones the inner `hyper` request with its body
type modified to `Full<Bytes>` for sending.
Clones the inner `hyper` request.
*/
#[allow(dead_code)]
pub fn as_full(&self) -> HyperRequest<Full<Bytes>> {
let mut builder = HyperRequest::builder()
.version(self.inner.version())
.method(self.inner.method())
.uri(self.inner.uri());
builder
.headers_mut()
.expect("request was valid")
.extend(self.inner.headers().clone());
let body = bytes_to_full(self.inner.body().clone());
builder.body(body).expect("request was valid")
pub fn clone_inner(&self) -> HyperRequest<ReadableBody> {
self.inner.clone()
}
/**
Takes the inner `hyper` request with its body
type modified to `Full<Bytes>` for sending.
Takes the inner `hyper` request by ownership.
*/
#[allow(dead_code)]
pub fn into_full(self) -> HyperRequest<Full<Bytes>> {
let (parts, body) = self.inner.into_parts();
HyperRequest::from_parts(parts, bytes_to_full(body))
pub fn into_inner(self) -> HyperRequest<ReadableBody> {
self.inner
}
}
@ -179,7 +164,7 @@ impl FromLua for Request {
let uri = s.to_str()?;
let uri = uri.parse().into_lua_err()?;
let mut request = HyperRequest::new(Bytes::new());
let mut request = HyperRequest::new(ReadableBody::empty());
*request.uri_mut() = uri;
Ok(Self {
@ -221,8 +206,7 @@ impl FromLua for Request {
.unwrap_or_default();
// Extract body
let body = tab.get::<LuaValue>("body")?;
let body = lua_value_to_bytes(&body)?;
let body = tab.get::<ReadableBody>("body")?;
// Build the full request
let mut request = HyperRequest::new(body);

View file

@ -1,24 +1,19 @@
use http_body_util::Full;
use hyper::{
body::{Bytes, Incoming},
body::Incoming,
header::{HeaderValue, CONTENT_TYPE},
HeaderMap, Response as HyperResponse, StatusCode,
};
use mlua::prelude::*;
use crate::shared::{
body::{bytes_to_full, handle_incoming_body},
headers::header_map_to_table,
lua::{lua_table_to_header_map, lua_value_to_bytes},
use crate::{
body::{handle_incoming_body, ReadableBody},
shared::{headers::header_map_to_table, lua::lua_table_to_header_map},
};
#[derive(Debug, Clone)]
pub struct Response {
// NOTE: We use Bytes instead of Full<Bytes> to avoid
// needing async when getting a reference to the body
pub(crate) inner: HyperResponse<Bytes>,
pub(crate) inner: HyperResponse<ReadableBody>,
pub(crate) decompressed: bool,
}
@ -35,7 +30,7 @@ impl Response {
let (body, decompressed) = handle_incoming_body(&parts.headers, body, decompress).await?;
Ok(Self {
inner: HyperResponse::from_parts(parts, body),
inner: HyperResponse::from_parts(parts, ReadableBody::from(body)),
decompressed,
})
}
@ -72,42 +67,29 @@ impl Response {
Returns the body of the response.
*/
pub fn body(&self) -> &[u8] {
self.inner.body()
self.inner.body().as_slice()
}
/**
Clones the inner `hyper` response with its body
type modified to `Full<Bytes>` for sending.
Clones the inner `hyper` response.
*/
#[allow(dead_code)]
pub fn as_full(&self) -> HyperResponse<Full<Bytes>> {
let mut builder = HyperResponse::builder()
.version(self.inner.version())
.status(self.inner.status());
builder
.headers_mut()
.expect("request was valid")
.extend(self.inner.headers().clone());
let body = bytes_to_full(self.inner.body().clone());
builder.body(body).expect("request was valid")
pub fn clone_inner(&self) -> HyperResponse<ReadableBody> {
self.inner.clone()
}
/**
Takes the inner `hyper` response with its body
type modified to `Full<Bytes>` for sending.
Takes the inner `hyper` response by ownership.
*/
#[allow(dead_code)]
pub fn into_full(self) -> HyperResponse<Full<Bytes>> {
let (parts, body) = self.inner.into_parts();
HyperResponse::from_parts(parts, bytes_to_full(body))
pub fn into_inner(self) -> HyperResponse<ReadableBody> {
self.inner
}
}
impl FromLua for Response {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let Ok(body) = lua_value_to_bytes(&value) {
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
if let Ok(body) = ReadableBody::from_lua(value.clone(), lua) {
// String or buffer is always a 200 text/plain response
let mut response = HyperResponse::new(body);
response
@ -130,8 +112,7 @@ impl FromLua for Response {
.unwrap_or_default();
// Extract body
let body = tab.get::<LuaValue>("body")?;
let body = lua_value_to_bytes(&body)?;
let body = tab.get::<ReadableBody>("body")?;
// Build the full response
let mut response = HyperResponse::new(body);

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-process"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -14,11 +14,10 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
directories = "6.0"
pin-project = "1.0"
os_str_bytes = { version = "7.0", features = ["conversions"] }
bstr = "1.9"
bytes = "1.6.0"
@ -30,4 +29,4 @@ blocking = "1.6"
futures-lite = "2.6"
futures-util = "0.3" # Needed for select! macro...
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,10 +1,7 @@
#![allow(clippy::cargo_common_metadata)]
use std::{
env::{
self,
consts::{ARCH, OS},
},
env::consts::{ARCH, OS},
path::MAIN_SEPARATOR,
process::Stdio,
};
@ -12,9 +9,11 @@ use std::{
use mlua::prelude::*;
use mlua_luau_scheduler::Functions;
use os_str_bytes::RawOsString;
use lune_utils::{path::get_current_dir, TableBuilder};
use lune_utils::{
path::get_current_dir,
process::{ProcessArgs, ProcessEnv},
TableBuilder,
};
mod create;
mod exec;
@ -58,25 +57,15 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
"little"
})?;
// Create readonly args array
let args_vec = lua
.app_data_ref::<Vec<String>>()
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
// Extract stored userdatas for args + env, the runtime struct should always provide this
let process_args = lua
.app_data_ref::<ProcessArgs>()
.ok_or_else(|| LuaError::runtime("Missing process args in Lua app data"))?
.clone();
let process_env = lua
.app_data_ref::<ProcessEnv>()
.ok_or_else(|| LuaError::runtime("Missing process env in Lua app data"))?
.clone();
let args_tab = TableBuilder::new(lua.clone())?
.with_sequential_values(args_vec)?
.build_readonly()?;
// Create proxied table for env that gets & sets real env vars
let env_tab = TableBuilder::new(lua.clone())?
.with_metatable(
TableBuilder::new(lua.clone())?
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
.build_readonly()?,
)?
.build_readonly()?;
// Create our process exit function, the scheduler crate provides this
let fns = Functions::new(lua.clone())?;
@ -87,73 +76,18 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
.with_value("os", os)?
.with_value("arch", arch)?
.with_value("endianness", endianness)?
.with_value("args", args_tab)?
.with_value("args", process_args)?
.with_value("cwd", cwd_str)?
.with_value("env", env_tab)?
.with_value("env", process_env)?
.with_value("exit", process_exit)?
.with_async_function("exec", process_exec)?
.with_function("create", process_create)?
.build_readonly()
}
fn process_env_get(lua: &Lua, (_, key): (LuaValue, String)) -> LuaResult<LuaValue> {
match env::var_os(key) {
Some(value) => {
let raw_value = RawOsString::new(value);
Ok(LuaValue::String(
lua.create_string(raw_value.to_raw_bytes())?,
))
}
None => Ok(LuaValue::Nil),
}
}
fn process_env_set(_: &Lua, (_, key, value): (LuaValue, String, Option<String>)) -> LuaResult<()> {
// Make sure key is valid, otherwise set_var will panic
if key.is_empty() {
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
} else if key.contains('=') {
Err(LuaError::RuntimeError(
"Key must not contain the equals character '='".to_string(),
))
} else if key.contains('\0') {
Err(LuaError::RuntimeError(
"Key must not contain the NUL character".to_string(),
))
} else if let Some(value) = value {
// Make sure value is valid, otherwise set_var will panic
if value.contains('\0') {
Err(LuaError::RuntimeError(
"Value must not contain the NUL character".to_string(),
))
} else {
env::set_var(&key, &value);
Ok(())
}
} else {
env::remove_var(&key);
Ok(())
}
}
fn process_env_iter(lua: &Lua, (_, ()): (LuaValue, ())) -> LuaResult<LuaFunction> {
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
lua.create_function_mut(move |lua, (): ()| match vars.next() {
Some((key, value)) => {
let raw_key = RawOsString::new(key);
let raw_value = RawOsString::new(value);
Ok((
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
))
}
None => Ok((LuaValue::Nil, LuaValue::Nil)),
})
}
async fn process_exec(
lua: Lua,
(program, args, mut options): (String, Option<Vec<String>>, ProcessSpawnOptions),
(program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),
) -> LuaResult<LuaTable> {
let stdin = options.stdio.stdin.take();
let stdout = options.stdio.stdout;
@ -171,7 +105,7 @@ async fn process_exec(
fn process_create(
lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
(program, args, options): (String, ProcessArgs, ProcessSpawnOptions),
) -> LuaResult<LuaValue> {
let child = options
.into_command(program, args)

View file

@ -1,9 +1,11 @@
use std::{
collections::HashMap,
env::{self},
ffi::OsString,
path::PathBuf,
};
use lune_utils::process::ProcessArgs;
use mlua::prelude::*;
use async_process::Command;
@ -129,31 +131,24 @@ impl FromLua for ProcessSpawnOptions {
}
impl ProcessSpawnOptions {
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
let mut program = program.into();
pub fn into_command(self, program: impl Into<OsString>, args: ProcessArgs) -> Command {
let mut program: OsString = program.into();
let mut args = args.into_iter().collect::<Vec<_>>();
// Run a shell using the command param if wanted
let pargs = match self.shell {
None => args,
Some(shell) => {
let shell_args = match args {
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
None => vec!["-c".to_string(), program.to_string()],
};
program = shell.to_string();
Some(shell_args)
if let Some(shell) = self.shell {
let mut shell_command = program.clone();
for arg in args {
shell_command.push(" ");
shell_command.push(arg);
}
};
args = vec![OsString::from("-c"), shell_command];
program = shell.into();
}
// Create command with the wanted options
let mut cmd = match pargs {
None => Command::new(program),
Some(args) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
};
let mut cmd = Command::new(program);
cmd.args(args);
// Set dir to run in and env variables
if let Some(cwd) = self.cwd {

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-regex"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -18,4 +18,4 @@ mlua = { version = "0.10.3", features = ["luau"] }
regex = "1.10"
self_cell = "1.0"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-roblox"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -14,10 +14,10 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
rbx_cookie = { version = "0.1.4", default-features = false }
roblox_install = "1.0"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-roblox = { version = "0.2.1", path = "../lune-roblox" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-roblox = { version = "0.2.2", path = "../lune-roblox" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-serde"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -42,4 +42,4 @@ sha3 = "0.10.8"
# Check before updating it.
blake3 = { version = "=1.5.0", features = ["traits-preview"] }
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-stdio"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -14,7 +14,7 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau", "error-send"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
async-io = "2.4"
async-lock = "3.4"
@ -22,4 +22,4 @@ blocking = "1.6"
dialoguer = "0.11"
futures-lite = "2.6"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-task"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -14,9 +14,9 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
async-io = "2.4"
futures-lite = "2.6"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -39,7 +39,7 @@ task = ["dep:lune-std-task"]
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
async-channel = "2.3"
async-fs = "2.1"
@ -48,15 +48,15 @@ async-lock = "3.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-std-datetime = { optional = true, version = "0.2.1", path = "../lune-std-datetime" }
lune-std-fs = { optional = true, version = "0.2.1", path = "../lune-std-fs" }
lune-std-luau = { optional = true, version = "0.2.1", path = "../lune-std-luau" }
lune-std-net = { optional = true, version = "0.2.1", path = "../lune-std-net" }
lune-std-process = { optional = true, version = "0.2.1", path = "../lune-std-process" }
lune-std-regex = { optional = true, version = "0.2.1", path = "../lune-std-regex" }
lune-std-roblox = { optional = true, version = "0.2.1", path = "../lune-std-roblox" }
lune-std-serde = { optional = true, version = "0.2.1", path = "../lune-std-serde" }
lune-std-stdio = { optional = true, version = "0.2.1", path = "../lune-std-stdio" }
lune-std-task = { optional = true, version = "0.2.1", path = "../lune-std-task" }
lune-std-datetime = { optional = true, version = "0.2.2", path = "../lune-std-datetime" }
lune-std-fs = { optional = true, version = "0.2.2", path = "../lune-std-fs" }
lune-std-luau = { optional = true, version = "0.2.2", path = "../lune-std-luau" }
lune-std-net = { optional = true, version = "0.2.2", path = "../lune-std-net" }
lune-std-process = { optional = true, version = "0.2.2", path = "../lune-std-process" }
lune-std-regex = { optional = true, version = "0.2.2", path = "../lune-std-regex" }
lune-std-roblox = { optional = true, version = "0.2.2", path = "../lune-std-roblox" }
lune-std-serde = { optional = true, version = "0.2.2", path = "../lune-std-serde" }
lune-std-stdio = { optional = true, version = "0.2.2", path = "../lune-std-stdio" }
lune-std-task = { optional = true, version = "0.2.2", path = "../lune-std-task" }

View file

@ -35,7 +35,18 @@ pub fn create(lua: Lua) -> LuaResult<LuaValue> {
3. The lua chunk we are require-ing from
*/
let require_fn = lua.create_async_function(require)?;
let require_fn = lua.create_async_function(|lua, (source, path)| {
// NOTE: We need to make sure that the app data reference does not
// live through the entire require call, to prevent panicking from
// being unable to borrow other app data in the main body of scripts
let context = {
let context = lua
.app_data_ref::<RequireContext>()
.expect("Failed to get RequireContext from app data");
context.clone()
};
require(lua, context, source, path)
})?;
let get_source_fn = lua.create_function(move |lua, (): ()| match lua.inspect_stack(2) {
None => Err(LuaError::runtime(
"Failed to get stack info for require source",
@ -60,7 +71,12 @@ pub fn create(lua: Lua) -> LuaResult<LuaValue> {
.into_lua(&lua)
}
async fn require(lua: Lua, (source, path): (LuaString, LuaString)) -> LuaResult<LuaMultiValue> {
async fn require(
lua: Lua,
context: RequireContext,
source: LuaString,
path: LuaString,
) -> LuaResult<LuaMultiValue> {
let source = source
.to_str()
.into_lua_err()
@ -73,11 +89,6 @@ async fn require(lua: Lua, (source, path): (LuaString, LuaString)) -> LuaResult<
.context("Failed to parse require path as string")?
.to_string();
let context = lua
.app_data_ref::<RequireContext>()
.expect("Failed to get RequireContext from app data")
.clone();
if let Some(builtin_name) = path.strip_prefix("@lune/").map(str::to_ascii_lowercase) {
library::require(lua, &context, &builtin_name)
} else if let Some(self_path) = path.strip_prefix("@self/") {

View file

@ -1,6 +1,6 @@
[package]
name = "lune-utils"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -17,6 +17,7 @@ mlua = { version = "0.10.3", features = ["luau", "async"] }
console = "0.15"
dunce = "1.0"
os_str_bytes = { version = "7.0", features = ["conversions"] }
path-clean = "1.0"
pathdiff = "0.2"
parking_lot = "0.12.3"

View file

@ -4,8 +4,13 @@ mod table_builder;
mod version_string;
pub mod fmt;
pub mod jit;
pub mod path;
pub mod process;
pub use self::table_builder::TableBuilder;
pub use self::version_string::get_version_string;
// TODO: Remove this in the next major semver
pub mod jit {
pub use super::process::ProcessJitEnablement as JitEnablement;
}

View file

@ -0,0 +1,252 @@
#![allow(clippy::missing_panics_doc)]
use std::{
env::args_os,
ffi::OsString,
sync::{Arc, Mutex},
};
use mlua::prelude::*;
use os_str_bytes::OsStringBytes;
// Inner (shared) struct
#[derive(Debug, Default)]
struct ProcessArgsInner {
values: Vec<OsString>,
}
impl FromIterator<OsString> for ProcessArgsInner {
fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {
Self {
values: iter.into_iter().collect(),
}
}
}
/**
A struct that can be easily shared, stored in Lua app data,
and that also guarantees the values are valid OS strings
that can be used for process arguments.
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
Also provides convenience methods for working with the arguments
as either `OsString` or `Vec<u8>`, where using the latter implicitly
converts to an `OsString` and fails if the conversion is not possible.
*/
#[derive(Debug, Clone)]
pub struct ProcessArgs {
inner: Arc<Mutex<ProcessArgsInner>>,
}
impl ProcessArgs {
#[must_use]
pub fn empty() -> Self {
Self {
inner: Arc::new(Mutex::new(ProcessArgsInner::default())),
}
}
#[must_use]
pub fn current() -> Self {
Self {
inner: Arc::new(Mutex::new(args_os().collect())),
}
}
#[must_use]
pub fn len(&self) -> usize {
let inner = self.inner.lock().unwrap();
inner.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.values.is_empty()
}
// OS strings
#[must_use]
pub fn all(&self) -> Vec<OsString> {
let inner = self.inner.lock().unwrap();
inner.values.clone()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<OsString> {
let inner = self.inner.lock().unwrap();
inner.values.get(index).cloned()
}
pub fn set(&self, index: usize, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
if let Some(arg) = inner.values.get_mut(index) {
*arg = val.into();
}
}
pub fn push(&self, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
inner.values.push(val.into());
}
#[must_use]
pub fn pop(&self) -> Option<OsString> {
let mut inner = self.inner.lock().unwrap();
inner.values.pop()
}
pub fn insert(&self, index: usize, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
if index <= inner.values.len() {
inner.values.insert(index, val.into());
}
}
#[must_use]
pub fn remove(&self, index: usize) -> Option<OsString> {
let mut inner = self.inner.lock().unwrap();
if index < inner.values.len() {
Some(inner.values.remove(index))
} else {
None
}
}
// Bytes wrappers
#[must_use]
pub fn all_bytes(&self) -> Vec<Vec<u8>> {
self.all()
.into_iter()
.filter_map(OsString::into_io_vec)
.collect()
}
#[must_use]
pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {
let val = self.get(index)?;
val.into_io_vec()
}
pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.set(index, val_os);
}
}
pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.push(val_os);
}
}
#[must_use]
pub fn pop_bytes(&self) -> Option<Vec<u8>> {
self.pop().and_then(OsString::into_io_vec)
}
pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.insert(index, val_os);
}
}
pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {
self.remove(index).and_then(OsString::into_io_vec)
}
}
// Iterator implementations
impl IntoIterator for ProcessArgs {
type Item = OsString;
type IntoIter = std::vec::IntoIter<OsString>;
fn into_iter(self) -> Self::IntoIter {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter()
}
}
impl<S: Into<OsString>> FromIterator<S> for ProcessArgs {
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
Self {
inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),
}
}
}
impl<S: Into<OsString>> Extend<S> for ProcessArgs {
fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
let mut inner = self.inner.lock().unwrap();
inner.values.extend(iter.into_iter().map(Into::into));
}
}
// Lua implementations
impl FromLua for ProcessArgs {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::Nil = value {
Ok(Self::from_iter([] as [OsString; 0]))
} else if let LuaValue::Boolean(true) = value {
Ok(Self::current())
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
Ok(u.clone())
} else if let LuaValue::Table(arr) = value {
let mut args = Vec::new();
for pair in arr.pairs::<LuaValue, LuaValue>() {
let val_res = pair.map(|p| p.1.clone());
let val = super::lua_value_to_os_string(val_res, "ProcessArgs")?;
super::validate_os_value(&val)?;
args.push(val);
}
Ok(Self::from_iter(args))
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: String::from("ProcessArgs"),
message: Some(format!(
"Invalid type for process args - expected table or nil, got '{}'",
value.type_name()
)),
})
}
}
}
impl LuaUserData for ProcessArgs {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {
if index == 0 {
Ok(None)
} else {
Ok(this.get(index - 1))
}
});
methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {
Err::<(), _>(LuaError::runtime("ProcessArgs is read-only"))
});
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
let mut vars = this
.clone()
.into_iter()
.filter_map(OsStringBytes::into_io_vec)
.enumerate();
lua.create_function_mut(move |lua, (): ()| match vars.next() {
None => Ok((LuaValue::Nil, LuaValue::Nil)),
Some((index, value)) => Ok((
LuaValue::Integer(index as i32),
LuaValue::String(lua.create_string(value)?),
)),
})
});
}
}

View file

@ -0,0 +1,254 @@
#![allow(clippy::missing_panics_doc)]
use std::{
collections::BTreeMap,
env::vars_os,
ffi::{OsStr, OsString},
sync::{Arc, Mutex},
};
use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, OsStringBytes};
// Inner (shared) struct
#[derive(Debug, Default)]
struct ProcessEnvInner {
values: BTreeMap<OsString, OsString>,
}
impl FromIterator<(OsString, OsString)> for ProcessEnvInner {
fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {
Self {
values: iter.into_iter().collect(),
}
}
}
/**
A struct that can be easily shared, stored in Lua app data,
and that also guarantees the pairs are valid OS strings
that can be used for process environment variables.
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
Also provides convenience methods for working with the variables
as either `OsString` or `Vec<u8>`, where using the latter implicitly
converts to an `OsString` and fails if the conversion is not possible.
*/
#[derive(Debug, Clone)]
pub struct ProcessEnv {
inner: Arc<Mutex<ProcessEnvInner>>,
}
impl ProcessEnv {
#[must_use]
pub fn empty() -> Self {
Self {
inner: Arc::new(Mutex::new(ProcessEnvInner::default())),
}
}
#[must_use]
pub fn current() -> Self {
Self {
inner: Arc::new(Mutex::new(vars_os().collect())),
}
}
#[must_use]
pub fn len(&self) -> usize {
let inner = self.inner.lock().unwrap();
inner.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.values.is_empty()
}
// OS strings
#[must_use]
pub fn get_all(&self) -> Vec<(OsString, OsString)> {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter().collect()
}
#[must_use]
pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
let key = key.as_ref();
super::validate_os_key(key).ok()?;
let inner = self.inner.lock().unwrap();
inner.values.get(key).cloned()
}
pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {
let key = key.into();
let val = val.into();
if super::validate_os_pair((&key, &val)).is_err() {
return;
}
let mut inner = self.inner.lock().unwrap();
inner.values.insert(key, val);
}
pub fn remove_value(&self, key: impl AsRef<OsStr>) {
let key = key.as_ref();
if super::validate_os_key(key).is_err() {
return;
}
let mut inner = self.inner.lock().unwrap();
inner.values.remove(key);
}
// Bytes wrappers
#[must_use]
pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
self.get_all()
.into_iter()
.filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))
.collect()
}
#[must_use]
pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {
let key = OsStr::from_io_bytes(key.as_ref())?;
let val = self.get_value(key)?;
val.into_io_vec()
}
pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {
let key = OsStr::from_io_bytes(key.as_ref());
let val = OsString::from_io_vec(val.into());
if let (Some(key), Some(val)) = (key, val) {
self.set_value(key, val);
}
}
pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {
let key = OsStr::from_io_bytes(key.as_ref());
if let Some(key) = key {
self.remove_value(key);
}
}
}
// Iterator implementations
impl IntoIterator for ProcessEnv {
type Item = (OsString, OsString);
type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;
fn into_iter(self) -> Self::IntoIter {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter()
}
}
impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self {
inner: Arc::new(Mutex::new(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())
.collect(),
)),
}
}
}
impl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
let mut inner = self.inner.lock().unwrap();
inner.values.extend(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),
);
}
}
// Lua implementations
impl FromLua for ProcessEnv {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::Nil = value {
Ok(Self::from_iter([] as [(OsString, OsString); 0]))
} else if let LuaValue::Boolean(true) = value {
Ok(Self::current())
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
Ok(u.clone())
} else if let LuaValue::Table(arr) = value {
let mut args = Vec::new();
for pair in arr.pairs::<LuaValue, LuaValue>() {
let (key_res, val_res) = match pair {
Ok((key, val)) => (Ok(key), Ok(val)),
Err(err) => (Err(err.clone()), Err(err)),
};
let key = super::lua_value_to_os_string(key_res, "ProcessEnv")?;
let val = super::lua_value_to_os_string(val_res, "ProcessEnv")?;
super::validate_os_pair((&key, &val))?;
args.push((key, val));
}
Ok(Self::from_iter(args))
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: String::from("ProcessEnv"),
message: Some(format!(
"Invalid type for process env - expected table or nil, got '{}'",
value.type_name()
)),
})
}
}
}
impl LuaUserData for ProcessEnv {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
Ok(this.get_value(key))
});
methods.add_meta_method(
LuaMetaMethod::NewIndex,
|_, this, (key, val): (LuaValue, Option<LuaValue>)| {
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
if let Some(val) = val {
let val = super::lua_value_to_os_string(Ok(val), "OsString")?;
this.set_value(key, val);
} else {
this.remove_value(key);
}
Ok(())
},
);
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
let mut vars = this
.clone()
.into_iter()
.filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));
lua.create_function_mut(move |lua, (): ()| match vars.next() {
None => Ok((LuaValue::Nil, LuaValue::Nil)),
Some((key, val)) => Ok((
LuaValue::String(lua.create_string(key)?),
LuaValue::String(lua.create_string(val)?),
)),
})
});
}
}

View file

@ -1,29 +1,31 @@
#[derive(Debug, Clone, Copy, Default)]
pub struct JitEnablement(bool);
pub struct ProcessJitEnablement {
enabled: bool,
}
impl JitEnablement {
impl ProcessJitEnablement {
#[must_use]
pub fn new(enabled: bool) -> Self {
Self(enabled)
Self { enabled }
}
pub fn set_status(&mut self, enabled: bool) {
self.0 = enabled;
self.enabled = enabled;
}
#[must_use]
pub fn enabled(self) -> bool {
self.0
self.enabled
}
}
impl From<JitEnablement> for bool {
fn from(val: JitEnablement) -> Self {
impl From<ProcessJitEnablement> for bool {
fn from(val: ProcessJitEnablement) -> Self {
val.enabled()
}
}
impl From<bool> for JitEnablement {
impl From<bool> for ProcessJitEnablement {
fn from(val: bool) -> Self {
Self::new(val)
}

View file

@ -0,0 +1,78 @@
use std::ffi::{OsStr, OsString};
use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, OsStringBytes};
mod args;
mod env;
mod jit;
pub use self::args::ProcessArgs;
pub use self::env::ProcessEnv;
pub use self::jit::ProcessJitEnablement;
fn lua_value_to_os_string(res: LuaResult<LuaValue>, to: &'static str) -> LuaResult<OsString> {
let (btype, bs) = match res {
Ok(LuaValue::String(s)) => ("string", s.as_bytes().to_vec()),
Ok(LuaValue::Buffer(b)) => ("buffer", b.to_vec()),
res => {
let vtype = match res {
Ok(v) => v.type_name(),
Err(_) => "unknown",
};
return Err(LuaError::FromLuaConversionError {
from: vtype,
to: String::from(to),
message: Some(format!(
"Expected value to be a string or buffer, got '{vtype}'",
)),
});
}
};
let Some(s) = OsString::from_io_vec(bs) else {
return Err(LuaError::FromLuaConversionError {
from: btype,
to: String::from(to),
message: Some(String::from("Expected {btype} to contain valid OS bytes")),
});
};
Ok(s)
}
fn validate_os_key(key: &OsStr) -> LuaResult<()> {
let Some(key) = key.to_io_bytes() else {
return Err(LuaError::runtime("Key must be IO-safe"));
};
if key.is_empty() {
Err(LuaError::runtime("Key must not be empty"))
} else if key.contains(&b'=') {
Err(LuaError::runtime(
"Key must not contain the equals character '='",
))
} else if key.contains(&b'\0') {
Err(LuaError::runtime("Key must not contain the NUL character"))
} else {
Ok(())
}
}
fn validate_os_value(val: &OsStr) -> LuaResult<()> {
let Some(val) = val.to_io_bytes() else {
return Err(LuaError::runtime("Value must be IO-safe"));
};
if val.contains(&b'\0') {
Err(LuaError::runtime(
"Value must not contain the NUL character",
))
} else {
Ok(())
}
}
fn validate_os_pair((key, value): (&OsStr, &OsStr)) -> LuaResult<()> {
validate_os_key(key)?;
validate_os_value(value)?;
Ok(())
}

View file

@ -1,6 +1,6 @@
[package]
name = "lune"
version = "0.9.1"
version = "0.9.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -51,7 +51,7 @@ workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.1", path = "../mlua-luau-scheduler" }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
anyhow = "1.0"
console = "0.15"
@ -65,13 +65,14 @@ async-io = "2.4"
async-fs = "2.1"
blocking = "1.6"
futures-lite = "2.6"
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] }
ureq = { version = "3.0", default-features = false, features = ["rustls", "gzip"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
lune-std = { optional = true, version = "0.2.1", path = "../lune-std" }
lune-utils = { version = "0.2.1", path = "../lune-utils" }
lune-std = { optional = true, version = "0.2.2", path = "../lune-std" }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
### CLI

View file

@ -5,6 +5,7 @@ use std::{
use async_fs as fs;
use blocking::unblock;
use rustls::crypto::ring;
use crate::standalone::metadata::CURRENT_EXE;
@ -46,6 +47,8 @@ pub async fn get_or_download_base_executable(target: BuildTarget) -> BuildResult
// making sure transient errors are handled gracefully and
// with a different error message than "not found"
let (res_status, res_body) = unblock(move || {
// Only errors if already installed, which is fine
ring::default_provider().install_default().ok();
let mut res = ureq::get(release_url).call()?;
let body = res.body_mut().read_to_vec()?;
Ok::<_, BuildError>((res.status(), body))

View file

@ -1,11 +1,14 @@
#![allow(clippy::missing_panics_doc)]
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
use std::{
ffi::OsString,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use lune_utils::jit::JitEnablement;
use lune_utils::process::{ProcessArgs, ProcessEnv, ProcessJitEnablement};
use mlua::prelude::*;
use mlua_luau_scheduler::{Functions, Scheduler};
@ -58,7 +61,9 @@ impl RuntimeReturnValues {
pub struct Runtime {
lua: Lua,
sched: Scheduler,
jit: JitEnablement,
args: ProcessArgs,
env: ProcessEnv,
jit: ProcessJitEnablement,
}
impl Runtime {
@ -75,8 +80,6 @@ impl Runtime {
pub fn new() -> LuaResult<Self> {
let lua = Lua::new();
lua.set_app_data(Vec::<String>::new());
let sched = Scheduler::new(lua.clone());
let fns = Functions::new(lua.clone()).expect("has scheduler");
@ -126,21 +129,47 @@ impl Runtime {
.set(g_table.name(), g_table.create(lua.clone())?)?;
}
let jit = JitEnablement::default();
Ok(Self { lua, sched, jit })
let args = ProcessArgs::current();
let env = ProcessEnv::current();
let jit = ProcessJitEnablement::default();
Ok(Self {
lua,
sched,
args,
env,
jit,
})
}
/**
Sets arguments to give in `process.args` for Lune scripts.
By default, `std::env::args_os()` is used.
*/
#[must_use]
pub fn with_args<A, S>(self, args: A) -> Self
pub fn with_args<A, S>(mut self, args: A) -> Self
where
A: IntoIterator<Item = S>,
S: Into<String>,
S: Into<OsString>,
{
let args = args.into_iter().map(Into::into).collect::<Vec<_>>();
self.lua.set_app_data(args);
self.args = args.into_iter().map(Into::into).collect();
self
}
/**
Sets environment values to give in `process.env` for Lune scripts.
By default, `std::env::vars_os()` is used.
*/
#[must_use]
pub fn with_env<E, K, V>(mut self, env: E) -> Self
where
E: IntoIterator<Item = (K, V)>,
K: Into<OsString>,
V: Into<OsString>,
{
self.env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
self
}
@ -148,7 +177,10 @@ impl Runtime {
Enables or disables JIT compilation.
*/
#[must_use]
pub fn with_jit(mut self, jit_status: impl Into<JitEnablement>) -> Self {
pub fn with_jit<J>(mut self, jit_status: J) -> Self
where
J: Into<ProcessJitEnablement>,
{
self.jit = jit_status.into();
self
}
@ -175,8 +207,12 @@ impl Runtime {
eprintln!("{}", RuntimeError::from(e));
});
// Enable / disable the JIT as requested and store the current status as AppData
// Store the provided args, environment variables, and jit enablement as AppData
self.lua.set_app_data(self.args.clone());
self.lua.set_app_data(self.env.clone());
self.lua.set_app_data(self.jit);
// Enable / disable the JIT as requested, before loading anything
self.lua.enable_jit(self.jit.enabled());
// Load our "main" thread

View file

@ -33,14 +33,8 @@ macro_rules! create_tests {
let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
let script = read_to_string(&full_name).await?;
let mut lune = Runtime::new()?
.with_jit(true)
.with_args(
ARGS
.clone()
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
);
.with_args(ARGS.iter().cloned())
.with_jit(true);
let script_name = full_name
.trim_end_matches(".luau")
.trim_end_matches(".lua")
@ -126,6 +120,7 @@ create_tests! {
create_tests! {
net_request_codes: "net/request/codes",
net_request_compression: "net/request/compression",
net_request_https: "net/request/https",
net_request_methods: "net/request/methods",
net_request_query: "net/request/query",
net_request_redirect: "net/request/redirect",

View file

@ -1,6 +1,6 @@
[package]
name = "mlua-luau-scheduler"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -18,8 +18,6 @@ workspace = true
[dependencies]
async-executor = "1.13"
blocking = "1.6"
concurrent-queue = "2.5"
event-listener = "5.4"
futures-lite = "2.6"
rustc-hash = "2.1"
tracing = "0.1"

View file

@ -0,0 +1,5 @@
mod multi;
mod once;
pub(crate) use self::multi::MultiEvent;
pub(crate) use self::once::OnceEvent;

View file

@ -0,0 +1,91 @@
use std::{
cell::{Cell, RefCell},
future::Future,
mem,
pin::Pin,
rc::Rc,
task::{Context, Poll, Waker},
};
/**
Internal state for events.
*/
#[derive(Debug, Default)]
struct MultiEventState {
generation: Cell<u64>,
wakers: RefCell<Vec<Waker>>,
}
/**
A single-threaded event signal that can be notified multiple times.
*/
#[derive(Debug, Clone, Default)]
pub(crate) struct MultiEvent {
state: Rc<MultiEventState>,
}
impl MultiEvent {
/**
Creates a new event.
*/
pub fn new() -> Self {
Self::default()
}
/**
Notifies all waiting listeners.
*/
pub fn notify(&self) {
self.state.generation.set(self.state.generation.get() + 1);
let wakers = {
let mut wakers = self.state.wakers.borrow_mut();
mem::take(&mut *wakers)
};
for waker in wakers {
waker.wake();
}
}
/**
Creates a listener that implements `Future` and resolves when `notify` is called.
*/
pub fn listen(&self) -> MultiListener {
MultiListener {
state: self.state.clone(),
generation: self.state.generation.get(),
}
}
}
/**
A listener future that resolves when the corresponding [`QueueEvent`] is notified.
*/
#[derive(Debug)]
pub(crate) struct MultiListener {
state: Rc<MultiEventState>,
generation: u64,
}
impl Future for MultiListener {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Check if notify was called (generation is more recent)
let current = self.state.generation.get();
if current > self.generation {
self.get_mut().generation = current;
return Poll::Ready(());
}
// No notification observed yet
let mut wakers = self.state.wakers.borrow_mut();
if !wakers.iter().any(|w| w.will_wake(cx.waker())) {
wakers.push(cx.waker().clone());
}
Poll::Pending
}
}
impl Unpin for MultiListener {}

View file

@ -0,0 +1,106 @@
use std::{
cell::RefCell,
future::Future,
pin::Pin,
rc::Rc,
task::{Context, Poll, Waker},
};
/**
State which is highly optimized for a single notification event.
`Some` means not notified yet, `None` means notified.
*/
#[derive(Debug, Default)]
struct OnceEventState {
wakers: RefCell<Option<Vec<Waker>>>,
}
impl OnceEventState {
fn new() -> Self {
Self {
wakers: RefCell::new(Some(Vec::new())),
}
}
}
/**
An event that may be notified exactly once.
May be cheaply cloned.
*/
#[derive(Debug, Clone, Default)]
pub struct OnceEvent {
state: Rc<OnceEventState>,
}
impl OnceEvent {
/**
Creates a new event that can be notified exactly once.
*/
pub fn new() -> Self {
let initial_state = OnceEventState::new();
Self {
state: Rc::new(initial_state),
}
}
/**
Notifies waiting listeners.
This is idempotent; subsequent calls do nothing.
*/
pub fn notify(&self) {
if let Some(wakers) = { self.state.wakers.borrow_mut().take() } {
for waker in wakers {
waker.wake();
}
}
}
/**
Creates a listener that implements `Future` and resolves when `notify` is called.
If `notify` has already been called, the future will resolve immediately.
*/
pub fn listen(&self) -> OnceListener {
OnceListener {
state: self.state.clone(),
}
}
}
/**
A listener that resolves when the event is notified.
May be cheaply cloned.
See [`OnceEvent`] for more information.
*/
#[derive(Debug)]
pub struct OnceListener {
state: Rc<OnceEventState>,
}
impl Future for OnceListener {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut wakers_guard = self.state.wakers.borrow_mut();
match &mut *wakers_guard {
Some(wakers) => {
// Not yet notified
if !wakers.iter().any(|w| w.will_wake(cx.waker())) {
wakers.push(cx.waker().clone());
}
Poll::Pending
}
None => {
// Already notified
Poll::Ready(())
}
}
}
}
impl Unpin for OnceListener {}

View file

@ -1,24 +1,24 @@
use std::{cell::Cell, rc::Rc};
use event_listener::Event;
use crate::events::OnceEvent;
#[derive(Debug, Clone)]
pub(crate) struct Exit {
code: Rc<Cell<Option<u8>>>,
event: Rc<Event>,
event: OnceEvent,
}
impl Exit {
pub fn new() -> Self {
Self {
code: Rc::new(Cell::new(None)),
event: Rc::new(Event::new()),
event: OnceEvent::new(),
}
}
pub fn set(&self, code: u8) {
self.code.set(Some(code));
self.event.notify(usize::MAX);
self.event.notify();
}
pub fn get(&self) -> Option<u8> {

View file

@ -5,8 +5,7 @@ use mlua::prelude::*;
use crate::{
error_callback::ThreadErrorCallback,
queue::{DeferredThreadQueue, SpawnedThreadQueue},
result_map::ThreadResultMap,
thread_id::ThreadId,
threads::{ThreadId, ThreadMap},
traits::LuaSchedulerExt,
util::{is_poll_pending, LuaThreadOrFunction},
};
@ -102,13 +101,13 @@ impl Functions {
.app_data_ref::<ThreadErrorCallback>()
.expect(ERR_METADATA_NOT_ATTACHED)
.clone();
let result_map = lua
.app_data_ref::<ThreadResultMap>()
let thread_map = lua
.app_data_ref::<ThreadMap>()
.expect(ERR_METADATA_NOT_ATTACHED)
.clone();
let resume_queue = defer_queue.clone();
let resume_map = result_map.clone();
let resume_map = thread_map.clone();
let resume =
lua.create_function(move |lua, (thread, args): (LuaThread, LuaMultiValue)| {
let _span = tracing::trace_span!("Scheduler::fn_resume").entered();
@ -158,7 +157,7 @@ impl Functions {
.set_environment(wrap_env)
.into_function()?;
let spawn_map = result_map.clone();
let spawn_map = thread_map.clone();
let spawn = lua.create_function(
move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
let _span = tracing::trace_span!("Scheduler::fn_spawn").entered();

View file

@ -1,18 +1,18 @@
#![allow(clippy::cargo_common_metadata)]
mod error_callback;
mod events;
mod exit;
mod functions;
mod queue;
mod result_map;
mod scheduler;
mod status;
mod thread_id;
mod threads;
mod traits;
mod util;
pub use functions::Functions;
pub use scheduler::Scheduler;
pub use status::Status;
pub use thread_id::ThreadId;
pub use threads::ThreadId;
pub use traits::{IntoLuaThread, LuaSchedulerExt, LuaSpawnExt};

View file

@ -1,188 +0,0 @@
use std::{
ops::{Deref, DerefMut},
pin::Pin,
rc::Rc,
};
use concurrent_queue::ConcurrentQueue;
use event_listener::Event;
use futures_lite::{Future, FutureExt};
use mlua::prelude::*;
use crate::{traits::IntoLuaThread, ThreadId};
/**
Queue for storing [`LuaThread`]s with associated arguments.
Provides methods for pushing and draining the queue, as
well as listening for new items being pushed to the queue.
*/
#[derive(Debug, Clone)]
pub(crate) struct ThreadQueue {
inner: Rc<ThreadQueueInner>,
}
impl ThreadQueue {
pub fn new() -> Self {
let inner = Rc::new(ThreadQueueInner::new());
Self { inner }
}
pub fn push_item(
&self,
lua: &Lua,
thread: impl IntoLuaThread,
args: impl IntoLuaMulti,
) -> LuaResult<ThreadId> {
let thread = thread.into_lua_thread(lua)?;
let args = args.into_lua_multi(lua)?;
tracing::trace!("pushing item to queue with {} args", args.len());
let id = ThreadId::from(&thread);
let _ = self.inner.queue.push((thread, args));
self.inner.event.notify(usize::MAX);
Ok(id)
}
#[inline]
pub fn drain_items(&self) -> impl Iterator<Item = (LuaThread, LuaMultiValue)> + '_ {
self.inner.queue.try_iter()
}
#[inline]
pub async fn wait_for_item(&self) {
if self.inner.queue.is_empty() {
let listener = self.inner.event.listen();
// NOTE: Need to check again, we could have gotten
// new queued items while creating our listener
if self.inner.queue.is_empty() {
listener.await;
}
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.queue.is_empty()
}
}
/**
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
*/
#[derive(Debug, Clone)]
pub(crate) struct SpawnedThreadQueue(ThreadQueue);
impl SpawnedThreadQueue {
pub fn new() -> Self {
Self(ThreadQueue::new())
}
}
impl Deref for SpawnedThreadQueue {
type Target = ThreadQueue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SpawnedThreadQueue {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/**
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
*/
#[derive(Debug, Clone)]
pub(crate) struct DeferredThreadQueue(ThreadQueue);
impl DeferredThreadQueue {
pub fn new() -> Self {
Self(ThreadQueue::new())
}
}
impl Deref for DeferredThreadQueue {
type Target = ThreadQueue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DeferredThreadQueue {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
/**
Queue for storing local futures.
Provides methods for pushing and draining the queue, as
well as listening for new items being pushed to the queue.
*/
#[derive(Debug, Clone)]
pub(crate) struct FuturesQueue<'fut> {
inner: Rc<FuturesQueueInner<'fut>>,
}
impl<'fut> FuturesQueue<'fut> {
pub fn new() -> Self {
let inner = Rc::new(FuturesQueueInner::new());
Self { inner }
}
pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {
let _ = self.inner.queue.push(fut.boxed_local());
self.inner.event.notify(usize::MAX);
}
pub fn drain_items<'outer>(
&'outer self,
) -> impl Iterator<Item = LocalBoxFuture<'fut>> + 'outer {
self.inner.queue.try_iter()
}
pub async fn wait_for_item(&self) {
if self.inner.queue.is_empty() {
self.inner.event.listen().await;
}
}
}
// Inner structs without ref counting so that outer structs
// have only a single ref counter for extremely cheap clones
#[derive(Debug)]
struct ThreadQueueInner {
queue: ConcurrentQueue<(LuaThread, LuaMultiValue)>,
event: Event,
}
impl ThreadQueueInner {
fn new() -> Self {
let queue = ConcurrentQueue::unbounded();
let event = Event::new();
Self { queue, event }
}
}
#[derive(Debug)]
struct FuturesQueueInner<'fut> {
queue: ConcurrentQueue<LocalBoxFuture<'fut>>,
event: Event,
}
impl FuturesQueueInner<'_> {
pub fn new() -> Self {
let queue = ConcurrentQueue::unbounded();
let event = Event::new();
Self { queue, event }
}
}

View file

@ -0,0 +1,28 @@
use std::ops::{Deref, DerefMut};
use super::threads::ThreadQueue;
/**
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
*/
#[derive(Debug, Clone)]
pub(crate) struct DeferredThreadQueue(ThreadQueue);
impl DeferredThreadQueue {
pub fn new() -> Self {
Self(ThreadQueue::new())
}
}
impl Deref for DeferredThreadQueue {
type Target = ThreadQueue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for DeferredThreadQueue {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View file

@ -0,0 +1,55 @@
use std::{cell::RefCell, mem, pin::Pin, rc::Rc};
use futures_lite::prelude::*;
use crate::events::MultiEvent;
pub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
struct FuturesQueueInner<'fut> {
queue: RefCell<Vec<LocalBoxFuture<'fut>>>,
event: MultiEvent,
}
impl FuturesQueueInner<'_> {
pub fn new() -> Self {
Self {
queue: RefCell::new(Vec::new()),
event: MultiEvent::new(),
}
}
}
/**
Queue for storing local futures.
Provides methods for pushing and draining the queue, as
well as listening for new items being pushed to the queue.
*/
#[derive(Clone)]
pub(crate) struct FuturesQueue<'fut> {
inner: Rc<FuturesQueueInner<'fut>>,
}
impl<'fut> FuturesQueue<'fut> {
pub fn new() -> Self {
let inner = Rc::new(FuturesQueueInner::new());
Self { inner }
}
pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {
self.inner.queue.borrow_mut().push(fut.boxed_local());
self.inner.event.notify();
}
pub fn take_items(&self) -> Vec<LocalBoxFuture<'fut>> {
let mut queue = self.inner.queue.borrow_mut();
mem::take(&mut *queue)
}
pub async fn wait_for_item(&self) {
if self.inner.queue.borrow().is_empty() {
self.inner.event.listen().await;
}
}
}

View file

@ -0,0 +1,8 @@
mod deferred;
mod futures;
mod spawned;
mod threads;
pub(crate) use self::deferred::DeferredThreadQueue;
pub(crate) use self::futures::FuturesQueue;
pub(crate) use self::spawned::SpawnedThreadQueue;

View file

@ -0,0 +1,28 @@
use std::ops::{Deref, DerefMut};
use super::threads::ThreadQueue;
/**
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
*/
#[derive(Debug, Clone)]
pub(crate) struct SpawnedThreadQueue(ThreadQueue);
impl SpawnedThreadQueue {
pub fn new() -> Self {
Self(ThreadQueue::new())
}
}
impl Deref for SpawnedThreadQueue {
type Target = ThreadQueue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SpawnedThreadQueue {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

View file

@ -0,0 +1,78 @@
#![allow(clippy::inline_always)]
use std::{cell::RefCell, mem, rc::Rc};
use mlua::prelude::*;
use crate::{threads::ThreadId, traits::IntoLuaThread};
use crate::events::MultiEvent;
#[derive(Debug)]
struct ThreadQueueInner {
queue: RefCell<Vec<(LuaThread, LuaMultiValue)>>,
event: MultiEvent,
}
impl ThreadQueueInner {
fn new() -> Self {
Self {
queue: RefCell::new(Vec::new()),
event: MultiEvent::new(),
}
}
}
/**
Queue for storing [`LuaThread`]s with associated arguments.
Provides methods for pushing and draining the queue, as
well as listening for new items being pushed to the queue.
*/
#[derive(Debug, Clone)]
pub(crate) struct ThreadQueue {
inner: Rc<ThreadQueueInner>,
}
impl ThreadQueue {
pub fn new() -> Self {
let inner = Rc::new(ThreadQueueInner::new());
Self { inner }
}
pub fn push_item(
&self,
lua: &Lua,
thread: impl IntoLuaThread,
args: impl IntoLuaMulti,
) -> LuaResult<ThreadId> {
let thread = thread.into_lua_thread(lua)?;
let args = args.into_lua_multi(lua)?;
tracing::trace!("pushing item to queue with {} args", args.len());
let id = ThreadId::from(&thread);
self.inner.queue.borrow_mut().push((thread, args));
self.inner.event.notify();
Ok(id)
}
#[inline(always)]
pub fn take_items(&self) -> Vec<(LuaThread, LuaMultiValue)> {
let mut queue = self.inner.queue.borrow_mut();
mem::take(&mut *queue)
}
#[inline(always)]
pub async fn wait_for_item(&self) {
if self.inner.queue.borrow().is_empty() {
self.inner.event.listen().await;
}
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.inner.queue.borrow().is_empty()
}
}

View file

@ -1,81 +0,0 @@
#![allow(clippy::inline_always)]
use std::{cell::RefCell, rc::Rc};
use event_listener::Event;
// NOTE: This is the hash algorithm that mlua also uses, so we
// are not adding any additional dependencies / bloat by using it.
use mlua::prelude::*;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::thread_id::ThreadId;
struct ThreadResultMapInner {
tracked: FxHashSet<ThreadId>,
results: FxHashMap<ThreadId, LuaResult<LuaMultiValue>>,
events: FxHashMap<ThreadId, Rc<Event>>,
}
impl ThreadResultMapInner {
fn new() -> Self {
Self {
tracked: FxHashSet::default(),
results: FxHashMap::default(),
events: FxHashMap::default(),
}
}
}
#[derive(Clone)]
pub(crate) struct ThreadResultMap {
inner: Rc<RefCell<ThreadResultMapInner>>,
}
impl ThreadResultMap {
pub fn new() -> Self {
let inner = Rc::new(RefCell::new(ThreadResultMapInner::new()));
Self { inner }
}
#[inline(always)]
pub fn track(&self, id: ThreadId) {
self.inner.borrow_mut().tracked.insert(id);
}
#[inline(always)]
pub fn is_tracked(&self, id: ThreadId) -> bool {
self.inner.borrow().tracked.contains(&id)
}
pub fn insert(&self, id: ThreadId, result: LuaResult<LuaMultiValue>) {
debug_assert!(self.is_tracked(id), "Thread must be tracked");
let mut inner = self.inner.borrow_mut();
inner.results.insert(id, result);
if let Some(event) = inner.events.remove(&id) {
event.notify(usize::MAX);
}
}
pub async fn listen(&self, id: ThreadId) {
debug_assert!(self.is_tracked(id), "Thread must be tracked");
if !self.inner.borrow().results.contains_key(&id) {
let listener = {
let mut inner = self.inner.borrow_mut();
let event = inner
.events
.entry(id)
.or_insert_with(|| Rc::new(Event::new()));
event.listen()
};
listener.await;
}
}
pub fn remove(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
let mut inner = self.inner.borrow_mut();
let res = inner.results.remove(&id)?;
inner.tracked.remove(&id);
inner.events.remove(&id);
Some(res)
}
}

View file

@ -17,9 +17,8 @@ use crate::{
error_callback::ThreadErrorCallback,
exit::Exit,
queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},
result_map::ThreadResultMap,
status::Status,
thread_id::ThreadId,
threads::{ThreadId, ThreadMap},
traits::IntoLuaThread,
util::run_until_yield,
};
@ -48,7 +47,7 @@ pub struct Scheduler {
queue_spawn: SpawnedThreadQueue,
queue_defer: DeferredThreadQueue,
error_callback: ThreadErrorCallback,
result_map: ThreadResultMap,
thread_map: ThreadMap,
status: Rc<Cell<Status>>,
exit: Exit,
}
@ -68,7 +67,7 @@ impl Scheduler {
let queue_spawn = SpawnedThreadQueue::new();
let queue_defer = DeferredThreadQueue::new();
let error_callback = ThreadErrorCallback::default();
let result_map = ThreadResultMap::new();
let result_map = ThreadMap::new();
let exit = Exit::new();
assert!(
@ -84,7 +83,7 @@ impl Scheduler {
"{ERR_METADATA_ALREADY_ATTACHED}"
);
assert!(
lua.app_data_ref::<ThreadResultMap>().is_none(),
lua.app_data_ref::<ThreadMap>().is_none(),
"{ERR_METADATA_ALREADY_ATTACHED}"
);
assert!(
@ -105,7 +104,7 @@ impl Scheduler {
queue_spawn,
queue_defer,
error_callback,
result_map,
thread_map: result_map,
status,
exit,
}
@ -201,7 +200,7 @@ impl Scheduler {
args: impl IntoLuaMulti,
) -> LuaResult<ThreadId> {
let id = self.queue_spawn.push_item(&self.lua, thread, args)?;
self.result_map.track(id);
self.thread_map.track(id);
Ok(id)
}
@ -228,7 +227,7 @@ impl Scheduler {
args: impl IntoLuaMulti,
) -> LuaResult<ThreadId> {
let id = self.queue_defer.push_item(&self.lua, thread, args)?;
self.result_map.track(id);
self.thread_map.track(id);
Ok(id)
}
@ -248,7 +247,7 @@ impl Scheduler {
*/
#[must_use]
pub fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
self.result_map.remove(id)
self.thread_map.remove(id)
}
/**
@ -257,7 +256,7 @@ impl Scheduler {
This will return instantly if the thread has already completed.
*/
pub async fn wait_for_thread(&self, id: ThreadId) {
self.result_map.listen(id).await;
self.thread_map.listen(id).await;
}
/**
@ -320,7 +319,7 @@ impl Scheduler {
when there are new Lua threads to enqueue and potentially more work to be done.
*/
let fut = async {
let result_map = self.result_map.clone();
let result_map = self.thread_map.clone();
let process_thread = |thread: LuaThread, args| {
// NOTE: Thread may have been cancelled from Lua
// before we got here, so we need to check it again
@ -397,21 +396,21 @@ impl Scheduler {
let mut num_futures = 0;
{
let _span = trace_span!("Scheduler::drain_spawned").entered();
for (thread, args) in self.queue_spawn.drain_items() {
for (thread, args) in self.queue_spawn.take_items() {
process_thread(thread, args);
num_spawned += 1;
}
}
{
let _span = trace_span!("Scheduler::drain_deferred").entered();
for (thread, args) in self.queue_defer.drain_items() {
for (thread, args) in self.queue_defer.take_items() {
process_thread(thread, args);
num_deferred += 1;
}
}
{
let _span = trace_span!("Scheduler::drain_futures").entered();
for fut in fut_queue.drain_items() {
for fut in fut_queue.take_items() {
local_exec.spawn(fut).detach();
num_futures += 1;
}
@ -458,7 +457,7 @@ impl Drop for Scheduler {
self.lua.remove_app_data::<SpawnedThreadQueue>();
self.lua.remove_app_data::<DeferredThreadQueue>();
self.lua.remove_app_data::<ThreadErrorCallback>();
self.lua.remove_app_data::<ThreadResultMap>();
self.lua.remove_app_data::<ThreadMap>();
self.lua.remove_app_data::<Exit>();
} else {
// In any other case we panic if metadata was removed incorrectly
@ -472,7 +471,7 @@ impl Drop for Scheduler {
.remove_app_data::<ThreadErrorCallback>()
.expect(ERR_METADATA_REMOVED);
self.lua
.remove_app_data::<ThreadResultMap>()
.remove_app_data::<ThreadMap>()
.expect(ERR_METADATA_REMOVED);
self.lua
.remove_app_data::<Exit>()

View file

@ -1,4 +1,7 @@
use std::hash::{Hash, Hasher};
use std::{
ffi::c_void,
hash::{Hash, Hasher},
};
use mlua::prelude::*;
@ -12,13 +15,13 @@ use mlua::prelude::*;
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ThreadId {
inner: usize,
inner: *const c_void,
}
impl From<&LuaThread> for ThreadId {
fn from(thread: &LuaThread) -> Self {
Self {
inner: thread.to_pointer() as usize,
inner: thread.to_pointer(),
}
}
}

View file

@ -0,0 +1,77 @@
#![allow(clippy::inline_always)]
use std::{cell::RefCell, rc::Rc};
use mlua::prelude::*;
use rustc_hash::FxHashMap;
use super::id::ThreadId;
use crate::events::OnceEvent;
struct ThreadEvent {
result: Option<LuaResult<LuaMultiValue>>,
event: OnceEvent,
}
impl ThreadEvent {
fn new() -> Self {
Self {
result: None,
event: OnceEvent::new(),
}
}
}
#[derive(Clone)]
pub(crate) struct ThreadMap {
inner: Rc<RefCell<FxHashMap<ThreadId, ThreadEvent>>>,
}
impl ThreadMap {
pub fn new() -> Self {
let inner = Rc::new(RefCell::new(FxHashMap::default()));
Self { inner }
}
#[inline(always)]
pub fn track(&self, id: ThreadId) {
self.inner.borrow_mut().insert(id, ThreadEvent::new());
}
#[inline(always)]
pub fn is_tracked(&self, id: ThreadId) -> bool {
self.inner.borrow().contains_key(&id)
}
#[inline(always)]
pub fn insert(&self, id: ThreadId, result: LuaResult<LuaMultiValue>) {
if let Some(tracker) = self.inner.borrow_mut().get_mut(&id) {
tracker.result.replace(result);
tracker.event.notify();
} else {
panic!("Thread must be tracked");
}
}
#[inline(always)]
pub async fn listen(&self, id: ThreadId) {
if let Some(listener) = {
let inner = self.inner.borrow();
let tracker = inner.get(&id);
tracker.map(|t| t.event.listen())
} {
listener.await;
} else {
panic!("Thread must be tracked");
}
}
#[inline(always)]
pub fn remove(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
if let Some(mut tracker) = self.inner.borrow_mut().remove(&id) {
tracker.result.take()
} else {
None
}
}
}

View file

@ -0,0 +1,5 @@
mod id;
mod map;
pub use id::ThreadId;
pub(crate) use map::ThreadMap;

View file

@ -12,9 +12,8 @@ use tracing::trace;
use crate::{
exit::Exit,
queue::{DeferredThreadQueue, FuturesQueue, SpawnedThreadQueue},
result_map::ThreadResultMap,
scheduler::Scheduler,
thread_id::ThreadId,
threads::{ThreadId, ThreadMap},
};
/**
@ -314,21 +313,21 @@ impl LuaSchedulerExt for Lua {
fn track_thread(&self, id: ThreadId) {
let map = self
.app_data_ref::<ThreadResultMap>()
.app_data_ref::<ThreadMap>()
.expect("lua threads can only be tracked from within an active scheduler");
map.track(id);
}
fn get_thread_result(&self, id: ThreadId) -> Option<LuaResult<LuaMultiValue>> {
let map = self
.app_data_ref::<ThreadResultMap>()
.app_data_ref::<ThreadMap>()
.expect("lua threads results can only be retrieved from within an active scheduler");
map.remove(id)
}
fn wait_for_thread(&self, id: ThreadId) -> impl Future<Output = ()> {
let map = self
.app_data_ref::<ThreadResultMap>()
.app_data_ref::<ThreadMap>()
.expect("lua threads results can only be retrieved from within an active scheduler");
async move { map.listen(id).await }
}

View file

@ -0,0 +1,23 @@
local task = require("@lune/task")
local util = require("./util")
local pass = util.pass
-- These are some public APIs that have, or most likely have, different
-- certificate authorities (CAs), plus are both free to use and stable.
-- This should be enough to ensure that rustls is configured correctly.
local servers = {
"https://www.googleapis.com/discovery/v1/apis",
"https://api.cloudflare.com/client/v4/ips",
"https://azure.microsoft.com/en-us/updates/feed/",
"https://acme-v02.api.letsencrypt.org/directory",
"https://ip-ranges.amazonaws.com/ip-ranges.json",
"https://en.wikipedia.org/w/api.php",
"https://status.godaddy.com/api/v2/summary.json",
}
for _, server in servers do
task.spawn(function()
pass("GET", server, server)
end)
end

View file

@ -4,22 +4,38 @@ local stdio = require("@lune/stdio")
local util = {}
function util.pass(method, url, message)
local response = net.request({
local success, response = pcall(net.request, {
method = method,
url = url,
})
if not response.ok then
error(string.format("%s failed!\nResponse: %s", message, stdio.format(response)))
if not success then
error(`{message} errored!\nError message: {tostring(response)}`)
elseif not response.ok then
error(
`{message} failed, but should have passed!`
.. `\nStatus code: {response.statusCode}`
.. `\nStatus message: {response.statusMessage}`
.. `\nResponse headers: {stdio.format(response.headers)}`
.. `\nResponse body: {response.body}`
)
end
end
function util.fail(method, url, message)
local response = net.request({
local success, response = pcall(net.request, {
method = method,
url = url,
})
if response.ok then
error(string.format("%s passed!\nResponse: %s", message, stdio.format(response)))
if not success then
error(`{message} errored!\nError message: {tostring(response)}`)
elseif response.ok then
error(
`{message} passed, but should have failed!`
.. `\nStatus code: {response.statusCode}`
.. `\nStatus message: {response.statusMessage}`
.. `\nResponse headers: {stdio.format(response.headers)}`
.. `\nResponse body: {response.body}`
)
end
end

View file

@ -5,9 +5,10 @@ assert(#process.args > 0, "No process arguments found")
assert(process.args[1] == "Foo", "Invalid first argument to process")
assert(process.args[2] == "Bar", "Invalid second argument to process")
local success, message = pcall(function()
local success, err = pcall(function()
process.args[1] = "abc"
end)
local message = if err == nil then nil else tostring(err)
assert(
success == false and type(message) == "string" and #message > 0,
"Trying to set process arguments should throw an error with a message"