From 7c9b627a8267fc095f83bb5c07c1b19175b228f4 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 10 May 2026 22:04:12 +0200 Subject: [PATCH 01/10] feat: migrate to Connect-RPC; new wire path /lock.v1.LockService/ Plugin now exposes the generated lockV1connect.LockService handler instead of the legacy net/rpc + goridge codec methods. RPC() returns (path, http.Handler) so the rpc plugin's HTTP/2 mux mounts it directly at /lock.v1.LockService/. - plugin.go: RPC() any -> RPC() (string, http.Handler) - rpc.go: 6 methods adapted to connect.Request/Response shapes; caller's ctx now bounds the locker acquire (was Background); empty ID returns CodeInvalidArgument - tests: shared http2 transport via sync.OnceValue pools idle conns across the 1700-goroutine TestLockInit; address arg removed (always 127.0.0.1:6001) - deps: api-go v6.0.0-beta.5, connect v1.19.2, goridge dropped - lint: dead dupl settings block removed --- .golangci.yml | 2 - go.mod | 14 +++- go.sum | 40 ++++++++- go.work.sum | 8 +- plugin.go | 12 +-- rpc.go | 196 +++++++++++++++++-------------------------- tests/go.mod | 10 ++- tests/go.sum | 44 ++++++++-- tests/lock_test.go | 91 ++++++++++---------- tests/rpc.go | 202 +++++++++++++++++++++------------------------ 10 files changed, 316 insertions(+), 303 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6903d2f..47848e7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,8 +31,6 @@ linters: - unused - whitespace settings: - dupl: - threshold: 100 godot: scope: declarations capital: true diff --git a/go.mod b/go.mod index 2362b24..59e7865 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,16 @@ go 1.26 toolchain go1.26.0 -require github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4 +require ( + connectrpc.com/connect v1.19.2 + github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 +) -require google.golang.org/protobuf v1.36.11 // indirect +require ( + golang.org/x/net v0.54.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect + google.golang.org/grpc v1.81.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/go.sum b/go.sum index a3f10d1..ef368ed 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,42 @@ +connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo= +connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4 h1:wX8IezPUeeBJzlzaBEFSZBE5Bc/Le1Uf/GdFRdFO3HQ= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4/go.mod h1:jI30i64yCAxJh7KHc8e1B8NgDcvcnSTI1OIK8lTE+Y0= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 h1:4x17K8qmxIbtKrCKoY1NR7bBvJbrB7w0P/0ovYR8C6E= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/go.work.sum b/go.work.sum index 7765dbe..2561b5f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -46,8 +46,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= @@ -71,8 +69,6 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -86,7 +82,6 @@ github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+Licev github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -101,8 +96,6 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8 github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ= @@ -290,6 +283,7 @@ go.temporal.io/api v1.35.0 h1:+1o2zyBncLjnpjJt4FedKZMtuUav/LUgTB+mhQxx0zs= go.temporal.io/api v1.35.0/go.mod h1:OYkuupuCw6s/5TkcKHMb9EcIrOI+vTsbf/CGaprbzb0= go.temporal.io/api v1.36.0/go.mod h1:0nWIrFRVPlcrkopXqxir/UWOtz/NZCo+EE9IX4UwVxw= go.temporal.io/api v1.49.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.62.11/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= diff --git a/plugin.go b/plugin.go index d9cb817..af812a9 100644 --- a/plugin.go +++ b/plugin.go @@ -3,6 +3,9 @@ package lock import ( "context" "log/slog" + "net/http" + + "github.com/roadrunner-server/api-go/v6/lock/v1/lockV1connect" ) const pluginName string = "lock" @@ -40,9 +43,8 @@ func (p *Plugin) Name() string { return pluginName } -func (p *Plugin) RPC() any { - return &rpc{ - log: p.log, - pl: p, - } +// RPC returns the Connect-RPC service handler for lock.v1.LockService. +// The rpc plugin mounts the returned handler at the returned path on its HTTP/2 mux. +func (p *Plugin) RPC() (string, http.Handler) { + return lockV1connect.NewLockServiceHandler(&rpc{pl: p, log: p.log}) } diff --git a/rpc.go b/rpc.go index 0ead95a..bc77236 100644 --- a/rpc.go +++ b/rpc.go @@ -2,171 +2,123 @@ package lock import ( "context" - "errors" + stderr "errors" "log/slog" "time" - lockApi "github.com/roadrunner-server/api-go/v6/lock/v1" + "connectrpc.com/connect" + lockV1 "github.com/roadrunner-server/api-go/v6/lock/v1" ) const defaultImmediateTimeout = time.Millisecond +// errEmptyID surfaces as connect.CodeInvalidArgument; all RPCs except ForceRelease +// require a caller-supplied ID to attribute ownership of the resource. +var errEmptyID = stderr.New("empty ID is not allowed") + type rpc struct { log *slog.Logger pl *Plugin } -func (r *rpc) Lock(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("lock request received", "ttl", int(req.GetTtl()), "wait_ttl", int(req.GetWait()), "resource", req.GetResource(), "id", req.GetId()) - - if req.GetId() == "" { - return errors.New("empty ID is not allowed") +// waitContext bounds the locker call by req.Wait microseconds, falling back to +// defaultImmediateTimeout when wait is zero. The parent ctx propagates the +// caller's deadline / cancellation into the lock acquire. +func waitContext(parent context.Context, waitUs int64) (context.Context, context.CancelFunc) { + if waitUs == 0 { + return context.WithTimeout(parent, defaultImmediateTimeout) } + return context.WithTimeout(parent, time.Microsecond*time.Duration(waitUs)) +} - var ctx context.Context - var cancel context.CancelFunc +func (r *rpc) Lock(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() + if msg.GetId() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - acq := r.pl.locks.lock(ctx, req.GetResource(), req.GetId(), int(req.GetTtl())) - if acq { - resp.Ok = true - return nil - } + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() - resp.Ok = false - return nil + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.lock(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + }), nil } -func (r *rpc) LockRead(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("read lock request received", "ttl", int(req.GetTtl()), "wait_ttl", int(req.GetWait()), "resource", req.GetResource(), "id", req.GetId()) +func (r *rpc) LockRead(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("read lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - if req.GetId() == "" { - return errors.New("empty ID is not allowed") + if msg.GetId() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - var ctx context.Context - var cancel context.CancelFunc + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() - } - - acq := r.pl.locks.lockRead(ctx, req.GetResource(), req.GetId(), int(req.GetTtl())) - if acq { - resp.Ok = true - return nil - } - - resp.Ok = false - return nil + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.lockRead(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + }), nil } -func (r *rpc) Release(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("release request received", - "ttl", int(req.GetTtl()), - "wait_ttl", int(req.GetWait()), - "resource", req.GetResource(), - "id", req.GetId(), - ) +func (r *rpc) Release(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - if req.GetId() == "" { - return errors.New("empty ID is not allowed") + if msg.GetId() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - var ctx context.Context - var cancel context.CancelFunc + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() - } - - resp.Ok = r.pl.locks.release(ctx, req.GetResource(), req.GetId()) - return nil + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.release(cctx, msg.GetResource(), msg.GetId()), + }), nil } -func (r *rpc) ForceRelease(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("force release request received", "ttl", int(req.GetTtl()), "wait_ttl", int(req.GetWait()), "resource", req.GetResource(), "id", req.GetId()) - - var ctx context.Context - var cancel context.CancelFunc +func (r *rpc) ForceRelease(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("force release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() - } + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() - resp.Ok = r.pl.locks.forceRelease(ctx, req.GetResource()) - return nil + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.forceRelease(cctx, msg.GetResource()), + }), nil } -func (r *rpc) Exists(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("exists request received", - "ttl", int(req.GetTtl()), - "wait_ttl", int(req.GetWait()), - "resource", req.GetResource(), - "id", req.GetId(), - ) +func (r *rpc) Exists(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("exists request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - if req.GetId() == "" { - return errors.New("empty ID is not allowed") + if msg.GetId() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - var ctx context.Context - var cancel context.CancelFunc - - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() - } + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() - resp.Ok = r.pl.locks.exists(ctx, req.GetResource(), req.GetId()) - return nil + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.exists(cctx, msg.GetResource(), msg.GetId()), + }), nil } -func (r *rpc) UpdateTTL(req *lockApi.LockRequest, resp *lockApi.LockResponse) error { - r.log.Debug("updateTTL request received", "ttl", int(req.GetTtl()), "wait_ttl", int(req.GetWait()), "resource", req.GetResource(), "id", req.GetId()) - if req.GetId() == "" { - return errors.New("empty ID is not allowed") - } - - var ctx context.Context - var cancel context.CancelFunc +func (r *rpc) UpdateTTL(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { + msg := req.Msg + r.log.Debug("updateTTL request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) - switch req.GetWait() { - case int64(0): - ctx, cancel = context.WithTimeout(context.Background(), defaultImmediateTimeout) - defer cancel() - default: - ctx, cancel = context.WithTimeout(context.Background(), time.Microsecond*time.Duration(req.GetWait())) - defer cancel() + if msg.GetId() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - resp.Ok = r.pl.locks.updateTTL(ctx, req.GetResource(), req.GetId(), int(req.GetTtl())) - return nil + cctx, cancel := waitContext(ctx, msg.GetWait()) + defer cancel() + + return connect.NewResponse(&lockV1.LockResponse{ + Ok: r.pl.locks.updateTTL(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + }), nil } diff --git a/tests/go.mod b/tests/go.mod index c8b2d9f..218f80e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -5,19 +5,21 @@ go 1.26 toolchain go1.26.0 require ( - github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4 + connectrpc.com/connect v1.19.2 + github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 github.com/roadrunner-server/config/v6 v6.0.0-beta.3 github.com/roadrunner-server/endure/v2 v2.6.2 - github.com/roadrunner-server/goridge/v4 v4.0.0-beta.1 github.com/roadrunner-server/lock/v6 v6.0.0 github.com/roadrunner-server/logger/v6 v6.0.0-beta.3 - github.com/roadrunner-server/rpc/v6 v6.0.0-beta.3 + github.com/roadrunner-server/rpc/v6 v6.0.0-beta.4 github.com/stretchr/testify v1.11.1 + golang.org/x/net v0.54.0 ) replace github.com/roadrunner-server/lock/v6 => ../ require ( + connectrpc.com/grpcreflect v1.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.19.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect @@ -40,6 +42,8 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.37.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect + google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 1d8d954..cde5d6b 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,3 +1,9 @@ +connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo= +connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w= +connectrpc.com/grpcreflect v1.3.0 h1:Y4V+ACf8/vOb1XOc251Qun7jMB75gCUNw6llvB9csXc= +connectrpc.com/grpcreflect v1.3.0/go.mod h1:nfloOtCS8VUQOQ1+GTdFzVg2CJo4ZGaat8JIovCtDYs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= @@ -6,10 +12,18 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -24,20 +38,18 @@ github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7ol github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4 h1:wX8IezPUeeBJzlzaBEFSZBE5Bc/Le1Uf/GdFRdFO3HQ= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.4/go.mod h1:jI30i64yCAxJh7KHc8e1B8NgDcvcnSTI1OIK8lTE+Y0= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 h1:4x17K8qmxIbtKrCKoY1NR7bBvJbrB7w0P/0ovYR8C6E= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= github.com/roadrunner-server/config/v6 v6.0.0-beta.3 h1:G0EUzJ6Yw4UnleM6BhnOBbYPXKDHRmCJiGhC3nXDBwI= github.com/roadrunner-server/config/v6 v6.0.0-beta.3/go.mod h1:eIB+c29njpcKokXrxe483FbQOBSTNGvU3hhC6W/qYSU= github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84= github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8= github.com/roadrunner-server/errors v1.5.0 h1:unG7LKIZrSzkCCF3YLRLA5VyqE0KKomofXVJUXJe00g= github.com/roadrunner-server/errors v1.5.0/go.mod h1:g9fo/T2C13cWRDR9PW1r0ZAOSQfNhWAZawyfkGiaHuI= -github.com/roadrunner-server/goridge/v4 v4.0.0-beta.1 h1:dO1wKnuMr4xMmH6DY2ZaZ6FWS+Vo50+C7fuAcyO/xBk= -github.com/roadrunner-server/goridge/v4 v4.0.0-beta.1/go.mod h1:+gKla9HAyYlk0TsC9VktwtOL63aimsWT3oPsuCLh4/o= github.com/roadrunner-server/logger/v6 v6.0.0-beta.3 h1:eoJKXAUSyykDfVX6eTUhmAn6Y8pS/LyI5fDP4H+G5rQ= github.com/roadrunner-server/logger/v6 v6.0.0-beta.3/go.mod h1:MwHb3AbltHYtu7nRpml5NeYu7O+W8rCpDBeNTTEoE1M= -github.com/roadrunner-server/rpc/v6 v6.0.0-beta.3 h1:hvVEDIMB9MKI8uWX++MrBzHRzq404ygU0fDs6U2V/3Y= -github.com/roadrunner-server/rpc/v6 v6.0.0-beta.3/go.mod h1:BpDpd2/UceDdsDJNP0iMfmegbXthxiZM4MU6GOJoSXo= +github.com/roadrunner-server/rpc/v6 v6.0.0-beta.4 h1:Qj2nrHIWOHE9Tys+FBG2IdoPtzgIUh6juQ5wXLGGDMw= +github.com/roadrunner-server/rpc/v6 v6.0.0-beta.4/go.mod h1:k5KT3fpnJVd27m0HbGGBiTPXlWI6eJdd6C+ohp5IE0U= github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA= github.com/roadrunner-server/tcplisten v1.5.2/go.mod h1:DufGBz7Dlx2KrNe/4RukEvGMTqZKB0Uve1GztwcyyR8= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -56,6 +68,18 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -64,10 +88,18 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw= +google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/lock_test.go b/tests/lock_test.go index 5ee6874..9831bcc 100644 --- a/tests/lock_test.go +++ b/tests/lock_test.go @@ -12,6 +12,8 @@ import ( "testing" "time" + mocklogger "tests/mock" + "github.com/roadrunner-server/config/v6" "github.com/roadrunner-server/endure/v2" lockPlugin "github.com/roadrunner-server/lock/v6" @@ -19,7 +21,6 @@ import ( rpcPlugin "github.com/roadrunner-server/rpc/v6" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - mocklogger "tests/mock" ) const secMult = 1000000 @@ -87,13 +88,13 @@ func TestLockDifferentIDs(t *testing.T) { }() time.Sleep(time.Second) - res, err := lock("127.0.0.1:6001", "foo", "bar", 20*secMult, 100*secMult) + res, err := lock("foo", "bar", 20*secMult, 100*secMult) assert.True(t, res) assert.NoError(t, err) time.Sleep(time.Second) - res, err = release("127.0.0.1:6001", "foo", "bar1") + res, err = release("foo", "bar1") assert.False(t, res) assert.NoError(t, err) @@ -175,73 +176,73 @@ func TestLockInit(t *testing.T) { for i := 0; i < 100; i++ { rs := randomString(10) go func() { - _, err1 := lock("127.0.0.1:6001", resources[genRandNum(6)], rs, (genRandNum(5)+1)*secMult, (genRandNum(15)+1)*secMult) + _, err1 := lock(resources[genRandNum(6)], rs, (genRandNum(5)+1)*secMult, (genRandNum(15)+1)*secMult) assert.NoError(t, err1) }() go func() { - _, err2 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(4)+1)*secMult, (genRandNum(11)+1)*secMult) + _, err2 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(4)+1)*secMult, (genRandNum(11)+1)*secMult) assert.NoError(t, err2) }() go func() { - _, err3 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(2)+1)*secMult, (genRandNum(90)+1)*secMult) + _, err3 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(2)+1)*secMult, (genRandNum(90)+1)*secMult) assert.NoError(t, err3) }() go func() { - _, err4 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(10)+1)*secMult, (genRandNum(10)+1)*secMult) + _, err4 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(10)+1)*secMult, (genRandNum(10)+1)*secMult) assert.NoError(t, err4) }() go func() { - _, err5 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(13)+1)*secMult) + _, err5 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(13)+1)*secMult) assert.NoError(t, err5) }() go func() { - _, err6 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(80)+1)*secMult, (genRandNum(10)+1)*secMult) + _, err6 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(80)+1)*secMult, (genRandNum(10)+1)*secMult) assert.NoError(t, err6) }() go func() { - _, err7 := lock("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(19)+1)*secMult) + _, err7 := lock(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(19)+1)*secMult) assert.NoError(t, err7) }() go func() { - _, err8 := updateTTL("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(5))*secMult) + _, err8 := updateTTL(resources[genRandNum(6)], randomString(3), (genRandNum(5))*secMult) assert.NoError(t, err8) }() go func() { - _, err9 := exists("127.0.0.1:6001", resources[genRandNum(6)], rs) + _, err9 := exists(resources[genRandNum(6)], rs) assert.NoError(t, err9) }() go func() { - _, err10 := release("127.0.0.1:6001", resources[genRandNum(6)], rs) + _, err10 := release(resources[genRandNum(6)], rs) assert.NoError(t, err10) }() go func() { - _, err11 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(15)+1)*secMult) + _, err11 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(15)+1)*secMult) assert.NoError(t, err11) }() go func() { - _, err12 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(2)+1)*secMult, (genRandNum(34)+1)*secMult) + _, err12 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(2)+1)*secMult, (genRandNum(34)+1)*secMult) assert.NoError(t, err12) }() go func() { - _, err13 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(13)+1)*secMult) + _, err13 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(13)+1)*secMult) assert.NoError(t, err13) }() go func() { - _, err14 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(25)+1)*secMult, (genRandNum(15)+1)*secMult) + _, err14 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(25)+1)*secMult, (genRandNum(15)+1)*secMult) assert.NoError(t, err14) }() go func() { - _, err15 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(76)+1)*secMult) + _, err15 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(76)+1)*secMult) assert.NoError(t, err15) }() go func() { - _, err16 := lockRead("127.0.0.1:6001", resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(15)+1)*secMult) + _, err16 := lockRead(resources[genRandNum(6)], randomString(3), (genRandNum(20)+1)*secMult, (genRandNum(15)+1)*secMult) assert.NoError(t, err16) }() go func() { - _, err17 := forceRelease("127.0.0.1:6001", resources[genRandNum(6)]) + _, err17 := forceRelease(resources[genRandNum(6)]) assert.NoError(t, err17) }() } @@ -319,7 +320,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { mu := &sync.Mutex{} go func() { - res, err := lock("127.0.0.1:6001", "foo", "bar", 5*secMult, 1*secMult) + res, err := lock("foo", "bar", 5*secMult, 1*secMult) assert.NoError(t, err) mu.Lock() @@ -332,7 +333,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { } }() go func() { - res, err := lock("127.0.0.1:6001", "foo", "bar", 5*secMult, 1*secMult) + res, err := lock("foo", "bar", 5*secMult, 1*secMult) assert.NoError(t, err) mu.Lock() @@ -345,7 +346,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { } }() go func() { - res, err := lock("127.0.0.1:6001", "foo", "bar", 5*secMult, 1*secMult) + res, err := lock("foo", "bar", 5*secMult, 1*secMult) assert.NoError(t, err) mu.Lock() @@ -358,7 +359,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { } }() go func() { - res, err := lock("127.0.0.1:6001", "foo", "bar", 5*secMult, 1*secMult) + res, err := lock("foo", "bar", 5*secMult, 1*secMult) assert.NoError(t, err) mu.Lock() @@ -446,11 +447,11 @@ func TestLockReadInit(t *testing.T) { }() time.Sleep(time.Second * 3) - res, err := lock("127.0.0.1:6001", "foo", "bar", 5*secMult, 0) + res, err := lock("foo", "bar", 5*secMult, 0) assert.True(t, res) assert.NoError(t, err) - res, err = lockRead("127.0.0.1:6001", "foo", "bar", 0, 10*secMult) + res, err = lockRead("foo", "bar", 0, 10*secMult) assert.True(t, res) assert.NoError(t, err) @@ -459,14 +460,14 @@ func TestLockReadInit(t *testing.T) { wg2 := &sync.WaitGroup{} wg2.Add(2) go func() { - res2, err2 := lockRead("127.0.0.1:6001", "foo", "bar1", 0, 11*secMult) + res2, err2 := lockRead("foo", "bar1", 0, 11*secMult) assert.True(t, res2) assert.NoError(t, err2) wg2.Done() }() go func() { - res3, err3 := lockRead("127.0.0.1:6001", "foo", "bar2", 0, 11*secMult) + res3, err3 := lockRead("foo", "bar2", 0, 11*secMult) assert.True(t, res3) assert.NoError(t, err3) wg2.Done() @@ -475,20 +476,20 @@ func TestLockReadInit(t *testing.T) { wg2.Wait() time.Sleep(time.Second) - res, err = exists("127.0.0.1:6001", "foo", "bar1") + res, err = exists("foo", "bar1") assert.True(t, res) assert.NoError(t, err) - res, err = exists("127.0.0.1:6001", "foo", "bar2") + res, err = exists("foo", "bar2") assert.True(t, res) assert.NoError(t, err) - res, err = release("127.0.0.1:6001", "foo", "bar") + res, err = release("foo", "bar") assert.True(t, res) assert.NoError(t, err) - res, err = release("127.0.0.1:6001", "foo", "bar1") + res, err = release("foo", "bar1") assert.True(t, res) assert.NoError(t, err) - res, err = release("127.0.0.1:6001", "foo", "bar2") + res, err = release("foo", "bar2") assert.True(t, res) assert.NoError(t, err) @@ -567,21 +568,21 @@ func TestLockUpdateTTL(t *testing.T) { time.Sleep(time.Second * 3) - res, err := lock("127.0.0.1:6001", "foo", "bar", 1000*secMult, 0) + res, err := lock("foo", "bar", 1000*secMult, 0) assert.NoError(t, err) assert.True(t, res) - res, err = updateTTL("127.0.0.1:6001", "foo", "bar", 2*secMult) + res, err = updateTTL("foo", "bar", 2*secMult) assert.NoError(t, err) assert.True(t, res) - res, err = lockRead("127.0.0.1:6001", "foo", "bar1", 0, 10*secMult) + res, err = lockRead("foo", "bar1", 0, 10*secMult) assert.NoError(t, err) assert.True(t, res) time.Sleep(time.Second * 3) - res, err = release("127.0.0.1:6001", "foo", "bar1") + res, err = release("foo", "bar1") assert.NoError(t, err) assert.True(t, res) @@ -656,29 +657,29 @@ func TestForceRelease(t *testing.T) { }() time.Sleep(time.Second * 3) - res, err := lock("127.0.0.1:6001", "foo", "bar", 1000*secMult, 0) + res, err := lock("foo", "bar", 1000*secMult, 0) assert.NoError(t, err) assert.True(t, res) - res, err = lockRead("127.0.0.1:6001", "foo", "bar1", 0, 1*secMult) + res, err = lockRead("foo", "bar1", 0, 1*secMult) assert.NoError(t, err) assert.False(t, res) - res, err = forceRelease("127.0.0.1:6001", "foo") + res, err = forceRelease("foo") assert.NoError(t, err) assert.True(t, res) - res, err = lockRead("127.0.0.1:6001", "foo", "bar1", 0, 10*secMult) + res, err = lockRead("foo", "bar1", 0, 10*secMult) assert.NoError(t, err) assert.True(t, res) time.Sleep(time.Second) - res, err = exists("127.0.0.1:6001", "foo", "bar1") + res, err = exists("foo", "bar1") assert.NoError(t, err) assert.True(t, res) - res, err = release("127.0.0.1:6001", "foo", "bar1") + res, err = release("foo", "bar1") assert.NoError(t, err) assert.True(t, res) @@ -701,8 +702,8 @@ func randomString(n int) string { return string(b) } -func genRandNum(max int) int { - bg := big.NewInt(int64(max)) +func genRandNum(upper int) int { + bg := big.NewInt(int64(upper)) n, err := rand.Int(rand.Reader, bg) if err != nil { diff --git a/tests/rpc.go b/tests/rpc.go index 171d38d..baf99b7 100644 --- a/tests/rpc.go +++ b/tests/rpc.go @@ -1,144 +1,128 @@ package lock import ( + "context" + "crypto/tls" "net" - "net/rpc" - - lockApi "github.com/roadrunner-server/api-go/v6/lock/v1" - goridgeRpc "github.com/roadrunner-server/goridge/v4/pkg/rpc" -) - -const ( - lockRPC string = "lock.Lock" - rlockRPC string = "lock.LockRead" - releaseRPC string = "lock.Release" - updateTTLRPC string = "lock.UpdateTTL" - forceReleaseRPC string = "lock.ForceRelease" - existsRPC string = "lock.Exists" + "net/http" + "sync" + "time" + + "connectrpc.com/connect" + lockV1 "github.com/roadrunner-server/api-go/v6/lock/v1" + "github.com/roadrunner-server/api-go/v6/lock/v1/lockV1connect" + "golang.org/x/net/http2" ) -func lock(address string, resource, id string, ttl, wait int) (bool, error) { - conn, err := net.Dial("tcp", address) - if err != nil { - return false, err - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - Id: id, - Ttl: ptrTo(int64(ttl)), - Wait: ptrTo(int64(wait)), - } - - resp := &lockApi.LockResponse{} - err = client.Call(lockRPC, req, resp) - if err != nil { - return false, err - } - return resp.Ok, nil +const lockRPCAddr = "127.0.0.1:6001" + +// h2cClient is a shared HTTP/2 cleartext client for talking to the rpc plugin's mux. +// All lock test calls hit the same address, so a single transport amortizes setup +// across the suite and lets http2 pool connections. +// +//nolint:gochecknoglobals // shared transport is the entire point — pools idle conns across tests +var h2cClient = sync.OnceValue(func() *http.Client { + return &http.Client{ + Timeout: 10 * time.Second, + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { + return new(net.Dialer).DialContext(ctx, network, addr) + }, + }, + } +}) + +func newLockClient() lockV1connect.LockServiceClient { + return lockV1connect.NewLockServiceClient(h2cClient(), "http://"+lockRPCAddr) } -func lockRead(address string, resource, id string, ttl, wait int) (bool, error) { - conn, err := net.Dial("tcp", address) - if err != nil { - return false, err - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - Id: id, - Ttl: ptrTo(int64(ttl)), - Wait: ptrTo(int64(wait)), - } - - resp := &lockApi.LockResponse{} - err = client.Call(rlockRPC, req, resp) +func lock(resource, id string, ttl, wait int) (bool, error) { + resp, err := newLockClient().Lock( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: ptrTo(int64(ttl)), + Wait: ptrTo(int64(wait)), + }), + ) if err != nil { return false, err } - return resp.Ok, nil + return resp.Msg.GetOk(), nil } -func release(address string, resource, id string) (bool, error) { - conn, err := net.Dial("tcp", address) +func lockRead(resource, id string, ttl, wait int) (bool, error) { + resp, err := newLockClient().LockRead( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: ptrTo(int64(ttl)), + Wait: ptrTo(int64(wait)), + }), + ) if err != nil { return false, err } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - Id: id, - } + return resp.Msg.GetOk(), nil +} - resp := &lockApi.LockResponse{} - err = client.Call(releaseRPC, req, resp) +func release(resource, id string) (bool, error) { + resp, err := newLockClient().Release( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + }), + ) if err != nil { return false, err } - return resp.Ok, nil + return resp.Msg.GetOk(), nil } -func updateTTL(address string, resource, id string, ttl int) (bool, error) { - conn, err := net.Dial("tcp", address) - if err != nil { - return false, nil - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - Id: id, - Ttl: ptrTo(int64(ttl)), - } - - resp := &lockApi.LockResponse{} - err = client.Call(updateTTLRPC, req, resp) +func updateTTL(resource, id string, ttl int) (bool, error) { + resp, err := newLockClient().UpdateTTL( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: ptrTo(int64(ttl)), + }), + ) if err != nil { - return false, nil + return false, err } - return resp.Ok, nil + return resp.Msg.GetOk(), nil } -func forceRelease(address string, resource string) (bool, error) { - conn, err := net.Dial("tcp", address) +func forceRelease(resource string) (bool, error) { + resp, err := newLockClient().ForceRelease( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + }), + ) if err != nil { - return false, nil - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - } - - resp := &lockApi.LockResponse{} - err = client.Call(forceReleaseRPC, req, resp) - if err != nil { - return false, nil + return false, err } - return resp.Ok, nil + return resp.Msg.GetOk(), nil } -func exists(address string, resource, id string) (bool, error) { - conn, err := net.Dial("tcp", address) +func exists(resource, id string) (bool, error) { + resp, err := newLockClient().Exists( + context.Background(), + connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + }), + ) if err != nil { - return false, nil - } - client := rpc.NewClientWithCodec(goridgeRpc.NewClientCodec(conn)) - - req := &lockApi.LockRequest{ - Resource: resource, - Id: id, - } - - resp := &lockApi.LockResponse{} - err = client.Call(existsRPC, req, resp) - if err != nil { - return false, nil + return false, err } - return resp.Ok, nil + return resp.Msg.GetOk(), nil } func ptrTo[T any](val T) *T { From a83d1ae0624c230dd6fb2be8ebbf0edfee419778 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 10 May 2026 22:06:40 +0200 Subject: [PATCH 02/10] chore: use new(val) instead of ptrTo helper Go 1.26's new() now accepts expressions, so new(int64(ttl)) returns *int64 directly. Drops the ptrTo[T any] generic helper from tests/rpc.go since it has no remaining callers. --- tests/rpc.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/rpc.go b/tests/rpc.go index baf99b7..743e850 100644 --- a/tests/rpc.go +++ b/tests/rpc.go @@ -43,8 +43,8 @@ func lock(resource, id string, ttl, wait int) (bool, error) { connect.NewRequest(&lockV1.LockRequest{ Resource: resource, Id: id, - Ttl: ptrTo(int64(ttl)), - Wait: ptrTo(int64(wait)), + Ttl: new(int64(ttl)), + Wait: new(int64(wait)), }), ) if err != nil { @@ -59,8 +59,8 @@ func lockRead(resource, id string, ttl, wait int) (bool, error) { connect.NewRequest(&lockV1.LockRequest{ Resource: resource, Id: id, - Ttl: ptrTo(int64(ttl)), - Wait: ptrTo(int64(wait)), + Ttl: new(int64(ttl)), + Wait: new(int64(wait)), }), ) if err != nil { @@ -89,7 +89,7 @@ func updateTTL(resource, id string, ttl int) (bool, error) { connect.NewRequest(&lockV1.LockRequest{ Resource: resource, Id: id, - Ttl: ptrTo(int64(ttl)), + Ttl: new(int64(ttl)), }), ) if err != nil { @@ -124,7 +124,3 @@ func exists(resource, id string) (bool, error) { } return resp.Msg.GetOk(), nil } - -func ptrTo[T any](val T) *T { - return &val -} From 0fe81f7c01975f5162013946f183ffd9a5657674 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 10 May 2026 22:19:22 +0200 Subject: [PATCH 03/10] fix(tests): drop http.Client.Timeout, server wait is authoritative MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 10s client-side Timeout I added cuts off any goroutine in TestLockInit whose `wait` value exceeds 10s — and TestLockInit intentionally sweeps wait values up to ~91s (genRandNum(90)+1)*secMult. CI failed with `Client.Timeout exceeded while awaiting headers` on the LockRead and other long-wait calls. Lock RPCs already carry a server-honored Wait field that bounds the acquire; the server's wait is the authoritative deadline. Removing the client Timeout restores parity with the prior goridge transport, which also had no client-level timeout. --- tests/rpc.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/rpc.go b/tests/rpc.go index 743e850..ce54413 100644 --- a/tests/rpc.go +++ b/tests/rpc.go @@ -6,7 +6,6 @@ import ( "net" "net/http" "sync" - "time" "connectrpc.com/connect" lockV1 "github.com/roadrunner-server/api-go/v6/lock/v1" @@ -18,12 +17,13 @@ const lockRPCAddr = "127.0.0.1:6001" // h2cClient is a shared HTTP/2 cleartext client for talking to the rpc plugin's mux. // All lock test calls hit the same address, so a single transport amortizes setup -// across the suite and lets http2 pool connections. +// across the suite and lets http2 pool connections. No client-side Timeout is set: +// lock RPCs carry a server-honored Wait field that bounds blocking on contended locks +// (TestLockInit goes up to ~91s); the server's wait is the authoritative deadline. // //nolint:gochecknoglobals // shared transport is the entire point — pools idle conns across tests var h2cClient = sync.OnceValue(func() *http.Client { return &http.Client{ - Timeout: 10 * time.Second, Transport: &http2.Transport{ AllowHTTP: true, DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { From e963b8ef44391732d9d733c27371ef812d498e13 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 10 May 2026 22:19:32 +0200 Subject: [PATCH 04/10] chore: apply modern Go 1.21-1.26 idioms repo-wide - locker.go: drop ptrTo[T] generic helper; use Go 1.26 new(val) at all 7 atomic.Pointer[string].Store() call sites - tests/lock_test.go: - wg.Add(1)/wg.Done() pairs across 6 test setups -> wg.Go (Go 1.25) - wg2.Add(2) + 2 explicit Done() -> 2x wg.Go (Go 1.25) - for i := 0; i < 100; i++ (i unused) -> for range 100 (Go 1.22) - sort.Ints -> slices.Sort (Go 1.21); drop "sort" import No behavior change; build and lint clean on root + tests modules. --- locker.go | 18 ++++++---------- tests/lock_test.go | 53 +++++++++++++++++----------------------------- 2 files changed, 26 insertions(+), 45 deletions(-) diff --git a/locker.go b/locker.go index be4894e..5ad5a1c 100644 --- a/locker.go +++ b/locker.go @@ -100,7 +100,7 @@ func (l *locker) lock(ctx context.Context, res, id string, ttl int) bool { stopCh: make(chan struct{}, 1), } - rr.ownerID.Store(ptrTo(id)) + rr.ownerID.Store(new(id)) rr.writerCount.Store(1) rr.readerCount.Store(0) @@ -176,7 +176,7 @@ func (l *locker) lock(ctx context.Context, res, id string, ttl int) bool { return false } - r.ownerID.Store(ptrTo(id)) + r.ownerID.Store(new(id)) r.writerCount.Store(1) r.readerCount.Store(0) @@ -276,7 +276,7 @@ func (l *locker) lock(ctx context.Context, res, id string, ttl int) bool { } // store writer and remove reader - r.ownerID.Store(ptrTo(id)) + r.ownerID.Store(new(id)) r.writerCount.Store(1) r.readerCount.Store(0) @@ -314,7 +314,7 @@ func (l *locker) lock(ctx context.Context, res, id string, ttl int) bool { case <-r.notificationCh: l.log.Debug("no readers holding mutex anymore, proceeding with acquiring write lock", "id", id) // store writer and remove reader - r.ownerID.Store(ptrTo(id)) + r.ownerID.Store(new(id)) r.writerCount.Store(1) r.readerCount.Store(0) @@ -356,7 +356,7 @@ func (l *locker) lock(ctx context.Context, res, id string, ttl int) bool { } // store the writer - r.ownerID.Store(ptrTo(id)) + r.ownerID.Store(new(id)) r.writerCount.Store(1) r.readerCount.Store(0) @@ -415,7 +415,7 @@ func (l *locker) lockRead(ctx context.Context, res, id string, ttl int) bool { stopCh: make(chan struct{}, 1), } - rr.ownerID.Store(ptrTo("")) + rr.ownerID.Store(new("")) rr.writerCount.Store(0) rr.readerCount.Store(1) @@ -885,7 +885,7 @@ func (l *locker) makeLockCallback(res, id string, ttl int) (callback, chan struc if r.writerCount.Load() == 1 { // clear owner, only a writer might be an owner - r.ownerID.Store(ptrTo("")) + r.ownerID.Store(new("")) r.writerCount.Store(0) r.readerCount.Store(0) } @@ -920,7 +920,3 @@ func (l *locker) makeLockCallback(res, id string, ttl int) (callback, chan struc } }, stopCbCh, updateTTLCh } - -func ptrTo[T any](val T) *T { - return &val -} diff --git a/tests/lock_test.go b/tests/lock_test.go index 9831bcc..e95e833 100644 --- a/tests/lock_test.go +++ b/tests/lock_test.go @@ -6,7 +6,7 @@ import ( "math/big" "os" "os/signal" - "sort" + "slices" "sync" "syscall" "testing" @@ -56,12 +56,10 @@ func TestLockDifferentIDs(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -85,7 +83,7 @@ func TestLockDifferentIDs(t *testing.T) { return } } - }() + }) time.Sleep(time.Second) res, err := lock("foo", "bar", 20*secMult, 100*secMult) @@ -138,12 +136,10 @@ func TestLockInit(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -167,13 +163,13 @@ func TestLockInit(t *testing.T) { return } } - }() + }) time.Sleep(time.Second * 3) resources := map[int]string{0: "foo", 1: "foo1", 2: "foo2", 3: "foo3", 4: "foo4", 5: "foo5"} - for i := 0; i < 100; i++ { + for range 100 { rs := randomString(10) go func() { _, err1 := lock(resources[genRandNum(6)], rs, (genRandNum(5)+1)*secMult, (genRandNum(15)+1)*secMult) @@ -284,12 +280,10 @@ func TestLockFromSeveralProcesses(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -313,7 +307,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { return } } - }() + }) time.Sleep(time.Second * 2) answ := make([]int, 0, 4) @@ -379,7 +373,7 @@ func TestLockFromSeveralProcesses(t *testing.T) { time.Sleep(time.Second * 2) mu.Lock() - sort.Ints(answ) + slices.Sort(answ) assert.Equal(t, []int{0, 0, 0, 1}, answ) mu.Unlock() } @@ -415,12 +409,10 @@ func TestLockReadInit(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -444,7 +436,7 @@ func TestLockReadInit(t *testing.T) { return } } - }() + }) time.Sleep(time.Second * 3) res, err := lock("foo", "bar", 5*secMult, 0) @@ -458,20 +450,17 @@ func TestLockReadInit(t *testing.T) { time.Sleep(time.Second) wg2 := &sync.WaitGroup{} - wg2.Add(2) - go func() { + wg2.Go(func() { res2, err2 := lockRead("foo", "bar1", 0, 11*secMult) assert.True(t, res2) assert.NoError(t, err2) - wg2.Done() - }() + }) - go func() { + wg2.Go(func() { res3, err3 := lockRead("foo", "bar2", 0, 11*secMult) assert.True(t, res3) assert.NoError(t, err3) - wg2.Done() - }() + }) wg2.Wait() time.Sleep(time.Second) @@ -535,12 +524,10 @@ func TestLockUpdateTTL(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -564,7 +551,7 @@ func TestLockUpdateTTL(t *testing.T) { return } } - }() + }) time.Sleep(time.Second * 3) @@ -626,12 +613,10 @@ func TestForceRelease(t *testing.T) { signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) wg := &sync.WaitGroup{} - wg.Add(1) stopCh := make(chan struct{}, 1) - go func() { - defer wg.Done() + wg.Go(func() { for { select { case e := <-ch: @@ -654,7 +639,7 @@ func TestForceRelease(t *testing.T) { return } } - }() + }) time.Sleep(time.Second * 3) res, err := lock("foo", "bar", 1000*secMult, 0) From f3decc76ceed374be547e32652e0cd179e24247d Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Sun, 10 May 2026 22:23:46 +0200 Subject: [PATCH 05/10] refactor: drop redundant rpc.log field; trim WHAT-only comments - rpc.go: rpc{pl, log} -> rpc{pl}; r.log was always p.log. Single source of truth at p.log; six handlers now reach it via r.pl.log. - plugin.go, rpc.go, tests/rpc.go: trim narration-style godoc that restated the signature/body. Keep the short rationale comments. --- plugin.go | 4 +--- rpc.go | 21 +++++++-------------- tests/rpc.go | 7 ++----- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/plugin.go b/plugin.go index af812a9..b9b5ed6 100644 --- a/plugin.go +++ b/plugin.go @@ -43,8 +43,6 @@ func (p *Plugin) Name() string { return pluginName } -// RPC returns the Connect-RPC service handler for lock.v1.LockService. -// The rpc plugin mounts the returned handler at the returned path on its HTTP/2 mux. func (p *Plugin) RPC() (string, http.Handler) { - return lockV1connect.NewLockServiceHandler(&rpc{pl: p, log: p.log}) + return lockV1connect.NewLockServiceHandler(&rpc{pl: p}) } diff --git a/rpc.go b/rpc.go index bc77236..aaef059 100644 --- a/rpc.go +++ b/rpc.go @@ -3,7 +3,6 @@ package lock import ( "context" stderr "errors" - "log/slog" "time" "connectrpc.com/connect" @@ -12,18 +11,12 @@ import ( const defaultImmediateTimeout = time.Millisecond -// errEmptyID surfaces as connect.CodeInvalidArgument; all RPCs except ForceRelease -// require a caller-supplied ID to attribute ownership of the resource. var errEmptyID = stderr.New("empty ID is not allowed") type rpc struct { - log *slog.Logger - pl *Plugin + pl *Plugin } -// waitContext bounds the locker call by req.Wait microseconds, falling back to -// defaultImmediateTimeout when wait is zero. The parent ctx propagates the -// caller's deadline / cancellation into the lock acquire. func waitContext(parent context.Context, waitUs int64) (context.Context, context.CancelFunc) { if waitUs == 0 { return context.WithTimeout(parent, defaultImmediateTimeout) @@ -33,7 +26,7 @@ func waitContext(parent context.Context, waitUs int64) (context.Context, context func (r *rpc) Lock(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) if msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) @@ -49,7 +42,7 @@ func (r *rpc) Lock(ctx context.Context, req *connect.Request[lockV1.LockRequest] func (r *rpc) LockRead(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("read lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("read lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) if msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) @@ -65,7 +58,7 @@ func (r *rpc) LockRead(ctx context.Context, req *connect.Request[lockV1.LockRequ func (r *rpc) Release(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) if msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) @@ -81,7 +74,7 @@ func (r *rpc) Release(ctx context.Context, req *connect.Request[lockV1.LockReque func (r *rpc) ForceRelease(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("force release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("force release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) cctx, cancel := waitContext(ctx, msg.GetWait()) defer cancel() @@ -93,7 +86,7 @@ func (r *rpc) ForceRelease(ctx context.Context, req *connect.Request[lockV1.Lock func (r *rpc) Exists(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("exists request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("exists request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) if msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) @@ -109,7 +102,7 @@ func (r *rpc) Exists(ctx context.Context, req *connect.Request[lockV1.LockReques func (r *rpc) UpdateTTL(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { msg := req.Msg - r.log.Debug("updateTTL request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("updateTTL request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) if msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) diff --git a/tests/rpc.go b/tests/rpc.go index ce54413..132031f 100644 --- a/tests/rpc.go +++ b/tests/rpc.go @@ -15,11 +15,8 @@ import ( const lockRPCAddr = "127.0.0.1:6001" -// h2cClient is a shared HTTP/2 cleartext client for talking to the rpc plugin's mux. -// All lock test calls hit the same address, so a single transport amortizes setup -// across the suite and lets http2 pool connections. No client-side Timeout is set: -// lock RPCs carry a server-honored Wait field that bounds blocking on contended locks -// (TestLockInit goes up to ~91s); the server's wait is the authoritative deadline. +// Shared h2c client; no client Timeout — server-side Wait is authoritative +// (and tests pass Wait values larger than any reasonable client deadline). // //nolint:gochecknoglobals // shared transport is the entire point — pools idle conns across tests var h2cClient = sync.OnceValue(func() *http.Client { From 32d95109780a2de68c12efce0b99575f039882f4 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Mon, 11 May 2026 09:21:24 +0200 Subject: [PATCH 06/10] style: drop msg := req.Msg alias; access fields via req.Msg.GetX() Per user preference: Connect handler bodies pull each field directly from req.Msg, no local alias. Keeps the call site self-documenting. --- rpc.go | 52 +++++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/rpc.go b/rpc.go index aaef059..f87cf7a 100644 --- a/rpc.go +++ b/rpc.go @@ -25,93 +25,87 @@ func waitContext(parent context.Context, waitUs int64) (context.Context, context } func (r *rpc) Lock(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("lock request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - if msg.GetId() == "" { + if req.Msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.lock(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + Ok: r.pl.locks.lock(cctx, req.Msg.GetResource(), req.Msg.GetId(), int(req.Msg.GetTtl())), }), nil } func (r *rpc) LockRead(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("read lock request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("read lock request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - if msg.GetId() == "" { + if req.Msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.lockRead(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + Ok: r.pl.locks.lockRead(cctx, req.Msg.GetResource(), req.Msg.GetId(), int(req.Msg.GetTtl())), }), nil } func (r *rpc) Release(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("release request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - if msg.GetId() == "" { + if req.Msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.release(cctx, msg.GetResource(), msg.GetId()), + Ok: r.pl.locks.release(cctx, req.Msg.GetResource(), req.Msg.GetId()), }), nil } func (r *rpc) ForceRelease(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("force release request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("force release request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.forceRelease(cctx, msg.GetResource()), + Ok: r.pl.locks.forceRelease(cctx, req.Msg.GetResource()), }), nil } func (r *rpc) Exists(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("exists request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("exists request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - if msg.GetId() == "" { + if req.Msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.exists(cctx, msg.GetResource(), msg.GetId()), + Ok: r.pl.locks.exists(cctx, req.Msg.GetResource(), req.Msg.GetId()), }), nil } func (r *rpc) UpdateTTL(ctx context.Context, req *connect.Request[lockV1.LockRequest]) (*connect.Response[lockV1.LockResponse], error) { - msg := req.Msg - r.pl.log.Debug("updateTTL request received", "ttl", int(msg.GetTtl()), "wait_ttl", int(msg.GetWait()), "resource", msg.GetResource(), "id", msg.GetId()) + r.pl.log.Debug("updateTTL request received", "ttl", int(req.Msg.GetTtl()), "wait_ttl", int(req.Msg.GetWait()), "resource", req.Msg.GetResource(), "id", req.Msg.GetId()) - if msg.GetId() == "" { + if req.Msg.GetId() == "" { return nil, connect.NewError(connect.CodeInvalidArgument, errEmptyID) } - cctx, cancel := waitContext(ctx, msg.GetWait()) + cctx, cancel := waitContext(ctx, req.Msg.GetWait()) defer cancel() return connect.NewResponse(&lockV1.LockResponse{ - Ok: r.pl.locks.updateTTL(cctx, msg.GetResource(), msg.GetId(), int(msg.GetTtl())), + Ok: r.pl.locks.updateTTL(cctx, req.Msg.GetResource(), req.Msg.GetId(), int(req.Msg.GetTtl())), }), nil } From c18a435a62af4b458b1c3baf7f8d431674fd6bbd Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Mon, 11 May 2026 10:13:59 +0200 Subject: [PATCH 07/10] test(lock): add Connect / HTTP / gRPC API coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New tests/lock_api_test.go runs a Lock / Exists / Release / Exists cycle three times — once per wire protocol Connect-RPC serves off the single rpc-plugin mount: - TestLockConnectAPI: Connect over h2c via connectrpc.com/connect. - TestLockHTTPApi: plain HTTP/1.1 POST with a protojson body and Content-Type: application/json (what PHP clients do via Guzzle/curl). - TestLockGRPCApi: regular gRPC via google.golang.org/grpc (used by PHP's gRPC extension). Helper startLockAPIContainer brings up rpc + lock + logger with a new minimal configs/.rr-lock-api.yaml. GET-idempotency test for Exists comes in a follow-up commit once api-go ships a beta with `option idempotency_level = NO_SIDE_EFFECTS` (api PR #72). --- tests/configs/.rr-lock-api.yaml | 9 ++ tests/go.mod | 4 +- tests/lock_api_test.go | 221 ++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 tests/configs/.rr-lock-api.yaml create mode 100644 tests/lock_api_test.go diff --git a/tests/configs/.rr-lock-api.yaml b/tests/configs/.rr-lock-api.yaml new file mode 100644 index 0000000..865cd05 --- /dev/null +++ b/tests/configs/.rr-lock-api.yaml @@ -0,0 +1,9 @@ +version: '3' + +rpc: + listen: tcp://127.0.0.1:6001 + +logs: + level: error + encoding: console + mode: development diff --git a/tests/go.mod b/tests/go.mod index 218f80e..f6febca 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -14,6 +14,8 @@ require ( github.com/roadrunner-server/rpc/v6 v6.0.0-beta.4 github.com/stretchr/testify v1.11.1 golang.org/x/net v0.54.0 + google.golang.org/grpc v1.81.0 + google.golang.org/protobuf v1.36.11 ) replace github.com/roadrunner-server/lock/v6 => ../ @@ -43,7 +45,5 @@ require ( golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.37.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect - google.golang.org/grpc v1.81.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/lock_api_test.go b/tests/lock_api_test.go new file mode 100644 index 0000000..dcb88da --- /dev/null +++ b/tests/lock_api_test.go @@ -0,0 +1,221 @@ +package lock + +import ( + "bytes" + "context" + "crypto/tls" + "io" + "log/slog" + "net" + "net/http" + "sync" + "testing" + "time" + + "connectrpc.com/connect" + lockV1 "github.com/roadrunner-server/api-go/v6/lock/v1" + "github.com/roadrunner-server/api-go/v6/lock/v1/lockV1connect" + "github.com/roadrunner-server/config/v6" + "github.com/roadrunner-server/endure/v2" + lockPlugin "github.com/roadrunner-server/lock/v6" + "github.com/roadrunner-server/logger/v6" + rpcPlugin "github.com/roadrunner-server/rpc/v6" + "github.com/stretchr/testify/require" + "golang.org/x/net/http2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +const lockAPIAddr = "127.0.0.1:6001" + +// startLockAPIContainer brings up rpc + lock + logger on lockAPIAddr. +// Returns a stop function the test must defer. +func startLockAPIContainer(t *testing.T) func() { + t.Helper() + + cont := endure.New(slog.LevelError) + cfg := &config.Plugin{ + Version: "2024.2.0", + Path: "configs/.rr-lock-api.yaml", + } + + require.NoError(t, cont.RegisterAll( + cfg, + &logger.Plugin{}, + &rpcPlugin.Plugin{}, + &lockPlugin.Plugin{}, + )) + require.NoError(t, cont.Init()) + + ch, err := cont.Serve() + require.NoError(t, err) + + wg := &sync.WaitGroup{} + stop := make(chan struct{}) + wg.Go(func() { + select { + case e := <-ch: + require.NoError(t, e.Error, "container reported error") + case <-stop: + } + }) + + time.Sleep(500 * time.Millisecond) + + return func() { + close(stop) + require.NoError(t, cont.Stop()) + wg.Wait() + } +} + +// TestLockConnectAPI exercises the lock RPCs through the Connect-RPC client +// (h2c). Go callers that import the generated lockV1connect package see +// exactly this wire shape. +func TestLockConnectAPI(t *testing.T) { + stop := startLockAPIContainer(t) + defer stop() + + httpc := &http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { + return (&net.Dialer{Timeout: 30 * time.Second}).DialContext(ctx, network, addr) + }, + }, + } + client := lockV1connect.NewLockServiceClient(httpc, "http://"+lockAPIAddr) + ctx := t.Context() + + const ( + resource = "connect-resource" + id = "connect-id" + ) + ttl := int64(30 * time.Second / time.Microsecond) + wait := int64(time.Second / time.Microsecond) + + resp, err := client.Lock(ctx, connect.NewRequest(&lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: &ttl, + Wait: &wait, + })) + require.NoError(t, err) + require.True(t, resp.Msg.GetOk()) + + resp, err = client.Exists(ctx, connect.NewRequest(&lockV1.LockRequest{Resource: resource, Id: id})) + require.NoError(t, err) + require.True(t, resp.Msg.GetOk()) + + resp, err = client.Release(ctx, connect.NewRequest(&lockV1.LockRequest{Resource: resource, Id: id})) + require.NoError(t, err) + require.True(t, resp.Msg.GetOk()) + + resp, err = client.Exists(ctx, connect.NewRequest(&lockV1.LockRequest{Resource: resource, Id: id})) + require.NoError(t, err) + require.False(t, resp.Msg.GetOk()) +} + +// TestLockHTTPApi exercises the lock RPCs through plain HTTP/1.1 with a +// protojson body — the wire shape PHP clients use via Guzzle/curl +// (PHP has no Connect SDK). +func TestLockHTTPApi(t *testing.T) { + stop := startLockAPIContainer(t) + defer stop() + + httpc := &http.Client{Timeout: 30 * time.Second} + ctx := t.Context() + + call := func(method string, in proto.Message, out proto.Message) { + body, err := protojson.Marshal(in) + require.NoError(t, err) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, + "http://"+lockAPIAddr+"/lock.v1.LockService/"+method, bytes.NewReader(body)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + resp, err := httpc.Do(req) + require.NoError(t, err) + defer func() { _ = resp.Body.Close() }() + + respBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equalf(t, http.StatusOK, resp.StatusCode, "method=%s body=%s", method, respBody) + require.NoError(t, protojson.Unmarshal(respBody, out)) + } + + const ( + resource = "http-resource" + id = "http-id" + ) + ttl := int64(30 * time.Second / time.Microsecond) + wait := int64(time.Second / time.Microsecond) + + var lockResp lockV1.LockResponse + call("Lock", &lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: &ttl, + Wait: &wait, + }, &lockResp) + require.True(t, lockResp.GetOk()) + + var existsResp lockV1.LockResponse + call("Exists", &lockV1.LockRequest{Resource: resource, Id: id}, &existsResp) + require.True(t, existsResp.GetOk()) + + var relResp lockV1.LockResponse + call("Release", &lockV1.LockRequest{Resource: resource, Id: id}, &relResp) + require.True(t, relResp.GetOk()) + + var existsResp2 lockV1.LockResponse + call("Exists", &lockV1.LockRequest{Resource: resource, Id: id}, &existsResp2) + require.False(t, existsResp2.GetOk()) +} + +// TestLockGRPCApi exercises the lock RPCs through a regular gRPC client +// (google.golang.org/grpc). The same Connect handler serves gRPC framing +// off the same port — used by PHP's gRPC extension. +func TestLockGRPCApi(t *testing.T) { + stop := startLockAPIContainer(t) + defer stop() + + conn, err := grpc.NewClient(lockAPIAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + defer func() { _ = conn.Close() }() + + client := lockV1.NewLockServiceClient(conn) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + + const ( + resource = "grpc-resource" + id = "grpc-id" + ) + ttl := int64(30 * time.Second / time.Microsecond) + wait := int64(time.Second / time.Microsecond) + + lockResp, err := client.Lock(ctx, &lockV1.LockRequest{ + Resource: resource, + Id: id, + Ttl: &ttl, + Wait: &wait, + }) + require.NoError(t, err) + require.True(t, lockResp.GetOk()) + + existsResp, err := client.Exists(ctx, &lockV1.LockRequest{Resource: resource, Id: id}) + require.NoError(t, err) + require.True(t, existsResp.GetOk()) + + relResp, err := client.Release(ctx, &lockV1.LockRequest{Resource: resource, Id: id}) + require.NoError(t, err) + require.True(t, relResp.GetOk()) + + existsResp, err = client.Exists(ctx, &lockV1.LockRequest{Resource: resource, Id: id}) + require.NoError(t, err) + require.False(t, existsResp.GetOk()) +} From ba8d83295eb0f6cf46fe4d6f29eb261d475f0caa Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Mon, 11 May 2026 11:53:37 +0200 Subject: [PATCH 08/10] test(lock): verify HTTP GET works for Exists; add api-go beta.7 api-go v6.0.0-beta.7 (tagged off api PR #72) regenerates the LockServiceHandler with WithIdempotency(IdempotencyNoSideEffects) on Exists. Connect now accepts HTTP GET for that method, encoding the request body in query params. - Bump api-go: v6.0.0-beta.5 -> v6.0.0-beta.7 in root + tests modules. - Add a table-driven TestLockHTTPGetIdempotency asserting: GET Exists -> 200 OK GET Lock, LockRead, Release, ForceRelease, UpdateTTL -> 405 Method Not Allowed Regression coverage for the proto idempotency annotation. --- go.mod | 2 +- go.sum | 4 ++-- tests/go.mod | 2 +- tests/go.sum | 4 ++-- tests/lock_api_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 59e7865..f4ce731 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.26.0 require ( connectrpc.com/connect v1.19.2 - github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 + github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7 ) require ( diff --git a/go.sum b/go.sum index ef368ed..d67bb2d 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 h1:4x17K8qmxIbtKrCKoY1NR7bBvJbrB7w0P/0ovYR8C6E= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7 h1:eCXrj6DVLmEGILk1HXLVMN+GcRsgDSvI2xPBO/C6k6A= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= diff --git a/tests/go.mod b/tests/go.mod index f6febca..ca9e6a4 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -6,7 +6,7 @@ toolchain go1.26.0 require ( connectrpc.com/connect v1.19.2 - github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 + github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7 github.com/roadrunner-server/config/v6 v6.0.0-beta.3 github.com/roadrunner-server/endure/v2 v2.6.2 github.com/roadrunner-server/lock/v6 v6.0.0 diff --git a/tests/go.sum b/tests/go.sum index cde5d6b..8b723b1 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -38,8 +38,8 @@ github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7ol github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5 h1:4x17K8qmxIbtKrCKoY1NR7bBvJbrB7w0P/0ovYR8C6E= -github.com/roadrunner-server/api-go/v6 v6.0.0-beta.5/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7 h1:eCXrj6DVLmEGILk1HXLVMN+GcRsgDSvI2xPBO/C6k6A= +github.com/roadrunner-server/api-go/v6 v6.0.0-beta.7/go.mod h1:B9CjHMnKrAUNM99XckiA8NEKg0oid22KqejR+lRnohw= github.com/roadrunner-server/config/v6 v6.0.0-beta.3 h1:G0EUzJ6Yw4UnleM6BhnOBbYPXKDHRmCJiGhC3nXDBwI= github.com/roadrunner-server/config/v6 v6.0.0-beta.3/go.mod h1:eIB+c29njpcKokXrxe483FbQOBSTNGvU3hhC6W/qYSU= github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84= diff --git a/tests/lock_api_test.go b/tests/lock_api_test.go index dcb88da..8db158d 100644 --- a/tests/lock_api_test.go +++ b/tests/lock_api_test.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "crypto/tls" + "encoding/base64" "io" "log/slog" "net" "net/http" + "net/url" "sync" "testing" "time" @@ -176,6 +178,53 @@ func TestLockHTTPApi(t *testing.T) { require.False(t, existsResp2.GetOk()) } +// TestLockHTTPGetIdempotency verifies which methods accept HTTP GET. Only +// Exists is marked `option idempotency_level = NO_SIDE_EFFECTS;` in the proto, +// so Connect generates a handler that accepts GET for it. Mutating methods +// stay POST-only, so GET against them returns 405 Method Not Allowed. +func TestLockHTTPGetIdempotency(t *testing.T) { + stop := startLockAPIContainer(t) + defer stop() + + body, err := protojson.Marshal(&lockV1.LockRequest{Resource: "probe", Id: "probe"}) + require.NoError(t, err) + + q := url.Values{} + q.Set("encoding", "json") + q.Set("base64", "1") + q.Set("message", base64.URLEncoding.EncodeToString(body)) + + cases := []struct { + method string + wantStatus int + }{ + {"Exists", http.StatusOK}, + {"Lock", http.StatusMethodNotAllowed}, + {"LockRead", http.StatusMethodNotAllowed}, + {"Release", http.StatusMethodNotAllowed}, + {"ForceRelease", http.StatusMethodNotAllowed}, + {"UpdateTTL", http.StatusMethodNotAllowed}, + } + + httpc := &http.Client{Timeout: 30 * time.Second} + for _, c := range cases { + t.Run(c.method, func(t *testing.T) { + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, + "http://"+lockAPIAddr+"/lock.v1.LockService/"+c.method+"?"+q.Encode(), nil) + require.NoError(t, err) + + resp, err := httpc.Do(req) + require.NoError(t, err) + defer func() { _ = resp.Body.Close() }() + + respBody, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equalf(t, c.wantStatus, resp.StatusCode, + "%s via GET -> %s\n%s", c.method, resp.Status, respBody) + }) + } +} + // TestLockGRPCApi exercises the lock RPCs through a regular gRPC client // (google.golang.org/grpc). The same Connect handler serves gRPC framing // off the same port — used by PHP's gRPC extension. From f64e4fec91311d0028936ad010512dc3d3169fda Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Mon, 11 May 2026 19:14:03 +0200 Subject: [PATCH 09/10] refactor(tests): drop sync.OnceValue pool; allocate http.Client per call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync.OnceValue + package-level h2cClient was a micro-optimization that didn't belong in test code. newLockClient now builds a fresh *http.Client every call — simpler, no gochecknoglobals suppression, and the cost is negligible against the RPC round-trip itself. --- .vscode/launch.json | 15 --------------- go.work.sum | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/rpc.go | 14 +++----------- 3 files changed, 46 insertions(+), 26 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index e972b42..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}" - } - ] -} diff --git a/go.work.sum b/go.work.sum index 2561b5f..a4efd60 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,5 @@ +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= @@ -14,6 +16,8 @@ cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3 cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= @@ -36,6 +40,8 @@ cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYE dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -52,6 +58,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= @@ -61,12 +69,22 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -75,6 +93,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= @@ -183,6 +203,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= @@ -220,10 +242,14 @@ github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZig github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo= +github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= @@ -267,6 +293,8 @@ go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ= +go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= @@ -283,6 +311,7 @@ go.temporal.io/api v1.35.0 h1:+1o2zyBncLjnpjJt4FedKZMtuUav/LUgTB+mhQxx0zs= go.temporal.io/api v1.35.0/go.mod h1:OYkuupuCw6s/5TkcKHMb9EcIrOI+vTsbf/CGaprbzb0= go.temporal.io/api v1.36.0/go.mod h1:0nWIrFRVPlcrkopXqxir/UWOtz/NZCo+EE9IX4UwVxw= go.temporal.io/api v1.49.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/api v1.62.11 h1:MWDaooDvOJCIRb1atqeZX2ErDPNTsNc3/mMEVEvvaVU= go.temporal.io/api v1.62.11/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -294,6 +323,8 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= @@ -308,6 +339,8 @@ golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= @@ -324,16 +357,22 @@ golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -351,6 +390,8 @@ golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= @@ -378,6 +419,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d h1: google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Od4k8V1LQSizPRUK4OzZ7TBE/20k+jPczUDAEyvn69Y= google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= diff --git a/tests/rpc.go b/tests/rpc.go index 132031f..da7f515 100644 --- a/tests/rpc.go +++ b/tests/rpc.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "net" "net/http" - "sync" "connectrpc.com/connect" lockV1 "github.com/roadrunner-server/api-go/v6/lock/v1" @@ -15,12 +14,8 @@ import ( const lockRPCAddr = "127.0.0.1:6001" -// Shared h2c client; no client Timeout — server-side Wait is authoritative -// (and tests pass Wait values larger than any reasonable client deadline). -// -//nolint:gochecknoglobals // shared transport is the entire point — pools idle conns across tests -var h2cClient = sync.OnceValue(func() *http.Client { - return &http.Client{ +func newLockClient() lockV1connect.LockServiceClient { + httpc := &http.Client{ Transport: &http2.Transport{ AllowHTTP: true, DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { @@ -28,10 +23,7 @@ var h2cClient = sync.OnceValue(func() *http.Client { }, }, } -}) - -func newLockClient() lockV1connect.LockServiceClient { - return lockV1connect.NewLockServiceClient(h2cClient(), "http://"+lockRPCAddr) + return lockV1connect.NewLockServiceClient(httpc, "http://"+lockRPCAddr) } func lock(resource, id string, ttl, wait int) (bool, error) { From 5e9bdea4da26f122706c54dbb1fee03884caef52 Mon Sep 17 00:00:00 2001 From: Valery Piashchynski Date: Mon, 11 May 2026 19:15:03 +0200 Subject: [PATCH 10/10] chore: restore .vscode/launch.json (accidentally dropped in f64e4fe) --- .vscode/launch.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e972b42 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +}