From bc52a4de5bec49aeb155d15ecae49bbab0884110 Mon Sep 17 00:00:00 2001 From: favalos Date: Fri, 2 Feb 2024 14:21:29 -0800 Subject: [PATCH 1/4] Change to REJECTD_TIMEDOUT. --- internal/app/coroutines/completePromise.go | 3 +- internal/app/subsystems/api/http/http_test.go | 40 +++++++++++++++ internal/app/subsystems/api/http/promise.go | 2 +- test/dst/dst.go | 2 +- test/dst/generator.go | 50 ++----------------- test/dst/model.go | 13 +++-- 6 files changed, 58 insertions(+), 52 deletions(-) diff --git a/internal/app/coroutines/completePromise.go b/internal/app/coroutines/completePromise.go index ddd5561e..85befb10 100644 --- a/internal/app/coroutines/completePromise.go +++ b/internal/app/coroutines/completePromise.go @@ -165,8 +165,9 @@ func CompletePromise(metadata *metadata.Metadata, req *t_api.Request, res func(* } else { status := t_api.ForbiddenStatus(p.State) strict := req.CompletePromise.Strict && p.State != req.CompletePromise.State + timeout := !req.CompletePromise.Strict && req.CompletePromise.State.In(promise.Timedout) - if !strict && p.IdempotencyKeyForComplete.Match(req.CompletePromise.IdempotencyKey) { + if (!strict && p.IdempotencyKeyForComplete.Match(req.CompletePromise.IdempotencyKey)) || timeout { status = t_api.StatusOK } diff --git a/internal/app/subsystems/api/http/http_test.go b/internal/app/subsystems/api/http/http_test.go index 1912a11e..17574b82 100644 --- a/internal/app/subsystems/api/http/http_test.go +++ b/internal/app/subsystems/api/http/http_test.go @@ -613,6 +613,46 @@ func TestHttpServer(t *testing.T) { }, status: 201, }, + { + name: "RejectTimeoutPromise", + path: "promises/foo/bar", + method: "PATCH", + headers: map[string]string{ + "Idempotency-Key": "bar", + "Strict": "true", + }, + body: []byte(`{ + "state": "REJECTED_TIMEDOUT", + "value": { + "headers": {"a":"a","b":"b","c":"c"}, + "data": "cmVqZWN0" + } + }`), + req: &t_api.Request{ + Kind: t_api.CompletePromise, + CompletePromise: &t_api.CompletePromiseRequest{ + Id: "foo/bar", + IdempotencyKey: util.ToPointer(idempotency.Key("bar")), + Strict: true, + State: promise.Timedout, + Value: promise.Value{ + Headers: map[string]string{"a": "a", "b": "b", "c": "c"}, + Data: []byte("reject"), + }, + }, + }, + res: &t_api.Response{ + Kind: t_api.CompletePromise, + CompletePromise: &t_api.CompletePromiseResponse{ + Status: t_api.StatusPromiseAlreadyTimedOut, + Promise: &promise.Promise{ + Id: "foo/bar", + State: promise.Timedout, + }, + }, + }, + status: 403, + }, { name: "ReadSchedule", path: "schedules/foo", diff --git a/internal/app/subsystems/api/http/promise.go b/internal/app/subsystems/api/http/promise.go index c4fe3a19..5b803561 100644 --- a/internal/app/subsystems/api/http/promise.go +++ b/internal/app/subsystems/api/http/promise.go @@ -121,7 +121,7 @@ func (s *server) completePromise(c *gin.Context) { err error ) - if !body.State.In(promise.Resolved | promise.Rejected | promise.Canceled) { + if !body.State.In(promise.Resolved | promise.Rejected | promise.Canceled | promise.Timedout) { c.JSON(http.StatusBadRequest, api.HandleValidationError(errors.New("invalid state"))) return } diff --git a/test/dst/dst.go b/test/dst/dst.go index 68e664c3..4904377e 100644 --- a/test/dst/dst.go +++ b/test/dst/dst.go @@ -58,7 +58,7 @@ func (d *DST) Run(r *rand.Rand, api api.API, aio aio.AIO, system *system.System, generator.AddRequest(generator.GenerateCreatePromise) model.AddResponse(t_api.CreatePromise, model.ValidatCreatePromise) case t_api.CompletePromise: - generator.AddRequest(generator.GenerateCancelPromise) + generator.AddRequest(generator.GenerateCompletePromise) model.AddResponse(t_api.CompletePromise, model.ValidateCompletePromise) // SCHEDULES diff --git a/test/dst/generator.go b/test/dst/generator.go index de47d077..01bc746f 100644 --- a/test/dst/generator.go +++ b/test/dst/generator.go @@ -2,6 +2,7 @@ package dst import ( "fmt" + "math" "math/rand" // nosemgrep "strconv" @@ -193,62 +194,19 @@ func (g *Generator) GenerateCreatePromise(r *rand.Rand, t int64) *t_api.Request } } -func (g *Generator) GenerateCancelPromise(r *rand.Rand, t int64) *t_api.Request { +func (g *Generator) GenerateCompletePromise(r *rand.Rand, t int64) *t_api.Request { id := g.idSet[r.Intn(len(g.idSet))] idempotencyKey := g.idemotencyKeySet[r.Intn(len(g.idemotencyKeySet))] data := g.dataSet[r.Intn(len(g.dataSet))] headers := g.headersSet[r.Intn(len(g.headersSet))] strict := r.Intn(2) == 0 + state := promise.State(math.Exp2(float64(r.Intn(4) + 1))) return &t_api.Request{ Kind: t_api.CompletePromise, CompletePromise: &t_api.CompletePromiseRequest{ Id: id, - State: promise.Canceled, - Value: promise.Value{ - Headers: headers, - Data: data, - }, - IdempotencyKey: idempotencyKey, - Strict: strict, - }, - } -} - -func (g *Generator) GenerateResolvePromise(r *rand.Rand, t int64) *t_api.Request { - id := g.idSet[r.Intn(len(g.idSet))] - idempotencyKey := g.idemotencyKeySet[r.Intn(len(g.idemotencyKeySet))] - data := g.dataSet[r.Intn(len(g.dataSet))] - headers := g.headersSet[r.Intn(len(g.headersSet))] - strict := r.Intn(2) == 0 - - return &t_api.Request{ - Kind: t_api.CompletePromise, - CompletePromise: &t_api.CompletePromiseRequest{ - Id: id, - State: promise.Resolved, - Value: promise.Value{ - Headers: headers, - Data: data, - }, - IdempotencyKey: idempotencyKey, - Strict: strict, - }, - } -} - -func (g *Generator) GenerateRejectPromise(r *rand.Rand, t int64) *t_api.Request { - id := g.idSet[r.Intn(len(g.idSet))] - idempotencyKey := g.idemotencyKeySet[r.Intn(len(g.idemotencyKeySet))] - data := g.dataSet[r.Intn(len(g.dataSet))] - headers := g.headersSet[r.Intn(len(g.headersSet))] - strict := r.Intn(2) == 0 - - return &t_api.Request{ - Kind: t_api.CompletePromise, - CompletePromise: &t_api.CompletePromiseRequest{ - Id: id, - State: promise.Rejected, + State: state, Value: promise.Value{ Headers: headers, Data: data, diff --git a/test/dst/model.go b/test/dst/model.go index 94649053..e3bc8654 100644 --- a/test/dst/model.go +++ b/test/dst/model.go @@ -252,9 +252,10 @@ func (m *Model) ValidateCompletePromise(t int64, req *t_api.Request, res *t_api. switch res.CompletePromise.Status { case t_api.StatusOK: if pm.completed() { - if !pm.idempotencyKeyForCompleteMatch(res.CompletePromise.Promise) { + if !(!req.CompletePromise.Strict && req.CompletePromise.State.In(promise.Timedout)) && + !pm.idempotencyKeyForCompleteMatch(res.CompletePromise.Promise) { return fmt.Errorf("ikey mismatch (%s, %s)", pm.promise.IdempotencyKeyForComplete, res.CompletePromise.Promise.IdempotencyKeyForComplete) - } else if req.CompletePromise.Strict && pm.promise.State != promise.Canceled { + } else if req.CompletePromise.Strict && pm.promise.State != req.CompletePromise.State { return fmt.Errorf("unexpected state %s when strict true", pm.promise.State) } } @@ -268,7 +269,13 @@ func (m *Model) ValidateCompletePromise(t int64, req *t_api.Request, res *t_api. pm.promise = res.CompletePromise.Promise return nil case t_api.StatusCreated: - if res.CompletePromise.Promise.State != promise.Canceled { + if req.CompletePromise.State.In(promise.Resolved) && res.CompletePromise.Promise.State != promise.Resolved { + return fmt.Errorf("unexpected state %s after resolve promise", res.CompletePromise.Promise.State) + } + if req.CompletePromise.State.In(promise.Rejected) && res.CompletePromise.Promise.State != promise.Rejected { + return fmt.Errorf("unexpected state %s after reject promise", res.CompletePromise.Promise.State) + } + if req.CompletePromise.State.In(promise.Canceled) && res.CompletePromise.Promise.State != promise.Canceled { return fmt.Errorf("unexpected state %s after cancel promise", res.CompletePromise.Promise.State) } if pm.completed() { From 4687d81c33608d2c727e7a664086ede423c943fb Mon Sep 17 00:00:00 2001 From: favalos Date: Sat, 3 Feb 2024 18:55:00 -0800 Subject: [PATCH 2/4] Changes to not allow timeout on request. --- internal/app/coroutines/completePromise.go | 2 +- internal/app/subsystems/api/http/http_test.go | 40 ------------------- internal/app/subsystems/api/http/promise.go | 2 +- test/dst/model.go | 2 +- 4 files changed, 3 insertions(+), 43 deletions(-) diff --git a/internal/app/coroutines/completePromise.go b/internal/app/coroutines/completePromise.go index 85befb10..7a1c7968 100644 --- a/internal/app/coroutines/completePromise.go +++ b/internal/app/coroutines/completePromise.go @@ -165,7 +165,7 @@ func CompletePromise(metadata *metadata.Metadata, req *t_api.Request, res func(* } else { status := t_api.ForbiddenStatus(p.State) strict := req.CompletePromise.Strict && p.State != req.CompletePromise.State - timeout := !req.CompletePromise.Strict && req.CompletePromise.State.In(promise.Timedout) + timeout := !req.CompletePromise.Strict && p.State == promise.Timedout if (!strict && p.IdempotencyKeyForComplete.Match(req.CompletePromise.IdempotencyKey)) || timeout { status = t_api.StatusOK diff --git a/internal/app/subsystems/api/http/http_test.go b/internal/app/subsystems/api/http/http_test.go index 17574b82..1912a11e 100644 --- a/internal/app/subsystems/api/http/http_test.go +++ b/internal/app/subsystems/api/http/http_test.go @@ -613,46 +613,6 @@ func TestHttpServer(t *testing.T) { }, status: 201, }, - { - name: "RejectTimeoutPromise", - path: "promises/foo/bar", - method: "PATCH", - headers: map[string]string{ - "Idempotency-Key": "bar", - "Strict": "true", - }, - body: []byte(`{ - "state": "REJECTED_TIMEDOUT", - "value": { - "headers": {"a":"a","b":"b","c":"c"}, - "data": "cmVqZWN0" - } - }`), - req: &t_api.Request{ - Kind: t_api.CompletePromise, - CompletePromise: &t_api.CompletePromiseRequest{ - Id: "foo/bar", - IdempotencyKey: util.ToPointer(idempotency.Key("bar")), - Strict: true, - State: promise.Timedout, - Value: promise.Value{ - Headers: map[string]string{"a": "a", "b": "b", "c": "c"}, - Data: []byte("reject"), - }, - }, - }, - res: &t_api.Response{ - Kind: t_api.CompletePromise, - CompletePromise: &t_api.CompletePromiseResponse{ - Status: t_api.StatusPromiseAlreadyTimedOut, - Promise: &promise.Promise{ - Id: "foo/bar", - State: promise.Timedout, - }, - }, - }, - status: 403, - }, { name: "ReadSchedule", path: "schedules/foo", diff --git a/internal/app/subsystems/api/http/promise.go b/internal/app/subsystems/api/http/promise.go index 5b803561..c4fe3a19 100644 --- a/internal/app/subsystems/api/http/promise.go +++ b/internal/app/subsystems/api/http/promise.go @@ -121,7 +121,7 @@ func (s *server) completePromise(c *gin.Context) { err error ) - if !body.State.In(promise.Resolved | promise.Rejected | promise.Canceled | promise.Timedout) { + if !body.State.In(promise.Resolved | promise.Rejected | promise.Canceled) { c.JSON(http.StatusBadRequest, api.HandleValidationError(errors.New("invalid state"))) return } diff --git a/test/dst/model.go b/test/dst/model.go index e3bc8654..bffcaa0f 100644 --- a/test/dst/model.go +++ b/test/dst/model.go @@ -252,7 +252,7 @@ func (m *Model) ValidateCompletePromise(t int64, req *t_api.Request, res *t_api. switch res.CompletePromise.Status { case t_api.StatusOK: if pm.completed() { - if !(!req.CompletePromise.Strict && req.CompletePromise.State.In(promise.Timedout)) && + if !(!req.CompletePromise.Strict && pm.promise.State == promise.Timedout) && !pm.idempotencyKeyForCompleteMatch(res.CompletePromise.Promise) { return fmt.Errorf("ikey mismatch (%s, %s)", pm.promise.IdempotencyKeyForComplete, res.CompletePromise.Promise.IdempotencyKeyForComplete) } else if req.CompletePromise.Strict && pm.promise.State != req.CompletePromise.State { From 6b98a5d0edab06dcc702d71318d73737d2603917 Mon Sep 17 00:00:00 2001 From: favalos Date: Sat, 3 Feb 2024 20:01:59 -0800 Subject: [PATCH 3/4] Re-order to enable only Resolved, Reject and Canceled in generator. --- .../subsystems/aio/store/postgres/postgres.go | 2 +- .../app/subsystems/aio/store/sqlite/sqlite.go | 2 +- .../app/subsystems/aio/store/test/cases.go | 18 +++++++++--------- pkg/promise/promise.go | 10 +++++----- test/dst/generator.go | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/app/subsystems/aio/store/postgres/postgres.go b/internal/app/subsystems/aio/store/postgres/postgres.go index f7f8a899..4446b18f 100644 --- a/internal/app/subsystems/aio/store/postgres/postgres.go +++ b/internal/app/subsystems/aio/store/postgres/postgres.go @@ -196,7 +196,7 @@ const ( UPDATE promises SET - state = 8, completed_on = timeout + state = 16, completed_on = timeout WHERE state = 1 AND timeout <= $1` diff --git a/internal/app/subsystems/aio/store/sqlite/sqlite.go b/internal/app/subsystems/aio/store/sqlite/sqlite.go index 5d9f5b47..6a5ce103 100644 --- a/internal/app/subsystems/aio/store/sqlite/sqlite.go +++ b/internal/app/subsystems/aio/store/sqlite/sqlite.go @@ -185,7 +185,7 @@ const ( UPDATE promises SET - state = 8, completed_on = timeout + state = 16, completed_on = timeout WHERE state = 1 AND timeout <= ?` diff --git a/internal/app/subsystems/aio/store/test/cases.go b/internal/app/subsystems/aio/store/test/cases.go index 7f24ffb7..c74dd6b4 100644 --- a/internal/app/subsystems/aio/store/test/cases.go +++ b/internal/app/subsystems/aio/store/test/cases.go @@ -1773,7 +1773,7 @@ var TestCases = []*testCase{ Kind: t_aio.UpdatePromise, UpdatePromise: &t_aio.UpdatePromiseCommand{ Id: "qux", - State: 8, + State: promise.Timedout, Value: promise.Value{ Headers: map[string]string{}, Data: []byte{}, @@ -1799,7 +1799,7 @@ var TestCases = []*testCase{ Kind: t_aio.UpdatePromise, UpdatePromise: &t_aio.UpdatePromiseCommand{ Id: "quy", - State: 16, + State: promise.Canceled, Value: promise.Value{ Headers: map[string]string{}, Data: []byte{}, @@ -1978,7 +1978,7 @@ var TestCases = []*testCase{ Records: []*promise.PromiseRecord{ { Id: "quy", - State: 16, + State: 8, ParamHeaders: []byte("{}"), ParamData: []byte{}, ValueHeaders: []byte("{}"), @@ -1991,7 +1991,7 @@ var TestCases = []*testCase{ }, { Id: "qux", - State: 8, + State: 16, ParamHeaders: []byte("{}"), ParamData: []byte{}, ValueHeaders: []byte("{}"), @@ -2026,7 +2026,7 @@ var TestCases = []*testCase{ Records: []*promise.PromiseRecord{ { Id: "quy", - State: 16, + State: 8, ParamHeaders: []byte("{}"), ParamData: []byte{}, ValueHeaders: []byte("{}"), @@ -2039,7 +2039,7 @@ var TestCases = []*testCase{ }, { Id: "qux", - State: 8, + State: 16, ParamHeaders: []byte("{}"), ParamData: []byte{}, ValueHeaders: []byte("{}"), @@ -3449,7 +3449,7 @@ var TestCases = []*testCase{ Records: []*promise.PromiseRecord{ { Id: "baz", - State: 8, + State: 16, ParamHeaders: []byte("{}"), ParamData: []byte{}, Timeout: 2, @@ -3460,7 +3460,7 @@ var TestCases = []*testCase{ }, { Id: "bar", - State: 8, + State: 16, ParamHeaders: []byte("{}"), ParamData: []byte{}, Timeout: 2, @@ -3471,7 +3471,7 @@ var TestCases = []*testCase{ }, { Id: "foo", - State: 8, + State: 16, ParamHeaders: []byte("{}"), ParamData: []byte{}, Timeout: 2, diff --git a/pkg/promise/promise.go b/pkg/promise/promise.go index ac3ad0ae..3c5fc2bf 100644 --- a/pkg/promise/promise.go +++ b/pkg/promise/promise.go @@ -42,8 +42,8 @@ const ( Pending State = 1 << iota Resolved Rejected - Timedout Canceled + Timedout ) func (s State) String() string { @@ -54,10 +54,10 @@ func (s State) String() string { return "RESOLVED" case Rejected: return "REJECTED" - case Timedout: - return "REJECTED_TIMEDOUT" case Canceled: return "REJECTED_CANCELED" + case Timedout: + return "REJECTED_TIMEDOUT" default: panic("invalid state") } @@ -80,10 +80,10 @@ func (s *State) UnmarshalJSON(data []byte) error { *s = Resolved case "REJECTED": *s = Rejected - case "REJECTED_TIMEDOUT": - *s = Timedout case "REJECTED_CANCELED": *s = Canceled + case "REJECTED_TIMEDOUT": + *s = Timedout default: return fmt.Errorf("invalid state '%s'", state) } diff --git a/test/dst/generator.go b/test/dst/generator.go index 01bc746f..f76b7e83 100644 --- a/test/dst/generator.go +++ b/test/dst/generator.go @@ -200,7 +200,7 @@ func (g *Generator) GenerateCompletePromise(r *rand.Rand, t int64) *t_api.Reques data := g.dataSet[r.Intn(len(g.dataSet))] headers := g.headersSet[r.Intn(len(g.headersSet))] strict := r.Intn(2) == 0 - state := promise.State(math.Exp2(float64(r.Intn(4) + 1))) + state := promise.State(math.Exp2(float64(r.Intn(3) + 1))) return &t_api.Request{ Kind: t_api.CompletePromise, From b8e9501b136ea880392ea6c6c1ab53f413246946 Mon Sep 17 00:00:00 2001 From: favalos Date: Sun, 4 Feb 2024 17:09:43 -0800 Subject: [PATCH 4/4] Changes to the if statements. --- test/dst/model.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/dst/model.go b/test/dst/model.go index bffcaa0f..048e56df 100644 --- a/test/dst/model.go +++ b/test/dst/model.go @@ -252,8 +252,8 @@ func (m *Model) ValidateCompletePromise(t int64, req *t_api.Request, res *t_api. switch res.CompletePromise.Status { case t_api.StatusOK: if pm.completed() { - if !(!req.CompletePromise.Strict && pm.promise.State == promise.Timedout) && - !pm.idempotencyKeyForCompleteMatch(res.CompletePromise.Promise) { + if !pm.idempotencyKeyForCompleteMatch(res.CompletePromise.Promise) && + (req.CompletePromise.Strict || pm.promise.State != promise.Timedout) { return fmt.Errorf("ikey mismatch (%s, %s)", pm.promise.IdempotencyKeyForComplete, res.CompletePromise.Promise.IdempotencyKeyForComplete) } else if req.CompletePromise.Strict && pm.promise.State != req.CompletePromise.State { return fmt.Errorf("unexpected state %s when strict true", pm.promise.State) @@ -269,13 +269,13 @@ func (m *Model) ValidateCompletePromise(t int64, req *t_api.Request, res *t_api. pm.promise = res.CompletePromise.Promise return nil case t_api.StatusCreated: - if req.CompletePromise.State.In(promise.Resolved) && res.CompletePromise.Promise.State != promise.Resolved { + if req.CompletePromise.State == promise.Resolved && res.CompletePromise.Promise.State != promise.Resolved { return fmt.Errorf("unexpected state %s after resolve promise", res.CompletePromise.Promise.State) } - if req.CompletePromise.State.In(promise.Rejected) && res.CompletePromise.Promise.State != promise.Rejected { + if req.CompletePromise.State == promise.Rejected && res.CompletePromise.Promise.State != promise.Rejected { return fmt.Errorf("unexpected state %s after reject promise", res.CompletePromise.Promise.State) } - if req.CompletePromise.State.In(promise.Canceled) && res.CompletePromise.Promise.State != promise.Canceled { + if req.CompletePromise.State == promise.Canceled && res.CompletePromise.Promise.State != promise.Canceled { return fmt.Errorf("unexpected state %s after cancel promise", res.CompletePromise.Promise.State) } if pm.completed() {