perf(worker): 隔离异步等待登记与执行锁

为异步等待队列登记增加独立的进程内和 PostgreSQL advisory lock 域,使严格队列上限校验不再等待 Worker 执行槽事务的跨城同步提交。任务级锁保持不变,调度期间 active 与 waiting 总量守恒。\n\n新增 PostgreSQL 集成测试,验证执行 scope 锁被占用时等待登记仍可独立完成。\n\n验证:go vet ./...;go test ./... -count=1;隔离 PostgreSQL 集成测试;迁移安全检查;gofmt;git diff --check。
This commit is contained in:
2026-07-31 09:29:01 +08:00
parent 11ec057e6a
commit 387bf5842d
3 changed files with 138 additions and 4 deletions
+14 -4
View File
@@ -136,7 +136,7 @@ func (s *Store) QueueTaskAdmissionWithHook(
if input.Mode != "async" {
return TaskAdmission{}, errors.New("queued admission registration requires asynchronous mode")
}
return retryAdmissionOperation(ctx, admissionOperationLockKeys(input), func() (TaskAdmission, error) {
return retryAdmissionOperation(ctx, queuedAdmissionOperationLockKeys(input), func() (TaskAdmission, error) {
return s.queueTaskAdmissionOnce(ctx, input, onQueued)
})
}
@@ -188,8 +188,14 @@ WHERE id = $1::uuid`, input.TaskID).Scan(&taskActive); err != nil {
AdmissionScope{ScopeType: "user_group", ScopeKey: admission.UserGroupID},
)
}
// Waiting registration has its own cross-process lock domain. Registrations
// still serialize with each other for exact queue-limit checks, but they do
// not wait behind a Worker admission transaction holding execution scope
// locks through synchronous replication. Moving one task from waiting to
// active preserves active+waiting, so concurrent dispatch cannot make this
// capacity check over-admit.
for _, scope := range normalizedAdmissionScopes(lockScopes) {
if err := tryAdmissionTransactionLock(ctx, tx, admissionLockKey(scope)); err != nil {
if err := tryAdmissionTransactionLock(ctx, tx, admissionQueueRegistrationLockKey(scope)); err != nil {
return TaskAdmission{}, err
}
}
@@ -567,7 +573,7 @@ func (s *Store) RebindWaitingTaskAdmission(ctx context.Context, input TaskAdmiss
if err := validateTaskAdmissionInput(input); err != nil {
return TaskAdmission{}, err
}
return retryAdmissionOperation(ctx, admissionOperationLockKeys(input), func() (TaskAdmission, error) {
return retryAdmissionOperation(ctx, queuedAdmissionOperationLockKeys(input), func() (TaskAdmission, error) {
return s.rebindWaitingTaskAdmissionOnce(ctx, input)
})
}
@@ -598,7 +604,7 @@ func (s *Store) rebindWaitingTaskAdmissionOnce(ctx context.Context, input TaskAd
AdmissionScope{ScopeType: "user_group", ScopeKey: current.UserGroupID},
)
for _, scope := range normalizedAdmissionScopes(lockScopes) {
if err := tryAdmissionTransactionLock(ctx, tx, admissionLockKey(scope)); err != nil {
if err := tryAdmissionTransactionLock(ctx, tx, admissionQueueRegistrationLockKey(scope)); err != nil {
return TaskAdmission{}, err
}
}
@@ -1099,6 +1105,10 @@ func admissionLockKey(scope AdmissionScope) string {
return fmt.Sprintf("task-admission:%d:%s:%d:%s", len(scope.ScopeType), scope.ScopeType, len(scope.ScopeKey), scope.ScopeKey)
}
func admissionQueueRegistrationLockKey(scope AdmissionScope) string {
return fmt.Sprintf("task-admission-queue:%d:%s:%d:%s", len(scope.ScopeType), scope.ScopeType, len(scope.ScopeKey), scope.ScopeKey)
}
type admissionScopeState struct {
Scope AdmissionScope
Active float64