pub struct Bridge { /* private fields */ }Expand description
kevy-lua per-shard bridge. One Bridge lives in each shard’s
runtime; it owns the per-dialect VM pool, the SHA1 cache, and
the kevy-side dispatch callback that redis.call invokes.
The bridge is intentionally NOT Send / Sync — same constraint
as luna-core’s Vm, which is !Send + !Sync by design. kevy’s
thread-per-core model means every shard owns its bridge
exclusively.
let mut bridge = kevy_lua::Bridge::with_no_dispatch();
// no shebang runs under Lua 5.1; `#!lua version=5.3` opts into 5.3
assert_eq!(bridge.eval(b"return 7 / 2", &[], &[]), b"$3\r\n3.5\r\n");
assert_eq!(bridge.eval(b"#!lua version=5.3\nreturn 7 // 2", &[], &[]), b":3\r\n");Implementations§
Source§impl Bridge
impl Bridge
Sourcepub fn new<F>(dispatch: F) -> Self
pub fn new<F>(dispatch: F) -> Self
Create a fresh bridge with dispatch as the host callback
behind redis.call. No Vms are spawned until the first
EVAL.
The dispatch closure receives the script’s argv (&[&[u8]],
command name at index 0) plus a read_only flag and must
return RESP reply bytes. When read_only is true the
dispatcher MUST reject write commands with
-READONLY can't write against a read-only script\r\n so
EVAL_RO / EVALSHA_RO deliver Redis semantics. kevy-rt
owns the canonical command-flag table and does this check
natively in production; tests provide a stub dispatcher
hard-coding a few write commands (see tests/integration.rs).
For embedders that don’t need real keyspace access (e.g.
pure-computation EVAL), Bridge::with_no_dispatch installs
a default that returns -ERR redis.call: no dispatch wired
for every call.
// a host that answers every command with its own name
let mut b = kevy_lua::Bridge::new(|argv: &[&[u8]], _read_only: bool| {
let name = argv[0];
[format!("${}\r\n", name.len()).as_bytes(), name, b"\r\n"].concat()
});
assert_eq!(b.eval(b"return redis.call('ECHO')", &[], &[]), b"$4\r\nECHO\r\n");Sourcepub fn set_instr_budget(&mut self, n: i64)
pub fn set_instr_budget(&mut self, n: i64)
Override the per-Vm instruction budget (~5 s ≈ 200 M instr by
default). 0 disables the cap (unlimited execution).
Setting it does NOT affect already-spawned VMs in the pool —
you can pair the call with Bridge::script_flush to force
a respawn under the new budget, or leave existing VMs as-is
and only catch new dialects.
let mut b = kevy_lua::Bridge::with_no_dispatch();
b.set_instr_budget(10_000);
// a runaway script is stopped and reported, not left spinning
assert!(b.eval(b"while true do end", &[], &[]).starts_with(b"-"));Sourcepub fn with_no_dispatch() -> Self
pub fn with_no_dispatch() -> Self
Bridge with a no-op dispatcher: every redis.call returns a
RESP error. Convenience for embedders that want EVAL but
don’t have the host dispatch wired yet (e.g. pure-computation
scripts during early development).
let mut b = kevy_lua::Bridge::with_no_dispatch();
assert_eq!(b.eval(b"return 6 * 7", &[], &[]), b":42\r\n");
assert!(b.eval(b"return redis.call('GET', 'k')", &[], &[]).starts_with(b"-"));Sourcepub fn set_allowed_dialects(&mut self, versions: &[LuaVersion])
pub fn set_allowed_dialects(&mut self, versions: &[LuaVersion])
Restrict which Lua dialects this bridge will spawn VMs for.
An EVAL with #!lua version=N for a non-allowed dialect is
rejected with a -ERR reply. The 5.1 default is always
accessible via scripts with no shebang regardless of this
setting (you can’t disable the ecosystem-default dialect
without taking a different with_allowed_dialects API).
Passing an empty slice = no restriction = all five dialects permitted (the constructor default).
use kevy_lua::{Bridge, LuaVersion};
let mut b = Bridge::with_no_dispatch();
b.set_allowed_dialects(&[LuaVersion::Lua51]);
assert!(b.eval(b"#!lua version=5.4\nreturn 1", &[], &[]).starts_with(b"-ERR"));
assert_eq!(b.eval(b"return 1", &[], &[]), b":1\r\n");Sourcepub fn eval(&mut self, script: &[u8], keys: &[&[u8]], args: &[&[u8]]) -> Reply
pub fn eval(&mut self, script: &[u8], keys: &[&[u8]], args: &[&[u8]]) -> Reply
Run a script and marshal its first return value into a RESP
reply: the #!lua version=N shebang picks the dialect (5.1 when
absent), KEYS and ARGV are bound for this call, and the script
enters the SHA1 cache before it runs, as Redis does. Every failure
— a bad shebang, a disabled dialect, a Lua error — is a RESP error
reply, never a panic.
let mut b = kevy_lua::Bridge::with_no_dispatch();
assert_eq!(b.eval(b"return 1", &[], &[]), b":1\r\n");
assert!(b.eval(b"error('boom')", &[], &[]).starts_with(b"-"));Sourcepub fn eval_ro(
&mut self,
script: &[u8],
keys: &[&[u8]],
args: &[&[u8]],
) -> Reply
pub fn eval_ro( &mut self, script: &[u8], keys: &[&[u8]], args: &[&[u8]], ) -> Reply
Read-only variant of Bridge::eval. The dispatcher receives
read_only = true for every redis.call from this script;
kevy-rt rejects write commands with
-READONLY can't write against a read-only script\r\n.
Redis 7.0+ EVAL_RO.
All other semantics (KEYS / ARGV / SHA1 cache fill /
dialect routing) are identical to eval.
// the host sees the read-only flag and refuses writes under it
let mut b = kevy_lua::Bridge::new(|argv: &[&[u8]], read_only: bool| {
if read_only && argv[0] == b"SET" {
b"-READONLY can't write against a read-only script\r\n".to_vec()
} else {
b"+OK\r\n".to_vec()
}
});
let script = b"return redis.pcall('SET', KEYS[1], 'v')";
assert_eq!(b.eval(script, &[b"k"], &[]), b"+OK\r\n");
assert!(b.eval_ro(script, &[b"k"], &[]).starts_with(b"-READONLY"));Sourcepub fn evalsha_ro(
&mut self,
sha1: ScriptSha1,
keys: &[&[u8]],
args: &[&[u8]],
) -> Reply
pub fn evalsha_ro( &mut self, sha1: ScriptSha1, keys: &[&[u8]], args: &[&[u8]], ) -> Reply
Read-only variant of Bridge::evalsha. Redis 7.0+ EVALSHA_RO.
let mut b = kevy_lua::Bridge::new(|_argv: &[&[u8]], read_only: bool| {
if read_only { b":1\r\n".to_vec() } else { b":0\r\n".to_vec() }
});
let sha = b.script_load(b"return redis.call('PING')");
assert_eq!(b.evalsha_ro(sha, &[], &[]), b":1\r\n");
assert_eq!(b.evalsha(sha, &[], &[]), b":0\r\n");Sourcepub fn evalsha(
&mut self,
sha1: ScriptSha1,
keys: &[&[u8]],
args: &[&[u8]],
) -> Reply
pub fn evalsha( &mut self, sha1: ScriptSha1, keys: &[&[u8]], args: &[&[u8]], ) -> Reply
Run a previously-cached script by SHA1 hex.
Returns -NOSCRIPT ... if the script isn’t in the cache.
Identical to running eval with the cached bytes — the same
shebang routing, KEYS/ARGV binding, and redis.* host plumbing
apply.
let mut b = kevy_lua::Bridge::with_no_dispatch();
let sha = b.script_load(b"return ARGV[1]");
assert_eq!(b.evalsha(sha, &[], &[b"hi"]), b"$2\r\nhi\r\n");
assert!(b.evalsha([0; 20], &[], &[]).starts_with(b"-NOSCRIPT"));Sourcepub fn script_load(&mut self, script: &[u8]) -> ScriptSha1
pub fn script_load(&mut self, script: &[u8]) -> ScriptSha1
Cache a script without running it. Returns the SHA1 digest; the operator-side wire layer hex-encodes it for the Redis SCRIPT LOAD reply.
let mut b = kevy_lua::Bridge::with_no_dispatch();
let sha = b.script_load(b"return 1");
assert_eq!(&kevy_lua::sha1::hex(&sha), b"e0e1f9fabfc9d4800c877a703b823ac0578ff8db");Sourcepub fn script_exists(&self, sha1s: &[ScriptSha1]) -> Vec<bool>
pub fn script_exists(&self, sha1s: &[ScriptSha1]) -> Vec<bool>
Test which of the given SHA1s are in the cache. Returns a
vector with true/false for each input SHA1 in order.
let mut b = kevy_lua::Bridge::with_no_dispatch();
let loaded = b.script_load(b"return 1");
assert_eq!(b.script_exists(&[loaded, [0; 20]]), [true, false]);Sourcepub fn script_flush(&mut self, _mode: FlushMode)
pub fn script_flush(&mut self, _mode: FlushMode)
Drop the SHA1 cache + all per-dialect VMs. ASYNC and SYNC
are currently both implemented as synchronous; the tag is
preserved for future differentiation.
let mut b = kevy_lua::Bridge::with_no_dispatch();
let sha = b.script_load(b"return 1");
b.script_flush(kevy_lua::FlushMode::default());
assert!(b.evalsha(sha, &[], &[]).starts_with(b"-NOSCRIPT"));Trait Implementations§
Source§impl Debug for Bridge
impl Debug for Bridge
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
fn fmt(&self, f: &mut Formatter<'_>) -> Result
Reports the bridge’s configuration and how much of it is live, without touching the VMs or the dispatch closure.
Neither can be printed: luna-core’s Vm has no Debug, and
dispatch is an Rc<dyn Fn> with no identity worth showing. The
VMs are reported as a count of spawned slots, which is the thing
worth knowing about them from outside — whether a dialect has been
used yet.