in-memory redis
redis in-memory testing
0

Configure Feed

Select the types of activity you want to include in your feed.

add ZREMRANGEBYSCORE

Required by docket's promote_due.lua to clear the queue ZSET after
moving due tasks into the stream. Without this command the script
EVALs hit "unknown command" mid-flight and the post-XADD cleanup is
skipped — the queue grows unbounded while docket still functions
(because XADD already landed) but the script returns an error each
tick.

Implementation: linear pass over by_score (it's sorted), bail when
score > max, remove from both by_score and by_member, free the owned
member bytes. Empties drop the key.

Also surfaces the offending command name on the unknown-command path
so we don't have to instrument the dep again next time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+43
+13
src/scripting.zig
··· 346 346 return 1; 347 347 } 348 348 349 + if (std.mem.eql(u8, cmd, "ZREMRANGEBYSCORE")) { 350 + if (args.len != 3) return errorReply(lua, pcall, "ERR wrong number of arguments for 'zremrangebyscore'"); 351 + const min = parseScoreBound(args[1]) catch 352 + return errorReply(lua, pcall, "ERR min is not a valid float"); 353 + const max = parseScoreBound(args[2]) catch 354 + return errorReply(lua, pcall, "ERR max is not a valid float"); 355 + const n = store.zremrangebyscore(args[0], min, max) catch |err| 356 + return errorReply(lua, pcall, redisErrorMsg(err)); 357 + lua.pushInteger(n); 358 + return 1; 359 + } 360 + 349 361 if (std.mem.eql(u8, cmd, "ZCARD")) { 350 362 if (args.len != 1) return errorReply(lua, pcall, "ERR wrong number of arguments for 'zcard'"); 351 363 const n = store.zcard(args[0]) catch |err| ··· 407 419 return 1; 408 420 } 409 421 422 + std.log.warn("burner-redis: unknown command '{s}' (docket-critical subset only)", .{cmd}); 410 423 return errorReply(lua, pcall, "ERR unknown command (docket-critical subset only)"); 411 424 } 412 425
+30
src/store.zig
··· 807 807 return entry.data.sorted_set.by_member.get(member); 808 808 } 809 809 810 + /// ZREMRANGEBYSCORE: remove all members with `min <= score <= max`. 811 + /// Returns count actually removed. Mirrors upstream `Store::zremrangebyscore`. 812 + pub fn zremrangebyscore(self: *Store, key: []const u8, min: f64, max: f64) StoreError!i64 { 813 + self.mutex.lockUncancelable(self.io); 814 + defer self.mutex.unlock(self.io); 815 + 816 + const entry = self.liveEntry(key) orelse return 0; 817 + if (entry.data != .sorted_set) return StoreError.WrongType; 818 + const zset = &entry.data.sorted_set; 819 + 820 + var removed: i64 = 0; 821 + var i: usize = 0; 822 + while (i < zset.by_score.items.len) { 823 + const sm = zset.by_score.items[i]; 824 + if (sm.score < min) { 825 + i += 1; 826 + continue; 827 + } 828 + if (sm.score > max) break; 829 + const owned_member = zset.by_member.getKey(sm.member).?; 830 + _ = zset.by_score.orderedRemove(i); 831 + _ = zset.by_member.remove(owned_member); 832 + self.allocator.free(owned_member); 833 + removed += 1; 834 + } 835 + 836 + if (zset.len() == 0) _ = self.removeKey(key); 837 + return removed; 838 + } 839 + 810 840 /// ZRANGEBYSCORE: members with `min <= score <= max`. Use ±inf for 811 841 /// unbounded. Caller owns the returned slice and the inner member bytes. 812 842 pub fn zrangebyscore(