feat: add bounded task priority and fair application dispatch
This commit is contained in:
@@ -65,6 +65,7 @@ func TestIntegrationApplicationResourcesUpgrade(t *testing.T) {
|
||||
assertApplicationCounts(t, f, 4, 2, 1)
|
||||
// 还原后续新增表和字段,得到真实 v9 结构;现有任务内容保持不变。
|
||||
f.exec(`DROP TABLE orbit_application_resources`)
|
||||
f.exec(`ALTER TABLE pulse_tasks DROP INDEX ix_task_priority_dispatch, DROP CHECK ck_task_priority, DROP COLUMN dispatch_at`)
|
||||
f.exec(`ALTER TABLE pulse_tasks DROP CHECK ck_task_concurrency_key, DROP COLUMN concurrency_key, DROP COLUMN concurrency_key_digest`)
|
||||
f.exec(`ALTER TABLE pulse_attempts DROP COLUMN concurrency_token`)
|
||||
f.exec(`ALTER TABLE pulse_worker_sessions DROP COLUMN concurrency_keys`)
|
||||
|
||||
104
internal/storage/mysql/fair_dispatch_integration_test.go
Normal file
104
internal/storage/mysql/fair_dispatch_integration_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/transport/httpserver"
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/worker"
|
||||
)
|
||||
|
||||
func TestIntegrationFairDispatchApplicationIsolation(t *testing.T) {
|
||||
dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("requires disposable MySQL")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
|
||||
defer cancel()
|
||||
db, err := Open(ctx, dsn, 4, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
if err = Migrate(ctx, db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
noisy := newExecutionFixture(t, ctx, db)
|
||||
quiet := newExecutionFixture(t, ctx, db)
|
||||
if noisy.binding.ApplicationID != noisy.resources.ApplicationID || quiet.binding.ApplicationID != quiet.resources.ApplicationID {
|
||||
t.Fatal("dispatch group was not derived from authenticated namespace")
|
||||
}
|
||||
hub := httpserver.NewWorkerHub(ctx, noisy.a)
|
||||
server := httpserver.New("", func(context.Context) error { return nil })
|
||||
httpserver.RegisterWorkers(server, hub)
|
||||
tls := httptest.NewTLSServer(server.Handler)
|
||||
defer tls.Close()
|
||||
defer hub.Shutdown(ctx)
|
||||
var connections []*testWorkerSocket
|
||||
for range 12 {
|
||||
f := *noisy
|
||||
f.hello.InstanceID = executionID(t)
|
||||
c := connectTestWorker(t, ctx, tls, &f)
|
||||
id := c.send("worker.capacity", worker.CapacityInput{Sequence: 1, Capacity: 0}, "")
|
||||
if reply := c.read(); reply.ReplyTo != id || reply.Error != nil {
|
||||
t.Fatal("could not disable initial claims")
|
||||
}
|
||||
connections = append(connections, c)
|
||||
}
|
||||
quiet.hello.InstanceID = executionID(t)
|
||||
q := connectTestWorker(t, ctx, tls, quiet)
|
||||
id := q.send("worker.capacity", worker.CapacityInput{Sequence: 1, Capacity: 0}, "")
|
||||
if reply := q.read(); reply.ReplyTo != id || reply.Error != nil {
|
||||
t.Fatal("quiet worker setup failed")
|
||||
}
|
||||
noisy.submit(1)
|
||||
want := quiet.submit(1)
|
||||
lock, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer lock.Rollback()
|
||||
var ignored string
|
||||
if err = lock.QueryRowContext(ctx, `SELECT HEX(application_id) FROM orbit_application_resources WHERE application_id=? FOR UPDATE`, noisy.resources.ApplicationID).Scan(&ignored); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, c := range connections {
|
||||
id := c.send("worker.capacity", worker.CapacityInput{Sequence: 2, Capacity: 1}, "")
|
||||
if reply := c.read(); reply.ReplyTo != id || reply.Error != nil {
|
||||
t.Fatal("noisy worker control blocked by claims")
|
||||
}
|
||||
}
|
||||
// 等到真的有领取事务在额度行上等待,再验证另一应用;不靠固定 sleep 猜测故障已发生。
|
||||
for {
|
||||
var waiting int
|
||||
if err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM performance_schema.data_lock_waits`).Scan(&waiting); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if waiting > 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("held application did not receive a claim")
|
||||
case <-time.After(20 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
id = q.send("worker.capacity", worker.CapacityInput{Sequence: 2, Capacity: 1}, "")
|
||||
if reply := q.read(); reply.ReplyTo != id || reply.Error != nil {
|
||||
t.Fatal("other application heartbeat starved")
|
||||
}
|
||||
reply := q.read()
|
||||
var assignment worker.Assignment
|
||||
if reply.Type != "task.assign" || json.Unmarshal(reply.Body, &assignment) != nil || assignment.Task.ID != want.ID {
|
||||
t.Fatal("quiet application could not claim while noisy application was blocked")
|
||||
}
|
||||
// 此时锁仍未释放;静默应用的真实领取已提交,证明没有耗尽四连接数据库池。
|
||||
if err := lock.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("12 noisy connections blocked on one application; another application claimed with a four-connection database pool before lock release")
|
||||
}
|
||||
@@ -309,11 +309,12 @@ func TestIntegrationKeyedConcurrency(t *testing.T) {
|
||||
sf := newScheduleFixture(t, f)
|
||||
def := sf.definition()
|
||||
def.Task.ConcurrencyKey = "schedule/resource"
|
||||
def.Task.Priority = 7
|
||||
v := sf.create(def)
|
||||
sf.due(v, 2*time.Second)
|
||||
sf.tick()
|
||||
a, ref := f.claim()
|
||||
if a.Task.ConcurrencyKey != def.Task.ConcurrencyKey || a.ConcurrencyToken == 0 {
|
||||
if a.Task.ConcurrencyKey != def.Task.ConcurrencyKey || a.ConcurrencyToken == 0 || a.Task.Priority != 7 {
|
||||
t.Fatal("schedule omitted frozen key")
|
||||
}
|
||||
noKeyClaim(t, f)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 任务优先级只影响已到期任务的排序,最多提前 270 秒的排序优势;不提前执行延时任务。
|
||||
-- 生成列随受理、退避及恢复后的 available_at 自动变化,禁止独立修改。
|
||||
ALTER TABLE pulse_tasks
|
||||
MODIFY COLUMN priority SMALLINT NOT NULL DEFAULT 0 COMMENT '冻结任务优先级,0至9;每级带来30秒排序优势,不改变最早执行时间',
|
||||
ADD CONSTRAINT ck_task_priority CHECK (priority BETWEEN 0 AND 9),
|
||||
ADD COLUMN dispatch_at DATETIME(6) GENERATED ALWAYS AS (TIMESTAMPADD(SECOND, -30 * priority, available_at)) STORED COMMENT '派发排序时间,由最早执行时间减优先级乘30秒生成;不是执行授权或可用时间',
|
||||
ADD KEY ix_task_priority_dispatch(namespace_id,queue_id,state,dispatch_at,available_at,id);
|
||||
@@ -88,7 +88,7 @@ func TestIntegrationNoForeignKeysUpgrade(t *testing.T) {
|
||||
}
|
||||
indexes := func() []string {
|
||||
t.Helper()
|
||||
rows, err := db.QueryContext(ctx, `SELECT CONCAT_WS(':',TABLE_NAME,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,COLUMN_NAME,COALESCE(SUB_PART,0),INDEX_TYPE,IS_VISIBLE) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME<>'orbit_application_resources' ORDER BY TABLE_NAME,INDEX_NAME,SEQ_IN_INDEX`)
|
||||
rows, err := db.QueryContext(ctx, `SELECT CONCAT_WS(':',TABLE_NAME,INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,COLUMN_NAME,COALESCE(SUB_PART,0),INDEX_TYPE,IS_VISIBLE) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME<>'orbit_application_resources' AND INDEX_NAME<>'ix_task_priority_dispatch' ORDER BY TABLE_NAME,INDEX_NAME,SEQ_IN_INDEX`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -135,6 +135,14 @@ func TestIntegrationNoForeignKeysUpgrade(t *testing.T) {
|
||||
}
|
||||
result := make([]string, 0, len(cols))
|
||||
for i, value := range values {
|
||||
if cols[i] == "dispatch_at" {
|
||||
for j, col := range cols {
|
||||
if col == "available_at" && value != values[j] {
|
||||
t.Fatal("legacy default priority changed ordering")
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if cols[i] == "concurrency_key" || cols[i] == "concurrency_key_digest" {
|
||||
if value.Valid {
|
||||
t.Fatal("legacy task unexpectedly enabled keyed concurrency")
|
||||
|
||||
126
internal/storage/mysql/priority_integration_test.go
Normal file
126
internal/storage/mysql/priority_integration_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pulse "git.yuntuo.hk/k2safe/orbit/pulse/model"
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||
)
|
||||
|
||||
func TestIntegrationDispatchPriority(t *testing.T) {
|
||||
dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("requires disposable MySQL")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
db, err := Open(ctx, dsn, 16, 4)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
if err = Migrate(ctx, db); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
submit := func(f *executionFixture, p int16, max uint32) service.TaskView {
|
||||
t.Helper()
|
||||
out, err := f.tasks.Submit(ctx, f.token, string(executionID(t)), service.SubmitInput{QueueID: f.resources.QueueID, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{}`), Priority: p, Retry: pulse.RetryPolicy{MaxAttempts: max}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return out.Task
|
||||
}
|
||||
t.Run("priority_overtakes_recent_work_but_not_future_release", func(t *testing.T) {
|
||||
f := newExecutionFixture(t, ctx, db)
|
||||
low := submit(f, 0, 1)
|
||||
high := submit(f, 9, 1)
|
||||
later := submit(f, 9, 1)
|
||||
f.exec(`UPDATE pulse_tasks SET available_at=DATE_SUB(UTC_TIMESTAMP(6),INTERVAL 5 SECOND) WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, low.ID)
|
||||
f.exec(`UPDATE pulse_tasks SET available_at=DATE_ADD(UTC_TIMESTAMP(6),INTERVAL 1 HOUR) WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, later.ID)
|
||||
a, _ := f.claim()
|
||||
if a.Task.ID != high.ID || a.Task.Priority != 9 {
|
||||
t.Fatal("priority not honored")
|
||||
}
|
||||
a, _ = f.claim()
|
||||
if a.Task.ID != low.ID {
|
||||
t.Fatal("priority bypassed actual availability")
|
||||
}
|
||||
noKeyClaim(t, f)
|
||||
page, err := f.tasks.ListTasks(ctx, f.token, service.ListInput{QueueID: f.resources.QueueID, Limit: 20})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
found := false
|
||||
for _, v := range page.Items {
|
||||
if v.ID == high.ID {
|
||||
found = v.Priority == 9
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("summary lost frozen priority")
|
||||
}
|
||||
})
|
||||
t.Run("old_low_priority_cannot_be_overtaken_by_unbounded_new_high_work", func(t *testing.T) {
|
||||
f := newExecutionFixture(t, ctx, db)
|
||||
old := submit(f, 0, 1)
|
||||
f.exec(`UPDATE pulse_tasks SET available_at=DATE_SUB(UTC_TIMESTAMP(6),INTERVAL 271 SECOND) WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, old.ID)
|
||||
for range 24 {
|
||||
submit(f, 9, 1)
|
||||
}
|
||||
a, _ := f.claim()
|
||||
if a.Task.ID != old.ID {
|
||||
t.Fatal("aged task starved behind fresh high priority prefix")
|
||||
}
|
||||
})
|
||||
t.Run("retry_keeps_priority_and_recomputes_sort_time", func(t *testing.T) {
|
||||
f := newExecutionFixture(t, ctx, db)
|
||||
task := submit(f, 9, 2)
|
||||
first := retryStart(t, f)
|
||||
retryFinish(t, f, first, "FAILED", true)
|
||||
var gap int
|
||||
var priority int16
|
||||
if err := db.QueryRowContext(ctx, `SELECT TIMESTAMPDIFF(SECOND,dispatch_at,available_at),priority FROM pulse_tasks WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, task.ID).Scan(&gap, &priority); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if gap != 270 || priority != 9 {
|
||||
t.Fatal("retry lost priority or sort derivation")
|
||||
}
|
||||
noKeyClaim(t, f)
|
||||
f.exec(`UPDATE pulse_tasks SET available_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, task.ID)
|
||||
second := retryStart(t, f)
|
||||
retryFinish(t, f, second, "SUCCEEDED", false)
|
||||
if f.get(task.ID).Priority != 9 {
|
||||
t.Fatal("completed task lost priority")
|
||||
}
|
||||
})
|
||||
t.Run("identity_validation_and_database_constraint", func(t *testing.T) {
|
||||
f := newExecutionFixture(t, ctx, db)
|
||||
in := service.SubmitInput{QueueID: f.resources.QueueID, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{}`), Priority: 0}
|
||||
first, err := f.tasks.Submit(ctx, f.token, "same", in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dupe, err := f.tasks.Submit(ctx, f.token, "same", in)
|
||||
if err != nil || !dupe.Deduplicated || dupe.Task.ID != first.Task.ID {
|
||||
t.Fatal("default priority identity changed")
|
||||
}
|
||||
in.Priority = 9
|
||||
_, err = f.tasks.Submit(ctx, f.token, "same", in)
|
||||
expectWorkerCode(t, err, "IDEMPOTENCY_CONFLICT")
|
||||
for _, p := range []int16{-1, 10} {
|
||||
in.Priority = p
|
||||
_, err = f.tasks.Submit(ctx, f.token, string(executionID(t)), in)
|
||||
expectWorkerCode(t, err, "INVALID_ARGUMENT")
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, `UPDATE pulse_tasks SET priority=10 WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, first.Task.ID); err == nil {
|
||||
t.Fatal("database priority range unchecked")
|
||||
}
|
||||
if _, err := db.ExecContext(ctx, `UPDATE pulse_tasks SET dispatch_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=?`, f.resources.NamespaceID, first.Task.ID); err == nil {
|
||||
t.Fatal("generated dispatch order independently writable")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -224,7 +224,7 @@ func (s *ScheduleStore) saveOccurrence(ctx context.Context, tx *sql.Tx, v schedu
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,owner_kind,owner_id,available_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,'schedule',?,?,?,?,?,?,?)`, v.NamespaceID, value, v.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), v.ID, now, now.Add(time.Duration(in.TaskTimeoutMS)*time.Millisecond), in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now)
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,priority,owner_kind,owner_id,available_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?,'schedule',?,?,?,?,?,?,?)`, v.NamespaceID, value, v.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), in.Priority, v.ID, now, now.Add(time.Duration(in.TaskTimeoutMS)*time.Millisecond), in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -195,8 +195,8 @@ func (s *TaskStore) Submit(ctx context.Context, token auth.Token, key string, in
|
||||
if err := reserveQueueTask(ctx, tx, token.NamespaceID, in.QueueID); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,available_at,start_deadline_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?,?,?,?,?,?,?)`, token.NamespaceID, taskID, in.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), available, in.StartDeadlineAt, deadline, in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now)
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO pulse_tasks(namespace_id,id,queue_id,handler,handler_version,payload_version,payload,policy_snapshot,recovery_safety,business_key,concurrency_key,concurrency_key_digest,priority,available_at,start_deadline_at,deadline_at,attempt_timeout_ms,max_attempts,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,NULLIF(?,''),?,?,?,?,?,?,?,?,?,?)`, token.NamespaceID, taskID, in.QueueID, in.Handler, in.HandlerVersion, in.PayloadVersion, string(in.Payload), string(policy), in.RecoverySafety, in.BusinessKey, concurrencyKeyValue(in.ConcurrencyKey), concurrencyDigest(in.ConcurrencyKey), in.Priority, available, in.StartDeadlineAt, deadline, in.Retry.AttemptTimeoutMS, in.Retry.MaxAttempts, now, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -215,14 +215,14 @@ func (s *TaskStore) Submit(ctx context.Context, token auth.Token, key string, in
|
||||
return out, err
|
||||
}
|
||||
|
||||
const taskColumns = `id,namespace_id,queue_id,state,revision,handler,handler_version,payload_version,payload,result,COALESCE(business_key,''),COALESCE(concurrency_key,''),available_at,start_deadline_at,deadline_at,recovery_safety,policy_snapshot,attempt_count,cancel_requested_at,completed_at,created_at,updated_at,effect_uncertain`
|
||||
const taskColumns = `id,namespace_id,queue_id,state,revision,handler,handler_version,payload_version,payload,result,COALESCE(business_key,''),COALESCE(concurrency_key,''),priority,available_at,start_deadline_at,deadline_at,recovery_safety,policy_snapshot,attempt_count,cancel_requested_at,completed_at,created_at,updated_at,effect_uncertain`
|
||||
|
||||
type scanner interface{ Scan(...any) error }
|
||||
|
||||
func scanTask(row scanner) (service.TaskView, error) {
|
||||
var v service.TaskView
|
||||
var policy, payload, result []byte
|
||||
err := row.Scan(&v.ID, &v.NamespaceID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.PayloadVersion, &payload, &result, &v.BusinessKey, &v.ConcurrencyKey, &v.AvailableAt, &v.StartDeadlineAt, &v.DeadlineAt, &v.RecoverySafety, &policy, &v.AttemptCount, &v.CancelRequestedAt, &v.CompletedAt, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain)
|
||||
err := row.Scan(&v.ID, &v.NamespaceID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.PayloadVersion, &payload, &result, &v.BusinessKey, &v.ConcurrencyKey, &v.Priority, &v.AvailableAt, &v.StartDeadlineAt, &v.DeadlineAt, &v.RecoverySafety, &policy, &v.AttemptCount, &v.CancelRequestedAt, &v.CompletedAt, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
@@ -272,7 +272,7 @@ func (s *TaskStore) ListTasks(ctx context.Context, token auth.Token, in service.
|
||||
if !p.AllowsQueue(in.QueueID) {
|
||||
return fault.Forbidden
|
||||
}
|
||||
q := `SELECT id,queue_id,state,revision,handler,handler_version,COALESCE(business_key,''),COALESCE(concurrency_key,''),available_at,deadline_at,attempt_count,created_at,updated_at,effect_uncertain FROM pulse_tasks WHERE namespace_id=? AND queue_id=?`
|
||||
q := `SELECT id,queue_id,state,revision,handler,handler_version,COALESCE(business_key,''),COALESCE(concurrency_key,''),priority,available_at,deadline_at,attempt_count,created_at,updated_at,effect_uncertain FROM pulse_tasks WHERE namespace_id=? AND queue_id=?`
|
||||
args := []any{token.NamespaceID, in.QueueID}
|
||||
if in.State != "" {
|
||||
q += " AND state=?"
|
||||
@@ -296,7 +296,7 @@ func (s *TaskStore) ListTasks(ctx context.Context, token auth.Token, in service.
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var v service.TaskSummary
|
||||
if err := rows.Scan(&v.ID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.BusinessKey, &v.ConcurrencyKey, &v.AvailableAt, &v.DeadlineAt, &v.AttemptCount, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain); err != nil {
|
||||
if err := rows.Scan(&v.ID, &v.QueueID, &v.State, &v.Revision, &v.Handler, &v.HandlerVersion, &v.BusinessKey, &v.ConcurrencyKey, &v.Priority, &v.AvailableAt, &v.DeadlineAt, &v.AttemptCount, &v.CreatedAt, &v.UpdatedAt, &v.EffectUncertain); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Items = append(out.Items, v)
|
||||
|
||||
@@ -152,7 +152,7 @@ func (s *ExecutionStore) Claim(ctx context.Context, token auth.Token, b worker.B
|
||||
if !state.concurrencyKeys {
|
||||
keyFilter = "concurrency_key_digest IS NULL"
|
||||
}
|
||||
rows, err := tx.QueryContext(ctx, `SELECT id,gate_id FROM pulse_tasks WHERE namespace_id=? AND queue_id=? AND state='WAITING' AND available_at<=UTC_TIMESTAMP(6) AND deadline_at>UTC_TIMESTAMP(6) AND (start_deadline_at IS NULL OR start_deadline_at>UTC_TIMESTAMP(6)) AND attempt_count<max_attempts AND (gate_id IS NULL OR EXISTS (SELECT 1 FROM pulse_execution_gates g WHERE g.namespace_id=pulse_tasks.namespace_id AND g.id=pulse_tasks.gate_id AND g.paused=FALSE)) AND (`+strings.Join(matches, " OR ")+`) AND `+keyFilter+` ORDER BY available_at,priority DESC,id LIMIT 16`, args...)
|
||||
rows, err := tx.QueryContext(ctx, `SELECT id,gate_id FROM pulse_tasks WHERE namespace_id=? AND queue_id=? AND state='WAITING' AND available_at<=UTC_TIMESTAMP(6) AND deadline_at>UTC_TIMESTAMP(6) AND (start_deadline_at IS NULL OR start_deadline_at>UTC_TIMESTAMP(6)) AND attempt_count<max_attempts AND (gate_id IS NULL OR EXISTS (SELECT 1 FROM pulse_execution_gates g WHERE g.namespace_id=pulse_tasks.namespace_id AND g.id=pulse_tasks.gate_id AND g.paused=FALSE)) AND (`+strings.Join(matches, " OR ")+`) AND `+keyFilter+` ORDER BY dispatch_at,available_at,id LIMIT 16`, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ func (s *ExecutionStore) Connect(ctx context.Context, token auth.Token, in worke
|
||||
return err
|
||||
}
|
||||
binding = worker.Binding{InstanceID: in.InstanceID, SessionID: session, Epoch: epoch + 1, Proof: proof}
|
||||
if err := tx.QueryRowContext(ctx, `SELECT application_id FROM orbit_namespaces WHERE id=?`, token.NamespaceID).Scan(&binding.ApplicationID); err != nil {
|
||||
return err
|
||||
}
|
||||
state := sessionState{queue: in.QueueID, capacity: in.Capacity, draining: draining, expires: now.Add(s.options.SessionTTL), capabilities: in.Capabilities, concurrencyKeys: in.ConcurrencyKeys}
|
||||
caps, err := json.Marshal(in.Capabilities)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user