feat(tasks): implement atomic claims and leases

This commit is contained in:
QiuSW
2026-07-26 16:16:36 +08:00
parent 49db5b8305
commit ce875af889
50 changed files with 7589 additions and 207 deletions
+64
View File
@@ -15,10 +15,16 @@ const (
AssetDirectoryEnvironment = "CMROUBAO_ASSET_DIR"
TLSCertificateEnvironment = "CMROUBAO_TLS_CERT_FILE"
TLSPrivateKeyEnvironment = "CMROUBAO_TLS_KEY_FILE"
ClaimLeaseEnvironment = "CMROUBAO_CLAIM_LEASE"
RunningLeaseEnvironment = "CMROUBAO_RUNNING_LEASE"
ReadinessTTLEnvironment = "CMROUBAO_READINESS_TTL"
defaultHTTPAddress = "127.0.0.1:8080"
defaultDatabasePath = "var/cmroubao.db"
defaultAssetDirectory = "var/assets"
defaultClaimLease = 10 * time.Minute
defaultRunningLease = 90 * time.Second
defaultReadinessTTL = 2 * time.Minute
)
type LookupEnvironment func(string) (string, bool)
@@ -35,6 +41,9 @@ type Config struct {
IdleTimeout time.Duration
ShutdownTimeout time.Duration
MaxHeaderBytes int
ClaimLease time.Duration
RunningLease time.Duration
ReadinessTTL time.Duration
}
func Load(lookup LookupEnvironment) (Config, error) {
@@ -98,6 +107,36 @@ func Load(lookup LookupEnvironment) (Config, error) {
" must use loopback unless TLS is configured",
)
}
claimLease, err := durationEnvironment(
lookup,
ClaimLeaseEnvironment,
defaultClaimLease,
time.Minute,
30*time.Minute,
)
if err != nil {
return Config{}, err
}
runningLease, err := durationEnvironment(
lookup,
RunningLeaseEnvironment,
defaultRunningLease,
30*time.Second,
10*time.Minute,
)
if err != nil {
return Config{}, err
}
readinessTTL, err := durationEnvironment(
lookup,
ReadinessTTLEnvironment,
defaultReadinessTTL,
30*time.Second,
10*time.Minute,
)
if err != nil {
return Config{}, err
}
return Config{
HTTPAddress: httpAddress,
@@ -111,9 +150,34 @@ func Load(lookup LookupEnvironment) (Config, error) {
IdleTimeout: 60 * time.Second,
ShutdownTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ClaimLease: claimLease,
RunningLease: runningLease,
ReadinessTTL: readinessTTL,
}, nil
}
func durationEnvironment(
lookup LookupEnvironment,
name string,
defaultValue time.Duration,
minimum time.Duration,
maximum time.Duration,
) (time.Duration, error) {
value, exists := lookup(name)
if !exists {
return defaultValue, nil
}
value = strings.TrimSpace(value)
duration, err := time.ParseDuration(value)
if err != nil || duration < minimum || duration > maximum {
return 0, errors.New(
name + " must be a duration between " +
minimum.String() + " and " + maximum.String(),
)
}
return duration, nil
}
func cleanOptionalPath(value string) string {
if value == "" {
return ""
@@ -29,6 +29,16 @@ func TestLoadUsesSafeDefaults(t *testing.T) {
cfg.MaxHeaderBytes <= 0 {
t.Fatal("server safety limits must all be positive")
}
if cfg.ClaimLease != 10*time.Minute ||
cfg.RunningLease != 90*time.Second ||
cfg.ReadinessTTL != 2*time.Minute {
t.Fatalf(
"lifecycle durations = %s / %s / %s",
cfg.ClaimLease,
cfg.RunningLease,
cfg.ReadinessTTL,
)
}
if cfg.ShutdownTimeout > 30*time.Second {
t.Fatalf("ShutdownTimeout = %s", cfg.ShutdownTimeout)
}
@@ -41,6 +51,9 @@ func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
AssetDirectoryEnvironment: "tmp/assets",
TLSCertificateEnvironment: "tmp/server.crt",
TLSPrivateKeyEnvironment: "tmp/server.key",
ClaimLeaseEnvironment: "15m",
RunningLeaseEnvironment: "2m",
ReadinessTTLEnvironment: "3m",
}
cfg, err := Load(mapEnvironment(values))
@@ -65,6 +78,16 @@ func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
cfg.TLSPrivateKey,
)
}
if cfg.ClaimLease != 15*time.Minute ||
cfg.RunningLease != 2*time.Minute ||
cfg.ReadinessTTL != 3*time.Minute {
t.Fatalf(
"lifecycle durations = %s / %s / %s",
cfg.ClaimLease,
cfg.RunningLease,
cfg.ReadinessTTL,
)
}
}
func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
@@ -145,6 +168,24 @@ func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
TLSPrivateKeyEnvironment: " ",
},
},
{
name: "claim lease below minimum",
values: map[string]string{
ClaimLeaseEnvironment: "59s",
},
},
{
name: "running lease above maximum",
values: map[string]string{
RunningLeaseEnvironment: "11m",
},
},
{
name: "invalid readiness TTL",
values: map[string]string{
ReadinessTTLEnvironment: "soon",
},
},
}
for _, test := range tests {