English | 한국어
luart runs Lua scripts in Go with high performance and concurrency safety —
a per-key:version bytecode cache, per-script VM pooling, and a self-managing dynamic
registry — on top of lua-pure, a pure-Go
PUC-Lua 5.4 engine.
- Module
github.com/htcom-code/go-lua-perf· Go 1.25+ · lua-pure v0.1.2 (Lua 5.4) · v0.0.1 - The core library depends only on lua-pure (the config loader's YAML dependency is isolated in
luartconfig).
- Bytecode cache — compile once per
key:version. A script is parsed and compiled only on its first run; every later run is a 0-alloc cache hit, independent of source size. - Concurrency-safe VM pooling. Each script gets its own pool of Lua States, never shared across goroutines, so thousands of concurrent
Runcalls reuse warm VMs. - Self-managing registry — lazy load, TTL & memory-budget eviction, hot reload. Scripts load on demand, idle States are reclaimed (TTL + LRU under a memory cap), the cap applies backpressure, and a notification hot-reloads with no restart.
- Pluggable
SourceLoader+ metrics/logging/tracing. Fetch scripts from any backend (file, DB, in-memory, caching, routing); opt intoMetrics,Logger, and a per-stageTraceHookat zero cost when unset. - Sandboxed Lua 5.4 with exec-time & instruction limits. A safe default library set (customizable via
Config.Libs), plusExecTimeout(wall-clock) andMaxInstructions(opcode) caps for untrusted scripts.
go get github.com/htcom-code/go-lua-perfThe library lives at the module root; the package name is luart, so import it with an alias:
import luart "github.com/htcom-code/go-lua-perf"Public module — go get works directly via the module proxy. The API is 0.x and may change between minor versions.
import (
"context"
"fmt"
lua "github.com/htcom-code/lua-pure/lua"
luart "github.com/htcom-code/go-lua-perf"
)
loader := luart.NewMapLoader() // a SourceLoader (where your cache/DB plugs in)
src := `function greet(name) return "hello, " .. name end`
loader.Set("greeter", src, luart.HashVersion(src), "1.0.0")
rt := luart.New(loader, luart.Config{MaxStates: 4})
defer rt.Close()
out, _ := rt.Run(context.Background(), "greeter", "greet", lua.LString("luart"))
fmt.Println(out[0].String()) // hello, luartImportant
SourceLoader is the interface you implement — write it to fetch sources from your external cache/DB/service:
Load(key string) (src, version, displayVersion string, err error)
NewMapLoader (and Set / Loads) is a test/demo-only in-memory implementation, not suitable for production (it keeps every source resident forever). Only the content-hash helper HashVersion(src string) string is meant for use beyond demos.
Full guide + File/DB/Memory/hybrid examples: docs/SourceLoader.md.
The runtime is a small surface: construct with New, execute with Run /
RunValues / RunWith, reload with Notify, and stop with Close / Shutdown.
You implement SourceLoader to fetch scripts from your own cache/DB/service.
Run— fastest path; read the returned values synchronously.RunValues— deep-copies results to Go values, safe to keep after the call.RunWith— consume results inside a handler while the State is still owned.
Runnable, test-covered examples — one folder per feature (hot reload, TTL, memory budget, exec timeout, graceful shutdown, config loading, metrics, logging, tracing, sandbox, custom libs, custom loaders) — live in examples/:
go run ./examples/basicsFull API reference: pkg.go.dev (or make doc).
Set luart.Config in code, or load the numeric/duration fields from JSON/YAML/env
via the luartconfig subpackage. Key knobs:
| Concern | Fields |
|---|---|
| Concurrency cap | MaxStates, or MemoryBudgetBytes (derives the cap) |
| Idle reclaim | IdleTTL, JanitorInterval |
| Runaway guards | ExecTimeout (wall-clock), MaxInstructions (opcodes) |
| Sandbox / libraries | Libs, ExtraLibs, IsolateGlobals |
| Observability | Metrics, Logger, Trace |
- Field reference & loading (JSON/YAML/env, precedence): docs/config.md
- Choosing values by workload: docs/tuning.md
- Implementing a
SourceLoader: docs/SourceLoader.md
- VM pool reuse ≈ 870× faster than building a fresh State per call (~258 ns vs ~225 µs), and a compile-cache hit is 0-alloc.
- Cache-hit execution is effectively size-independent (~550 ns): the one-time compile is paid once per
key:versionand amortized across every run. - Compile cost stays within ~1–2× time / ~2× memory of PUC-Lua's C compiler (lua-pure ports it in pure Go).
Full methodology and per-benchmark tables: docs/BENCHMARKS.md. Reproduce on your machine with make bench and go run ./performance.
- API reference — pkg.go.dev (
make doc/make doc-weblocally) - Guides — config · tuning · SourceLoader · benchmarks
- Changelog — CHANGELOG.md
luart is at v0.0.1 (0.x) — usable, with an API that may still change between
minor versions. See ROADMAP.md for direction and non-goals.
Contributions welcome — see CONTRIBUTING.md for the build/test
discipline (make all gate, per-file tests, benchmark guardrails) and the
architecture map. Bug reports / feature requests use the issue templates; Lua
language issues belong to the lua-pure engine.
luart can run untrusted Lua at scale, but the host owns the sandboxing and resource-limit policy. See SECURITY.md for the threat model and how to report a vulnerability privately.
MIT © 2026 htjulia