From 81418f9352056245c58ac43bf2271ce3c71c3bed Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 12 Oct 2026 21:50:38 +0400 Subject: [PATCH 1/3] fix: initialize MsgpackCodec with rybakit/msgpack when ext-msgpack is missing The library branch fell through to the LogicException, so the codec could only be built with ext-msgpack. Assisted-By: Claude Opus 5.5 --- src/RPC/Codec/MsgpackCodec.php | 2 ++ tests/Goridge/MsgpackCodecTest.php | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RPC/Codec/MsgpackCodec.php b/src/RPC/Codec/MsgpackCodec.php index d901cd5..3e59145 100644 --- a/src/RPC/Codec/MsgpackCodec.php +++ b/src/RPC/Codec/MsgpackCodec.php @@ -76,6 +76,8 @@ private function initPacker(): void $this->pack = static fn(mixed $payload): string => MessagePack::pack($payload); $this->unpack = static fn(string $payload, $options = null): mixed => MessagePack::unpack($payload, $options); + + return; } throw new \LogicException('Could not initialize codec, please install msgpack extension or library'); diff --git a/tests/Goridge/MsgpackCodecTest.php b/tests/Goridge/MsgpackCodecTest.php index 0f63a03..8532e63 100644 --- a/tests/Goridge/MsgpackCodecTest.php +++ b/tests/Goridge/MsgpackCodecTest.php @@ -9,7 +9,6 @@ use Testo\Assert; use Testo\Codecov\Covers; use Testo\Core\Exception\SkipTest; -use Testo\Skip; use Testo\Test; #[Test] @@ -29,7 +28,6 @@ public function testRoundTripWithExtension(): void Assert::same($codec->decode($codec->encode($payload)), $payload); } - #[Skip('MsgpackCodec::initPacker() lacks a return after the rybakit/msgpack branch, so without ext-msgpack the constructor always throws LogicException')] public function testRoundTripWithLibrary(): void { if (\extension_loaded('msgpack')) { From b30e633c94643a03599c2bf6aff22febba3c3b76 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 12 Oct 2026 21:50:41 +0400 Subject: [PATCH 2/3] feat: deprecate MsgpackCodec and Frame::CODEC_MSGPACK RoadRunner 3 runs Goridge v4, which drops the connection on a MessagePack frame, so the codec cannot work with spiral/roadrunner ^3.0. The class stays until the next major; its constructor raises a silenced E_USER_DEPRECATED. Assisted-By: Claude Opus 5.5 --- README.md | 7 +++++-- composer.json | 5 +---- psalm-baseline.xml | 5 ++++- src/Frame.php | 7 +++++++ src/RPC/Codec/MsgpackCodec.php | 11 +++++++++++ tests/Goridge/MsgpackCodecTest.php | 19 +++++++++++++++++++ 6 files changed, 47 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9c70535..d6e67da 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,10 @@ More examples, including the Go server side, can be found in [this directory](./ ### Codecs -JSON is the default codec. Use `withCodec()` to switch to `MsgpackCodec`, `ProtobufCodec` or `RawCodec` from the `Spiral\Goridge\RPC\Codec` namespace. +JSON is the default codec. Use `withCodec()` to switch to `ProtobufCodec` or `RawCodec` from the `Spiral\Goridge\RPC\Codec` namespace. + +> [!WARNING] +> `MsgpackCodec` is deprecated and will be removed in the next major version: RoadRunner 3 (Goridge v4) does not support MessagePack. ## Features @@ -71,7 +74,7 @@ JSON is the default codec. Use `withCodec()` to switch to `MsgpackCodec`, `Proto - very fast (300k calls per second on Ryzen 1700X over 20 threads) - native `net/rpc` integration, ability to connect to existing application(s) - standalone protocol usage - - structured data transfer using JSON, MessagePack or Protobuf, and raw payloads + - structured data transfer using JSON or Protobuf, and raw payloads - `[]byte` transfer, including big payloads - asynchronous calls over a pool of relays with `MultiRPC` - service, message and transport level error handling diff --git a/composer.json b/composer.json index 0efd25c..d2412b9 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ "relay", "socket", "pipes", - "msgpack", "protobuf" ], "license": "MIT", @@ -72,10 +71,8 @@ "spiral/goridge": "self.version" }, "suggest": { - "ext-msgpack": "MessagePack codec support", "ext-protobuf": "Protobuf codec support", - "google/protobuf": "(^3.22 || ^4.0 || ^5.0) Protobuf codec support", - "rybakit/msgpack": "(^0.7) MessagePack codec support" + "google/protobuf": "(^3.22 || ^4.0 || ^5.0) Protobuf codec support" }, "autoload": { "psr-4": { diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5247a99..b1f67f2 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,6 +1,9 @@ - + + + + diff --git a/src/Frame.php b/src/Frame.php index 6ad57b7..74b70b0 100644 --- a/src/Frame.php +++ b/src/Frame.php @@ -38,7 +38,14 @@ final class Frame */ public const CODEC_RAW = 0x04; public const CODEC_JSON = 0x08; + + /** + * @deprecated RoadRunner 3 (Goridge v4) does not support MessagePack: the server drops the connection + * on a frame with this flag. Use {@see self::CODEC_JSON} (the default) or {@see self::CODEC_PROTO} + * instead. The constant will be removed in the next major version. + */ public const CODEC_MSGPACK = 0x10; + public const CODEC_GOB = 0x20; public const CODEC_PROTO = 0x80; /*#@-*/ diff --git a/src/RPC/Codec/MsgpackCodec.php b/src/RPC/Codec/MsgpackCodec.php index 3e59145..aa04a3a 100644 --- a/src/RPC/Codec/MsgpackCodec.php +++ b/src/RPC/Codec/MsgpackCodec.php @@ -11,9 +11,17 @@ /** * @psalm-type PackHandler = \Closure(mixed): string * @psalm-type UnpackHandler = \Closure(string, mixed|null): mixed + * + * @deprecated RoadRunner 3 (Goridge v4) does not support MessagePack: the server drops the connection + * on a MessagePack frame. Use the default {@see JsonCodec} or {@see ProtobufCodec} instead. + * The codec will be removed in the next major version. */ final class MsgpackCodec implements CodecInterface { + private const DEPRECATION = 'Spiral\Goridge\RPC\Codec\MsgpackCodec is deprecated: RoadRunner 3 (Goridge v4)' + . ' does not support MessagePack. Use JsonCodec or ProtobufCodec instead.' + . ' The codec will be removed in the next major version.'; + /** * @var PackHandler * @psalm-suppress PropertyNotSetInConstructor Reason: Initialized via {@see initPacker()} @@ -31,6 +39,9 @@ final class MsgpackCodec implements CodecInterface */ public function __construct() { + // Silenced: the notice reaches only error handlers that collect deprecations, the default one stays quiet. + @\trigger_error(self::DEPRECATION, \E_USER_DEPRECATED); + $this->initPacker(); } diff --git a/tests/Goridge/MsgpackCodecTest.php b/tests/Goridge/MsgpackCodecTest.php index 8532e63..eaf0873 100644 --- a/tests/Goridge/MsgpackCodecTest.php +++ b/tests/Goridge/MsgpackCodecTest.php @@ -40,4 +40,23 @@ public function testRoundTripWithLibrary(): void Assert::same($codec->getIndex(), Frame::CODEC_MSGPACK); Assert::same($codec->decode($codec->encode($payload)), $payload); } + + public function testConstructorTriggersDeprecation(): void + { + $notices = []; + \set_error_handler(static function (int $level, string $message) use (&$notices): bool { + $notices[] = [$level, $message]; + return true; + }); + + try { + new MsgpackCodec(); + } finally { + \restore_error_handler(); + } + + Assert::count($notices, 1); + Assert::same($notices[0][0], \E_USER_DEPRECATED); + Assert::string($notices[0][1])->contains('MsgpackCodec is deprecated'); + } } From 254376fa6b01de5f6262eb117067872e8cae3424 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Mon, 12 Oct 2026 21:50:42 +0400 Subject: [PATCH 3/3] test: run the test server on Goridge v4 The v3 server still accepted MessagePack, so the MsgPack RPC suites passed against a protocol RoadRunner 3 no longer speaks. They are replaced by one test that pins the dropped connection, and the clone test switches its second codec to raw. Goridge v4 requires Go 1.27. Assisted-By: Claude Opus 5.5 --- tests/Goridge/MsgPackMultiRPCTest.php | 43 --------------------------- tests/Goridge/MsgPackRPCTest.php | 32 ++++++++++++-------- tests/Goridge/MultiRPC.php | 24 ++------------- tests/test-server/Dockerfile | 2 +- tests/test-server/go.mod | 11 +++---- tests/test-server/go.sum | 36 ++++++++-------------- tests/test-server/server.go | 2 +- 7 files changed, 40 insertions(+), 110 deletions(-) delete mode 100644 tests/Goridge/MsgPackMultiRPCTest.php diff --git a/tests/Goridge/MsgPackMultiRPCTest.php b/tests/Goridge/MsgPackMultiRPCTest.php deleted file mode 100644 index b1ec46e..0000000 --- a/tests/Goridge/MsgPackMultiRPCTest.php +++ /dev/null @@ -1,43 +0,0 @@ -rpc->call('Service.Process', random_bytes(256)); - } - - public function testJsonExceptionAsync(): void - { - $id = $this->rpc->callAsync('Service.Process', random_bytes(256)); - Expect::exception(ServiceException::class); - $this->rpc->getResponse($id); - } - - public function testJsonExceptionNotThrownWithIgnoreResponse(): void - { - $this->rpc->callIgnoreResponse('Service.Process', random_bytes(256)); - $this->forceFlushRpc(); - } - - protected function makeRPC(int $count = 10): void - { - parent::makeRPC($count); - $this->rpc = $this->rpc->withCodec(new MsgpackCodec()); - } -} diff --git a/tests/Goridge/MsgPackRPCTest.php b/tests/Goridge/MsgPackRPCTest.php index 3e60714..9585975 100644 --- a/tests/Goridge/MsgPackRPCTest.php +++ b/tests/Goridge/MsgPackRPCTest.php @@ -4,29 +4,35 @@ namespace Spiral\Goridge\Tests; +use Spiral\Goridge\Exception\TransportException; use Spiral\Goridge\RPC\Codec\MsgpackCodec; -use Spiral\Goridge\RPC\Exception\ServiceException; -use Spiral\Goridge\RPC\RPC; -use Testo\Expect; +use Spiral\Goridge\RPC\RPC as GoridgeRPC; +use Spiral\Goridge\SocketRelay; +use Testo\Assert; use Testo\Test; +/** + * Goridge v4 refuses MessagePack frames, which is why {@see MsgpackCodec} is deprecated. + */ #[Test] -final class MsgPackRPCTest extends \Spiral\Goridge\Tests\RPC +final class MsgPackRPCTest { - /** - * @throws \Exception - */ - public function testJsonException(): void + public function testServerDropsConnectionOnMsgpackFrame(): void { - Expect::exception(ServiceException::class); + $rpc = (new GoridgeRPC($this->makeRelay()))->withCodec(new MsgpackCodec()); - $conn = $this->makeRPC(); + try { + $rpc->call('Service.Ping', 'ping'); + Assert::fail('The server should have refused the MessagePack frame'); + } catch (TransportException $e) { + Assert::notBlank($e->getMessage()); + } - $conn->call('Service.Process', random_bytes(256)); + Assert::same((new GoridgeRPC($this->makeRelay()))->call('Service.Ping', 'ping'), 'pong'); } - protected function makeRPC(): RPC + private function makeRelay(): SocketRelay { - return (new RPC($this->makeRelay()))->withCodec(new MsgpackCodec()); + return new SocketRelay(RPC::SOCK_ADDR, RPC::SOCK_PORT, RPC::SOCK_TYPE); } } diff --git a/tests/Goridge/MultiRPC.php b/tests/Goridge/MultiRPC.php index 01b4d2b..ae68ab5 100644 --- a/tests/Goridge/MultiRPC.php +++ b/tests/Goridge/MultiRPC.php @@ -7,8 +7,6 @@ use Spiral\Goridge\ConnectedRelayInterface; use Spiral\Goridge\Exception\TransportException; use Spiral\Goridge\RelayInterface; -use Spiral\Goridge\RPC\Codec\JsonCodec; -use Spiral\Goridge\RPC\Codec\MsgpackCodec; use Spiral\Goridge\RPC\Codec\RawCodec; use Spiral\Goridge\RPC\Exception\CodecException; use Spiral\Goridge\RPC\Exception\RPCException; @@ -700,10 +698,7 @@ public function testHandlesCloneCorrectly(): void { $this->rpc->preConnectRelays(); - // This is to support the MsgPackMultiRPC Tests - $property = new \ReflectionProperty(GoridgeMultiRPC::class, 'codec'); - $codec = $property->getValue($this->rpc); - $clonedRpc = $this->rpc->withCodec($codec instanceof MsgpackCodec ? new JsonCodec() : new MsgpackCodec()); + $clonedRpc = $this->rpc->withCodec(new RawCodec()); $property = new \ReflectionProperty(GoridgeMultiRPC::class, 'freeRelays'); foreach ($property->getValue() as $relay) { @@ -719,27 +714,14 @@ public function testHandlesCloneCorrectly(): void } for ($i = 0; $i < 50; $i++) { - $clonedIds[] = $clonedRpc->callAsync('Service.Echo', 'Hello'); + $clonedIds[] = $clonedRpc->callAsync('Service.EchoBinary', 'Hello'); } // Wait 100ms for the response(s) usleep(100 * 1000); // Can use wrong RPC for response (unfortunately, but there's no easy solution) try { - $response = $this->rpc->getResponse($clonedIds[0]); - $property = new \ReflectionProperty(GoridgeMultiRPC::class, 'codec'); - - if ($property->getValue($this->rpc) instanceof MsgpackCodec) { - // Msgpack internally does not throw an error, only returns the encoded response because of course why - // would normal error handling be something that is important in a library. - // Locally this returned the number 34, but I'm not sure if there's some variation in that - // so we test on the expected response. - // This also notifies PHPUnit since msgpack logs a warning. - if ($response !== 'Hello') { - throw new CodecException("msgpack is a big meany"); - } - } - + $this->rpc->getResponse($clonedIds[0]); \Testo\Assert::fail("Should've thrown an Exception due to wrong codec"); } catch (CodecException $exception) { \Testo\Assert::false(empty($exception->getMessage())); diff --git a/tests/test-server/Dockerfile b/tests/test-server/Dockerfile index 2289add..80c0b75 100644 --- a/tests/test-server/Dockerfile +++ b/tests/test-server/Dockerfile @@ -1,5 +1,5 @@ # Image page: -FROM golang:1.22-alpine as builder +FROM golang:1.27-alpine as builder # Copy server sources into image COPY ./go.mod ./go.sum ./server.go /src/ diff --git a/tests/test-server/go.mod b/tests/test-server/go.mod index 7ac9f3b..076cd83 100644 --- a/tests/test-server/go.mod +++ b/tests/test-server/go.mod @@ -1,13 +1,10 @@ module test_server -go 1.22.2 +go 1.27 -require github.com/roadrunner-server/goridge/v3 v3.8.2 +require github.com/roadrunner-server/goridge/v4 v4.0.0 require ( - github.com/goccy/go-json v0.10.2 // indirect - github.com/roadrunner-server/errors v1.4.0 // indirect - github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect - github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - google.golang.org/protobuf v1.34.0 // indirect + github.com/roadrunner-server/errors v1.5.0 // indirect + google.golang.org/protobuf v1.36.12 // indirect ) diff --git a/tests/test-server/go.sum b/tests/test-server/go.sum index dfec05e..c71e364 100644 --- a/tests/test-server/go.sum +++ b/tests/test-server/go.sum @@ -1,24 +1,12 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/roadrunner-server/errors v1.4.0 h1:Odjg3VZrj1q5Y8ILwoN+JgERyv0pkhrWPNOM4h68iQ8= -github.com/roadrunner-server/errors v1.4.0/go.mod h1:78PvraAFj+Sxy5nDmo0S+h6rEMLFIDszWZxA3B0sPAs= -github.com/roadrunner-server/goridge/v3 v3.8.2 h1:4TpIJAMylMIVTva/L/STB4ZvYNVoQ77+Syr6abxj95c= -github.com/roadrunner-server/goridge/v3 v3.8.2/go.mod h1:7IIDW50j1saxnOxktFeUPpkSIfyvM/dYopTrbGWXboA= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= -github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -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= -google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/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 h1:hyhDQggB8wYfT6tE3yso8lezOprz9BFrkjjzglAlG2Q= +github.com/roadrunner-server/goridge/v4 v4.0.0/go.mod h1:ahS0+nR0wRB64a8ejYaBNIjks88zEMX04Ksg1tVm/Dk= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= +google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/tests/test-server/server.go b/tests/test-server/server.go index 639954e..c1e46eb 100644 --- a/tests/test-server/server.go +++ b/tests/test-server/server.go @@ -7,7 +7,7 @@ import ( "strings" "time" - goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc" + goridgeRpc "github.com/roadrunner-server/goridge/v4/pkg/rpc" ) // Service