Releases: koja-lang/koja
Releases · koja-lang/koja
Release list
v0.13.0
Added
- The interpreter now supports
spawn, cross-process messaging, timers, and I/O in scripts andkoja run --backend=interpreter— process programs that previously required--backend=llvmnow run identically on both backends. - A process stuck in long-running CPU-bound work — an infinite loop, tail recursion, or deep non-tail recursion alike — no longer freezes the others. The runtime now periodically interrupts such work so every process gets a fair turn, and higher-priority processes get a larger share.
- Per-process scheduling priority via
Process.priority()returningPriority.Low,Priority.Normal, orPriority.High(defaultNormal); the scheduler serves higher priorities first but periodically forces the lowest non-empty tier so lower priorities aren't starved. - Graceful shutdown on
SIGTERM: the runtime stops accepting newspawns and gives the program a grace period (default 30s, configurable with theKOJA_GRACE_MSenvironment variable) to wind itself down. A process that handles theShutdownlifecycle signal can exit cleanly; anything still running when the grace period elapses is stopped so the program always terminates. - Compiled (
--backend=llvm) programs now print a source backtrace on panic —** (panic) <message>followed byfile:line: name()frames for the user's call chain, including across spawned processes. Function-granular DWARF, so each frame resolves to its function's declaration line.
Changed
- Message passing and process spawning are dramatically faster (roughly 10× and 25× in micro-benchmarks). When one process sends to, replies to, or spawns another, the runtime now keeps the pair on the same CPU core instead of handing the work off across threads, eliminating a context switch and wakeup per message. Programs that run many processes at once also make better use of every core: each worker thread keeps its own run queue and pulls work from busier threads when it would otherwise sit idle. This is a performance change only — no difference in behavior — and applies to the compiled (
--backend=llvm) backend. - Cooperative preemption is now much cheaper: the fairness check is a near-free inline operation that only calls into the scheduler once a process has used up its turn, instead of a function call on every check. Tight loops run measurably faster as a result (~2× on a 200M-iteration micro-benchmark).
- Scheduler timers and deadlines are now backed by a single hashed timing wheel (plus an overflow heap for far-future entries) instead of two binary heaps, making timer arming amortized O(1).
Fixed
--releasenow actually engages LLVM-O3optimization; previously the flag only affected linking and emitted unoptimized (-O0) code.- A process blocked in a synchronous syscall (e.g. socket
accept) now wakes when a lifecycle signal (SIGTERM→Shutdown) arrives, instead of staying stuck until the I/O happens to complete.
v0.12.2
Added
Option/Resultinterop combinatorsOption.or_err(error),Result.ok(),Result.err(), andResult.map_err(f).
Fixed
- I/O readiness events now reach a process whose message union includes
IOReady— thehandlearm receives them instead of the program hitting an unreachable trap and exiting. Float32arithmetic, comparison, and negation now work under the interpreter.- Binary pattern matching no longer crashes on a subject shorter than the pattern's fixed prefix (e.g.
<<a::8, b::8, rest: Binary>>against<<>>); the match cleanly fails instead. Fd.watchon an already-readable fd (e.g. a pipe with buffered data) no longer hangs the process — a race could drop the first readiness event.- Formatter: Line-wrapping elements of a list when formatted now align properly vertically.
- Formatter: If a function signature line-wraps, always insert a blank line before the rest of the function body.
- Formatter: Preserve comment placement in match arms.
- Formatter: Overflowing method chains break at each
.method, and a sole trailing closure hugs its parentheses.
v0.12.1
v0.12.0
Added
- TLS support in the
netpackage, built on the bundled BoringSSL.TCPSocket.connect_tls(host, port)establishes a verified connection (certificate chain and hostname checked against the system CA bundle);connect_tls_with/upgrade_tls/accept_tlscover custom configs and server-side handshakes.TLSConfig.client()/.insecure()/.server(cert, key)/.with_ca(ca)configure verification and credentials. Certificates and keys are in-memoryCrypto.Certificate/Crypto.PrivateKeyvalues parsed from PEM text, validated up front — a swapped cert/key pair or an encrypted key fails with a clear message instead of a handshake error. The HTTP client routeshttps://URLs through TLS automatically, soHTTP.get("https://...")works out of the box. koja runnow executes projects with the interpreter by default — no--backend=llvmrequired. TheProcessentry runs in-process with argv passing, sockets, TLS, binary pattern matching, andreceiveover lifecycle signals (SIGTERM/SIGINT/SIGHUP) andaftertimeouts, so real services run unmodified with millisecond startup. Process features the interpreter does not cover yet (spawn, cross-process messaging) report a clear runtime error with a--backend=llvmhint.- Implicit numeric widening into the default types: sized integers (
Int8/Int16/Int32/UInt8/UInt16/UInt32) now flow intoIntslots without explicit conversion, andFloat32intoFloat— at call arguments, fields, payloads, returns, bindings, and assignments. Widening is always lossless. Sideways widening (Int8toInt16), narrowing,UInt64toInt, binary operators, and generic inference still require exact types. - Checked numeric narrowing methods:
Int.to_int8/to_int16/to_int32/to_uint8/to_uint16/to_uint32/to_uint64andUInt64.to_int, each returningResult<T, NumericConversionError>withOutOfRangewhen the value does not fit.Float.to_float32is total and rounds to the nearest representable value. koja test --traceprints each test grouped by struct with itspath:lineand per-test timing instead of progress dots. Names print before the test runs, so a crashing test leaves its name as the last line of output. Trace runs skip the per-binary timeout for long debugging sessions.koja testnow enforces a 60-second per-binary timeout, so a hung test process fails fast instead of stalling the suite.Fd.read_binary(count)andTCPSocket.read_binary(count)-- read up tocountbytes as aBinaryfor binary wire protocols.Binary.at(index) -> Option<Int>andBinary.slice(range) -> Binary-- O(1) byte access and inclusive-range byte slicing (endpoints clamp). The byte-oriented complement toString.get/String.slice, whose codepoint indexing is O(n) per call; scanners and parsers should step overtext.to_binary()instead.
Changed
- Breaking: Koja moved from affine ownership to value semantics. Every binding, parameter, return, and field is now an independent value: assignment and argument passing copy, and a value stays usable for as long as it is in scope. Copies are cheap — heap-backed values are reference-counted, shared until mutated (copy-on-write), and reclaimed deterministically at scope exit, with no garbage collector. The
movekeyword, borrow-by-default parameters, use-after-move errors, and thefn(move T) -> Usyntax are gone. Removemovefrom your signatures; code that previously had to satisfy move/borrow rules now just works. - Breaking: the
netpackage reports failures with typed errors instead of strings. Socket operations returnResult<_, Net.SocketError>, a cause-based enum (ConnectionRefused,TimedOut,NameNotFound, ...,Unknown(errno)) — match on the cause instead of parsing message text. TLS-capable operations returnResult<_, Net.SocketError | Net.TLSError>, withTLSError.VerificationFailed(VerificationError)carrying why certificate verification failed.Crypto.Certificate.parse/Crypto.PrivateKey.parsereturnResult<_, Crypto.PEMError>. All error enums exposemessage()for display.TCPEvent.Errornow carries aSocketError, andTLSSession.readis removed — useTCPSocket.read, which decodes UTF-8 and reportsSocketError.InvalidUTF8. - Breaking:
Int.parseandFloat.parse(and theString.to_int/String.to_floatwrappers) now returnResult<T, NumericConversionError>instead ofResult<T, String>:InvalidFormatfor malformed text,OutOfRangefor well-formed numbers that don't fit — including an integer overflowing 64 bits and a float like1e999that would round to infinity (previously parsed asinf). Theinf/nantokens are rejected asInvalidFormat. Fd.writeandTCPSocket.writenow acceptdata: Binary | String(aStringis encoded as UTF-8), unifying text and binary writes under one method.
Removed
- Breaking:
fn mainis no longer a program entry point. Every compiled program's entry is a type implementingProcess<C, M, R>, named byentry = "App"inkoja.toml; the program exits with the code mapped from the entry'sStopReason. Standalone.kojafiles can no longer be built or run — use a.kojsscript for entry-free programs, or a project for compiled binaries (koja check file.kojastill works).koja newscaffolds aProcessentry, and thekoja testharness runs as one too. - Breaking: The
Cloneprotocol and.clone()method are removed. Under value semantics every binding is already an independent value, so explicit cloning is unnecessary — replacex.clone()withx.
Fixed
- Fixed a scheduler race that could intermittently crash message-heavy programs with a segfault when a yielded process was resumed from a stale stack pointer.
- Fixed a stack overflow in long-running processes: a self-recursive tail call written as the value of an
if/match/receive(notably thereceive ... after -> self.loop()actor loop) was not tail-call-optimized, so the loop grew the stack until it crashed. These loops now run in constant stack. - Passing a heap-owning local (e.g. a
Stringbuilt with<>, or aList) to a function or method that stores it no longer crashes or returns corrupt data under--backend=llvm. - Fixed an unbounded memory leak on common patterns: a heap-backed temporary passed as an argument (
f(a <> b)), used as a receiver in a method chain (make().foo().bar()), or produced by a literal or constructor was never released. Long-running programs no longer bleed memory on routine work. - Message payloads that are never delivered — sent to a dead process, or still queued when the program exits — are now reclaimed instead of leaked.
- Fixed a race in the I/O reactor that could leave a process blocked forever on a readiness event delivered between fd registration and the blocking state transition.
- Closing a file descriptor that another process is blocked on now wakes that process with an error instead of stranding it forever; a
Fd.watchowner of the closed fd receives a synthetic readiness-error event so its handler observes the hangup. - Closing a file descriptor now drops it from the reactor's watch maps, preventing spurious wakeups when the kernel recycles the fd.
- Package-level functions are now callable from other packages with qualified syntax (
HTTP.get(url)), as the 0.11.0 changelog advertised — previously this failed withunknown identifier. A call to a missing function in a known package now reportspackageXhas no functiony``. JSON.Decoder.decodeis now O(n): it scans the input's UTF-8 bytes instead of indexing codepoints (which walked from the start of the string on every call, making the old decoder quadratic). Decoding a 200 KB payload drops from ~8 s to milliseconds.- The interpreter no longer deep-copies
String/Binarypayloads on every variable read and argument pass — buffers are now shared via reference counting, matching compiled binaries. Workloads that move large payloads through functions (e.g. decoding a big HTTPS JSON response) run several times faster. koja formatnow lays outmatch/cond/receivearms consistently: when any arm body is long enough to wrap, every sibling arm breaks onto its own indented line instead of leaving short siblings inline next to a wrapped one.