summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 11:06:13 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 11:06:13 +0200
commitd1edd420463cdda399d8acb8124b137cbfd7a898 (patch)
treee7a7a94ce764793d39e63e8fac6f5013177b0e8f
parent1f21f4c90d2f99f83b5718650e5f8e487a4e1218 (diff)
Sample primary repeat timeout
-rw-r--r--lua/clever_f/repeat_resolver.lua36
-rw-r--r--tests/run.lua17
2 files changed, 53 insertions, 0 deletions
diff --git a/lua/clever_f/repeat_resolver.lua b/lua/clever_f/repeat_resolver.lua
index e246ef8..5e1dc41 100644
--- a/lua/clever_f/repeat_resolver.lua
+++ b/lua/clever_f/repeat_resolver.lua
@@ -18,6 +18,22 @@ local function fail(message, level)
error(message, (level or 1) + 1)
end
+local function is_nonnegative_integer(value)
+ return type(value) == "number"
+ and value >= 0
+ and value < math.huge
+ and value == math.floor(value)
+end
+
+local function require_policy(service)
+ if service ~= nil and (type(service) ~= "table"
+ or type(service.sample_timeouts) ~= "function")
+ then
+ fail("RepeatResolver policy must provide sample_timeouts", 3)
+ end
+ return service
+end
+
local resolver_metatable = {
__index = RepeatResolver,
__newindex = function()
@@ -48,6 +64,7 @@ function RepeatResolver.new(options)
local resolver = setmetatable({}, resolver_metatable)
resolver_records[resolver] = {
+ policy = require_policy(options.policy or options.policy_service),
state = state,
}
return resolver
@@ -77,6 +94,21 @@ end
RepeatResolver.eligibility = RepeatResolver.decide
RepeatResolver.resolve_eligibility = RepeatResolver.decide
+function RepeatResolver:sample_repeat_timeout_ms()
+ local service = resolver_records[self].policy
+ if service == nil then
+ fail("RepeatResolver requires a policy to sample repeat timeout", 2)
+ end
+ local sampled = service:sample_timeouts()
+ local timeout = type(sampled) == "table" and sampled.repeat_timeout_ms or nil
+ if not is_nonnegative_integer(timeout) then
+ fail("repeat_timeout_ms sample must be a nonnegative integer", 2)
+ end
+ return timeout
+end
+
+RepeatResolver.sample_repeat_timeout = RepeatResolver.sample_repeat_timeout_ms
+
function M.new(options)
return RepeatResolver.new(options)
end
@@ -95,6 +127,10 @@ end
M.eligibility = M.decide
+function M.sample_repeat_timeout_ms(options)
+ return RepeatResolver.new(options):sample_repeat_timeout_ms()
+end
+
setmetatable(M, {
__call = function(_, options)
return RepeatResolver.new(options)
diff --git a/tests/run.lua b/tests/run.lua
index b010908..a2bf7e1 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -3843,6 +3843,23 @@ test("Primary eligibility repeats at a matching numeric landing", function()
)
end)
+test("Repeated primary timeout is sampled from live policy", function()
+ fresh_sequence_state()
+ local host = MemoryHost.new({
+ configuration = { repeat_timeout_ms = 75 },
+ })
+ local resolver = repeat_resolver.new({
+ policy = policy.new(host),
+ })
+
+ same(75, resolver:sample_repeat_timeout_ms())
+ host:set_configuration("repeat_timeout_ms", 125)
+ same(125, resolver:sample_repeat_timeout_ms())
+ fails(function()
+ repeat_resolver.new():sample_repeat_timeout_ms()
+ end, "requires a policy")
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then