[sdk] Parse gRPC messages with a raised protobuf recursion limit - #2282
[sdk] Parse gRPC messages with a raised protobuf recursion limit#2282morhaf-ko wants to merge 3 commits into
Conversation
Property values travel as protobuf Structs, where one level of a user-visible object costs three levels of protobuf nesting. Protobuf's Java runtime stops at 100, and gRPC's default marshaller keeps that default, so a provider returning deeply nested properties fails to deserialize on the channel and aborts the whole program rather than one resource registration. Rebuild the generated marshallers with the limit raised to match the engine's, on the monitor and engine channels for responses and on the provider service for requests. Fixes pulumi#2277
|
The three red checks here are the fork-OIDC limitation, not the patch.
To cover what CI could not run, I ran the suite locally against this branch (mise toolchain, JDK 11, Gradle 8.14.1):
For a trusted run, I think hosting it from a One reviewable judgement call worth your eyes: I set the limit to 10,000 to match what protobuf-go parses with, so the Java SDK stops being the narrower end of the connection. It is deliberately bounded rather than |
Note in passing that the raised limit is not the binding constraint: the JVM stack runs out first, since protobuf parses recursively.
|
Thanks - I checked this branch against the 300 levels Where Java sits, measured on this branch:
So the raised limit is not what binds any more - protobuf parses recursively, and the thread's stack gives out somewhere between 350 and 1000 levels, well before the 10,000 limit is reached. That answers the "why 10,000" question better than my earlier note did: the constant exists to stop protobuf's artificial cap of 100 from being the limit, and past that the runtime's own limit applies. I have said as much in a comment on Your 350 observation matches ours, for whatever that is worth as a cross-check.
|
Fixes #2277.
What happens today
Resource inputs and outputs travel as protobuf
Structs, where one level of a user-visible object costs three levels of protobuf nesting:Struct-> map entry ->Value. Protobuf's Java runtime refuses to parse past 100 levels, so the Java SDK tops out at roughly 33 levels of property nesting.gRPC's default marshaller keeps that default and, until recently, offered no way to change it (grpc/grpc-java#8256). When a provider returns something deeper - the recursive JSON Schema shapes AWS flattens into long generated type chains, for example - the parse fails on the channel rather than on the resource:
Because the failure is on the channel, the whole program aborts: no resource in the stack gets a plan, and no resource is named in the error.
The fix
grpc-java has exposed
ProtoUtils.marshallerWithRecursionLimitsince 1.56, and this repo is on 1.57.2, so no dependency bump is needed.GrpcRecursionLimitrebuilds the generated marshallers with the limit raised to 10,000 - matching what the Go engine parses with, so the Java SDK is no longer the narrower end of the connection - and installs them:GrpcMonitorandGrpcEngineadd a channel interceptor that swaps in the raised response marshaller. This is the path in the issue.ResourceProviderServicerebinds its service definition so a Java component provider parses deeply nested requests too. AServerInterceptorcannot do this: the method descriptor has already deserialized the request by the time one is consulted.Serialization is untouched - protobuf imposes no depth limit when writing.
Tests
GrpcRecursionLimitTestcovers both directions with 64 levels ofStructnesting, which is comfortably past the default:monitorParsesDeeplyNestedResponsesruns a realResourceMonitorserver over loopback and calls it throughGrpcMonitor, so it exercises the wiring and a real serialize/parse round trip. (An in-process channel would not reproduce the bug - gRPC hands the message across without serializing it.)reboundServiceParsesDeeplyNestedRequestsandreboundServiceKeepsItsMethodsAndHandlerscover the server-side rebind.I checked that
monitorParsesDeeplyNestedResponsesfails with the originalINTERNAL: Invalid protobuf byte sequencewhen the interceptor is removed fromGrpcMonitor.Notes
StackOverflowError. Happy to move it if you would rather it were lower.pulumi/pulumi- so it is up for grabs alongside this.