feat: make runtime scanning tunable within safe bounds

This commit is contained in:
2026-09-20 18:56:09 +08:00
parent 6d194122af
commit df6773be47
8 changed files with 94 additions and 15 deletions

View File

@@ -6,19 +6,34 @@ import (
"net"
"os"
"strconv"
"time"
)
type Config struct {
HTTPAddr string
MySQLDSN string
MaxOpen int
MaxIdle int
MetricsAddr string
HTTPAddr string
MySQLDSN string
MaxOpen int
MaxIdle int
MetricsAddr string
WorkflowTickInterval time.Duration // WorkflowTickInterval Weave 持久化推进扫描间隔,范围100ms–1m。
ScheduleTickInterval time.Duration // ScheduleTickInterval 计划扫描间隔,范围100ms–1m。
RecoveryTickInterval time.Duration // RecoveryTickInterval 执行租约恢复扫描间隔,范围100ms–1m。
RuntimeBatchLimit int // RuntimeBatchLimit 每个后台批次的候选上限,范围1–100。
}
func Load() (Config, error) { return FromEnv(os.Getenv) }
func FromEnv(get func(string) string) (Config, error) {
c := Config{HTTPAddr: get("ORBIT_HTTP_ADDR"), MySQLDSN: get("ORBIT_MYSQL_DSN"), MaxOpen: 20, MaxIdle: 5, MetricsAddr: get("ORBIT_METRICS_ADDR")}
c := Config{
HTTPAddr: get("ORBIT_HTTP_ADDR"),
MySQLDSN: get("ORBIT_MYSQL_DSN"),
MaxOpen: 20,
MaxIdle: 5,
MetricsAddr: get("ORBIT_METRICS_ADDR"),
WorkflowTickInterval: time.Second,
ScheduleTickInterval: time.Second,
RecoveryTickInterval: 2 * time.Second,
RuntimeBatchLimit: 100,
}
if c.HTTPAddr == "" {
c.HTTPAddr = "127.0.0.1:8080"
}
@@ -43,6 +58,25 @@ func FromEnv(get func(string) string) (Config, error) {
if c.MaxOpen <= 0 || c.MaxIdle > c.MaxOpen {
return Config{}, errors.New("database pool requires 0 <= max_idle <= max_open and max_open > 0")
}
for _, field := range []struct {
name string
dst *time.Duration
}{{"ORBIT_WORKFLOW_TICK_INTERVAL", &c.WorkflowTickInterval}, {"ORBIT_SCHEDULE_TICK_INTERVAL", &c.ScheduleTickInterval}, {"ORBIT_RECOVERY_TICK_INTERVAL", &c.RecoveryTickInterval}} {
if raw := get(field.name); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil || d < 100*time.Millisecond || d > time.Minute {
return Config{}, fmt.Errorf("%s must be a duration from 100ms to 1m", field.name)
}
*field.dst = d
}
}
if raw := get("ORBIT_RUNTIME_BATCH_LIMIT"); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil || n < 1 || n > 100 {
return Config{}, errors.New("ORBIT_RUNTIME_BATCH_LIMIT must be an integer from 1 to 100")
}
c.RuntimeBatchLimit = n
}
if c.MetricsAddr != "" {
_, port, err := net.SplitHostPort(c.MetricsAddr)
n, parseErr := strconv.Atoi(port)

View File

@@ -1,6 +1,38 @@
package config
import "testing"
import (
"testing"
"time"
)
func TestRuntimeDefaultsAndBoundedOverrides(t *testing.T) {
base := map[string]string{"ORBIT_MYSQL_DSN": "private-dsn"}
get := func(k string) string { return base[k] }
c, err := FromEnv(get)
if err != nil || c.WorkflowTickInterval != time.Second || c.ScheduleTickInterval != time.Second || c.RecoveryTickInterval != 2*time.Second || c.RuntimeBatchLimit != 100 {
t.Fatalf("unexpected runtime defaults: %+v err=%v", c, err)
}
base["ORBIT_WORKFLOW_TICK_INTERVAL"] = "250ms"
base["ORBIT_SCHEDULE_TICK_INTERVAL"] = "1500ms"
base["ORBIT_RECOVERY_TICK_INTERVAL"] = "1m"
base["ORBIT_RUNTIME_BATCH_LIMIT"] = "37"
c, err = FromEnv(get)
if err != nil || c.WorkflowTickInterval != 250*time.Millisecond || c.ScheduleTickInterval != 1500*time.Millisecond || c.RecoveryTickInterval != time.Minute || c.RuntimeBatchLimit != 37 {
t.Fatalf("valid runtime override rejected: %+v err=%v", c, err)
}
for key, value := range map[string]string{
"ORBIT_WORKFLOW_TICK_INTERVAL": "99ms",
"ORBIT_SCHEDULE_TICK_INTERVAL": "2m",
"ORBIT_RECOVERY_TICK_INTERVAL": "not-a-duration",
"ORBIT_RUNTIME_BATCH_LIMIT": "101",
} {
base[key] = value
if _, err := FromEnv(get); err == nil {
t.Fatalf("invalid runtime override accepted: %s=%s", key, value)
}
delete(base, key)
}
}
func TestMetricsAddressIsExplicitAndValidated(t *testing.T) {
base := map[string]string{"ORBIT_MYSQL_DSN": "private-dsn"}