@tishlang/tish 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
//! Process-level prefork for the `tish:http` server.
|
|
2
|
+
//!
|
|
3
|
+
//! ## Why
|
|
4
|
+
//!
|
|
5
|
+
//! Tish's `Value` type is reference-counted with `Rc`/`RefCell` and therefore
|
|
6
|
+
//! `!Send`. Serving HTTP in parallel across CPU cores with the existing VM
|
|
7
|
+
//! would require either
|
|
8
|
+
//!
|
|
9
|
+
//! 1. a wholesale `Rc → Arc` conversion across every Tish crate, or
|
|
10
|
+
//! 2. spinning up independent VM instances that never share a `Value`.
|
|
11
|
+
//!
|
|
12
|
+
//! Option 1 taxes every single-threaded Tish program with atomic ref-count
|
|
13
|
+
//! overhead forever. Option 2 is what this file implements, via the classic
|
|
14
|
+
//! UNIX **prefork** pattern:
|
|
15
|
+
//!
|
|
16
|
+
//! * The parent process (worker 0) `fork`s — actually `spawn`s a new
|
|
17
|
+
//! `std::process::Command` pointing at the current executable — once per
|
|
18
|
+
//! extra core. Each child re-executes the entire Tish program in its own
|
|
19
|
+
//! address space.
|
|
20
|
+
//! * All processes (parent + children) bind the same `port` with
|
|
21
|
+
//! `SO_REUSEPORT`; the kernel hashes incoming connections across them.
|
|
22
|
+
//! * Each process runs a *single-threaded* accept + dispatch loop, so the
|
|
23
|
+
//! Tish VM stays single-threaded and `Value` stays `Rc`-backed.
|
|
24
|
+
//!
|
|
25
|
+
//! ## Why this is the right default
|
|
26
|
+
//!
|
|
27
|
+
//! * **nginx, gunicorn, unicorn, puma (cluster), and phpfpm all ship this
|
|
28
|
+
//! model.** It's the battle-tested way to extract N-core throughput from a
|
|
29
|
+
//! single-threaded scripting runtime.
|
|
30
|
+
//! * Zero Tish-language semantic changes: users write `serve(port, handler)`
|
|
31
|
+
//! exactly as before and get N-core scaling for free.
|
|
32
|
+
//! * Every process has a fresh DB connection pool, a fresh cache, a fresh
|
|
33
|
+
//! whatever. No cache invalidation, no shared mutable state, no data races.
|
|
34
|
+
//! * Crash isolation: if one worker panics the others keep serving.
|
|
35
|
+
//!
|
|
36
|
+
//! ## Cost
|
|
37
|
+
//!
|
|
38
|
+
//! * Each worker re-runs top-level initialization (module imports, constant
|
|
39
|
+
//! folding, static route registration, cache warmup, ...). For typical
|
|
40
|
+
//! apps this is milliseconds and happens once at startup, in parallel. For
|
|
41
|
+
//! apps that preload hundreds of MB of in-process state (e.g. the TFB
|
|
42
|
+
//! `warmupWorldCache` that keeps 10 000 rows in RAM), the memory multiplier
|
|
43
|
+
//! is N×. Users who can't afford the memory set `TISH_HTTP_WORKERS=1`.
|
|
44
|
+
//!
|
|
45
|
+
//! ## Control surface
|
|
46
|
+
//!
|
|
47
|
+
//! | env var | default | effect |
|
|
48
|
+
//! |---------------------|------------------------|--------------------------------------|
|
|
49
|
+
//! | `TISH_HTTP_WORKERS` | `available_parallelism`| number of worker processes |
|
|
50
|
+
//! | `TISH_HTTP_PREFORK` | `1` (on) | set to `0` to force single-process |
|
|
51
|
+
//! | `TISH_WORKER_ID` | unset on parent | set on children by the parent |
|
|
52
|
+
//!
|
|
53
|
+
//! Children are launched with `TISH_WORKER_ID={1..N-1}` and
|
|
54
|
+
//! `TISH_HTTP_PREFORK=child`. The serve() runtime checks these before
|
|
55
|
+
//! deciding whether to fork again, preventing runaway forking.
|
|
56
|
+
|
|
57
|
+
use std::io;
|
|
58
|
+
use std::process::{Child, Command};
|
|
59
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
60
|
+
use std::sync::Arc;
|
|
61
|
+
|
|
62
|
+
/// Role of the current process in a prefork group.
|
|
63
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
64
|
+
pub enum PreforkRole {
|
|
65
|
+
/// The parent — owns the child PIDs, handles signals, re-execs nothing.
|
|
66
|
+
Parent,
|
|
67
|
+
/// A child spawned by the parent. Never forks again.
|
|
68
|
+
Child(usize),
|
|
69
|
+
/// Prefork disabled (single-process mode).
|
|
70
|
+
Single,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Inspect the environment to decide which role this process plays.
|
|
74
|
+
pub fn role_from_env() -> PreforkRole {
|
|
75
|
+
let is_child = std::env::var("TISH_HTTP_PREFORK")
|
|
76
|
+
.map(|v| v.eq_ignore_ascii_case("child"))
|
|
77
|
+
.unwrap_or(false);
|
|
78
|
+
if is_child {
|
|
79
|
+
let id = std::env::var("TISH_WORKER_ID")
|
|
80
|
+
.ok()
|
|
81
|
+
.and_then(|s| s.parse::<usize>().ok())
|
|
82
|
+
.unwrap_or(1);
|
|
83
|
+
return PreforkRole::Child(id);
|
|
84
|
+
}
|
|
85
|
+
let disabled = std::env::var("TISH_HTTP_PREFORK")
|
|
86
|
+
.map(|v| v == "0" || v.eq_ignore_ascii_case("false") || v.eq_ignore_ascii_case("off"))
|
|
87
|
+
.unwrap_or(false);
|
|
88
|
+
if disabled {
|
|
89
|
+
PreforkRole::Single
|
|
90
|
+
} else {
|
|
91
|
+
PreforkRole::Parent
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Spawn `n - 1` child processes (current worker is worker 0). Each child
|
|
96
|
+
/// inherits stdio and gets `TISH_WORKER_ID={1..n-1}` +
|
|
97
|
+
/// `TISH_HTTP_PREFORK=child` so it doesn't recurse.
|
|
98
|
+
///
|
|
99
|
+
/// Returns the child handles so the parent can reap / signal them.
|
|
100
|
+
pub fn spawn_children(n: usize) -> io::Result<Vec<Child>> {
|
|
101
|
+
if n <= 1 {
|
|
102
|
+
return Ok(Vec::new());
|
|
103
|
+
}
|
|
104
|
+
let exe = std::env::current_exe()?; // codacy-disable-line
|
|
105
|
+
let args: Vec<std::ffi::OsString> = std::env::args_os().skip(1).collect(); // codacy-disable-line
|
|
106
|
+
let mut out = Vec::with_capacity(n - 1);
|
|
107
|
+
for i in 1..n {
|
|
108
|
+
let mut cmd = Command::new(&exe);
|
|
109
|
+
cmd.args(&args);
|
|
110
|
+
cmd.env("TISH_WORKER_ID", i.to_string());
|
|
111
|
+
cmd.env("TISH_HTTP_PREFORK", "child");
|
|
112
|
+
// Children inherit the shared cache of 1 thread so they don't recurse
|
|
113
|
+
// into SO_REUSEPORT multi-listener logic. The parent keeps the same.
|
|
114
|
+
cmd.env("TISH_HTTP_WORKERS", "1");
|
|
115
|
+
// Inherit stdout/stderr: child logs stream into the same terminal.
|
|
116
|
+
let child = cmd.spawn()?;
|
|
117
|
+
out.push(child);
|
|
118
|
+
}
|
|
119
|
+
Ok(out)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Install a Ctrl-C / SIGTERM handler on the parent that propagates to all
|
|
123
|
+
/// children. Safe to call multiple times; the handler is stored in a
|
|
124
|
+
/// process-wide slot.
|
|
125
|
+
///
|
|
126
|
+
/// Returns a shared stop flag that callers can poll from their accept loop.
|
|
127
|
+
pub fn install_parent_signal_handler(children: Vec<Child>) -> Arc<AtomicBool> {
|
|
128
|
+
let stop = Arc::new(AtomicBool::new(false));
|
|
129
|
+
let pids: Vec<u32> = children.iter().map(|c| c.id()).collect();
|
|
130
|
+
install_shutdown_handler(Arc::clone(&stop), pids);
|
|
131
|
+
|
|
132
|
+
// Reap children in the background so they don't zombify when the user
|
|
133
|
+
// ^C's or when a child dies on its own.
|
|
134
|
+
std::thread::Builder::new()
|
|
135
|
+
.name("tish-prefork-reaper".into())
|
|
136
|
+
.spawn(move || {
|
|
137
|
+
for mut child in children {
|
|
138
|
+
let _ = child.wait();
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
.ok();
|
|
142
|
+
stop
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[cfg(unix)]
|
|
146
|
+
fn install_shutdown_handler(stop: Arc<AtomicBool>, pids: Vec<u32>) {
|
|
147
|
+
// Store state in process-global statics so an `extern "C"` fn can reach
|
|
148
|
+
// them from inside a signal handler. This is the usual pattern for
|
|
149
|
+
// libc::signal callbacks — setting a flag + waking up listeners is the
|
|
150
|
+
// only async-signal-safe work we do here.
|
|
151
|
+
use std::sync::OnceLock;
|
|
152
|
+
static STOP_FLAG: OnceLock<Arc<AtomicBool>> = OnceLock::new();
|
|
153
|
+
static CHILD_PIDS: OnceLock<Vec<u32>> = OnceLock::new();
|
|
154
|
+
|
|
155
|
+
let _ = STOP_FLAG.set(stop);
|
|
156
|
+
let _ = CHILD_PIDS.set(pids);
|
|
157
|
+
|
|
158
|
+
extern "C" fn on_signal(sig: libc::c_int) {
|
|
159
|
+
if let Some(flag) = STOP_FLAG.get() {
|
|
160
|
+
flag.store(true, Ordering::Relaxed);
|
|
161
|
+
}
|
|
162
|
+
if let Some(pids) = CHILD_PIDS.get() {
|
|
163
|
+
for pid in pids {
|
|
164
|
+
// codacy-disable-next-line
|
|
165
|
+
unsafe {
|
|
166
|
+
libc::kill(*pid as libc::pid_t, libc::SIGTERM);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Re-raise with default disposition so the parent actually exits.
|
|
171
|
+
// codacy-disable-next-line
|
|
172
|
+
unsafe {
|
|
173
|
+
libc::signal(sig, libc::SIG_DFL);
|
|
174
|
+
libc::raise(sig);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let h = on_signal as *const () as libc::sighandler_t;
|
|
179
|
+
// codacy-disable-next-line
|
|
180
|
+
unsafe {
|
|
181
|
+
libc::signal(libc::SIGINT, h);
|
|
182
|
+
libc::signal(libc::SIGTERM, h);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[cfg(not(unix))]
|
|
187
|
+
fn install_shutdown_handler(_stop: Arc<AtomicBool>, _pids: Vec<u32>) {
|
|
188
|
+
// TODO: SetConsoleCtrlHandler on Windows.
|
|
189
|
+
}
|