luby/src/main.rs

267 lines
6.7 KiB
Rust

use clap::Parser;
use luajit::Chunk;
use mimalloc::MiMalloc;
use owo_colors::OwoColorize;
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
use sysexits::ExitCode;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
fn panic_cb(panic: &panic::PanicHookInfo) {
let trace = Backtrace::force_capture();
let location = panic.location().unwrap();
let payload = panic.payload();
let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
s
} else if let Some(s) = payload.downcast_ref::<String>() {
s.as_str()
} else {
"unknown error"
};
eprint!(
"{}\n{trace}",
format_args!(
"thread '{}' panicked at {location}: {msg}",
thread::current().name().unwrap_or("<unnamed>")
)
.red()
.bold()
);
eprintln!(
"{}",
format_args!(
"luby should never panic. Please kindly report this bug at {}.",
env!("CARGO_PKG_REPOSITORY")
)
.yellow()
.bold()
);
}
fn error_cb(err: &luajit::Error) {
match err.trace() {
Some(trace) => eprintln!("{}\n{trace}", err.red().bold()),
None => eprintln!("{}", err.red().bold()),
}
process::exit(1);
}
fn unwrap_exit<T, E: Display>(code: ExitCode) -> impl FnOnce(E) -> T {
move |err| {
eprintln!("{}", err.red().bold());
code.exit()
}
}
#[derive(Debug, Parser)]
struct Args {
/// Paths to scripts to execute.
#[clap(value_name = "SCRIPTS")]
path: Vec<String>,
/// Strings to execute.
#[clap(long, short = 'e', value_name = "CHUNK")]
eval: Vec<String>,
/// Libraries to require.
#[clap(long, short = 'l', value_name = "NAME")]
lib: Vec<String>,
/// Console log level.
#[clap(long, value_name = "LEVEL", default_value = "debug")]
log: tracing::Level,
/// LuaJIT control commands.
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
jit: Vec<String>,
/// Allow global variables.
#[clap(
long,
help_heading = "Runtime",
value_name = "ENABLED",
default_value_t = true
)]
allow_globals: bool,
/// Number of worker threads.
#[clap(
long,
short = 'T',
help_heading = "Runtime",
value_name = "COUNT",
default_value_t = Self::threads()
)]
threads: NonZero<usize>,
/// Number of blocking threads.
#[clap(
long,
help_heading = "Runtime",
value_name = "COUNT",
default_value_t = Self::blocking_threads()
)]
blocking_threads: NonZero<usize>,
/// Enable tokio-console integration.
#[cfg(feature = "tokio-console")]
#[clap(long, help_heading = "Debugging", value_name = "ENABLED")]
enable_console: bool,
/// tokio-console publish address.
#[cfg(feature = "tokio-console")]
#[clap(
long,
help_heading = "Debugging",
value_name = "ADDRESS",
default_value = "127.0.0.1:6669",
requires = "enable_console"
)]
console_addr: std::net::SocketAddr,
/// Dump internal data.
#[clap(
long,
help_heading = "Debugging",
value_name = "DATA",
value_parser = ["cdef"]
)]
dump: Vec<String>,
/// Print version.
#[clap(long, short = 'V')]
version: bool,
}
impl Args {
fn threads() -> NonZero<usize> {
thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap())
}
fn blocking_threads() -> NonZero<usize> {
NonZero::new(1024).unwrap()
}
}
fn main() -> ExitCode {
panic::set_hook(Box::new(panic_cb));
let args = Args::parse();
if args.version {
print_version();
return ExitCode::Ok;
}
init_logger(&args);
let tokio = init_tokio(&args);
let lua = init_lua(&args);
let main = lua.spawn(async |s| main_async(args, s).await);
tokio.block_on(async {
lua.await;
match main.await {
Ok(res) => res,
Err(err) => panic::resume_unwind(err.into_panic()),
}
})
}
fn print_version() {
println!("luby {}", env!("VERGEN_GIT_DESCRIBE"));
println!("{}\n", env!("CARGO_PKG_HOMEPAGE"));
println!("Compiled with {} -- {}", luajit::version(), luajit::url());
println!(
"Compiled with rustc {} on {} for {}",
env!("VERGEN_RUSTC_SEMVER"),
env!("VERGEN_RUSTC_HOST_TRIPLE"),
env!("VERGEN_CARGO_TARGET_TRIPLE"),
);
}
fn init_logger(args: &Args) {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::util::*;
let log = tracing_subscriber::fmt()
.compact()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(LevelFilter::from(args.log).into())
.from_env_lossy(),
)
.with_file(false)
.with_line_number(false)
.with_target(false)
.finish();
#[cfg(feature = "tokio-console")]
{
use tracing_subscriber::Layer;
console_subscriber::ConsoleLayer::builder()
.with_default_env()
.server_addr(args.console_addr)
.spawn()
.with_subscriber(log)
.init();
}
#[cfg(not(feature = "tokio-console"))]
log.init();
}
fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
match args.threads.get() {
1 => tokio::runtime::Builder::new_current_thread(),
n => {
let mut rt = tokio::runtime::Builder::new_multi_thread();
rt.worker_threads(n - 1);
rt
}
}
.enable_all()
.thread_name("luby")
.max_blocking_threads(args.blocking_threads.get())
.build()
.unwrap_or_else(unwrap_exit(ExitCode::OsErr))
}
fn init_lua(args: &Args) -> lb::runtime::Runtime {
let mut rt = lb::runtime::Builder::new();
luby::open(&mut rt);
if args.dump.iter().find(|s| *s == "cdef").is_some() {
print!("{}", rt.registry()); // for cdef debugging
}
rt.unhandled_error(error_cb)
.prohibit_globals(!args.allow_globals)
.jit_opts(args.jit.iter())
.build()
.unwrap()
}
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
for ref path in args.path {
let chunk = match std::fs::read(path) {
Ok(chunk) => chunk,
Err(err) => {
eprintln!("{}", format_args!("{path}: {err}").red().bold());
return ExitCode::NoInput;
}
};
if let Err(ref err) = cx.load(&Chunk::new(chunk).with_path(path)) {
cx.report_error(err);
} else if let Err(ref err) = cx.call_async(0, Some(0)).await {
cx.report_error(err);
}
}
ExitCode::Ok
}