-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Add support for custom transport (in-process, wasm) #906
Comments
This can be done by providing a custom dialer (WithDialer) to the client and a custom listener to the server. |
Thanks for replying. However, even a loopback-connection will still require the serialization and de-serialization part of all the requests and metadata. That's kind of a waste if we already have the correctly typed things at either end. So this feature request is specifically about bypassing all of that. I haven't yet found a way to achieve that. For example, even if I were to try to wrap it myself and use a codegen, I can't actually inspect the passed CallOptions (and thus any sent metadata, which makes it kind of a no-starter) due to the way it's set up. So, from what I can tell, support for this would need to come from the grpc package itself. |
To bypass all of these overhead, we need an in-process-transport impl (in addition to http2Client and http2Server transport) in the transport package and the corresponding custom codec, listener, etc.. We did think about this previously but this is not a trivial change and we do not have enough hands covering this now unfortunately. As an alternative, I think if you can add a wrapper on the top of the grpc generated code to switch on in-process and normal case you should probably achieve this without any changes in grpc library (I have not thought through all the detail and could be wrong though). |
This would be really useful for @yugui and grpc-gateway use case. |
Added an enhancement label to this, but the work is not prioritized at the moment. |
It seems that this is already available for grpc development in Java: http://www.grpc.io/grpc-java/javadoc/io/grpc/inprocess/package-summary.html |
In the .NET world, this is something to be expected. OWIN does it really well and allows you to build HTTP-enabled components and host them anywhere. If you host it in-process, all the requests happen completely in-memory. |
@iamqizhao wrote:
Unfortunately, this is not necessarily the case. The main issue for an in-process implementation is that the I brought this up on the mailing list some time ago: I'm now re-visisting this issue because I've hit it again with something else I'm trying to do: a client interceptor to do client-side retries that can support "hedging". With hedging, the interceptor may issue a retry before the prior attempt completes -- triggered by a timeout vs. a failure. But header/trailer metadata for only the last/successful attempt should be used for the header and trailer call options. Since these types are totally opaque, it isn't possible for an interceptor to do what it needs to do. And if it just passes along the options, without any changes, to multiple concurrent attempts, it is both non-deterministic as well as unsafe/racy regarding how the client-provided metadata addresses will get set. |
FWIW, retries and hedging are coming to gRPC-Go natively in a month or two. Relevant gRFC. Regarding the initial issue, we have since created the bufconn package to at least bypass the network stack (message are still [de]serialized and everything goes through the http2 transport, but this should help with overhead somewhat). Otherwise, the team still doesn't have the bandwidth to take on an in-process transport any time soon. |
It would be nice if the API were at least amenable to a 3rd party library supplying this. Unfortunately, it is currently not due to #1495. |
Any updates on this? /cc @stevvooe @crosbymichael I don't think |
There are still a couple of issues that prevent a 3rd-party package from providing an in-process channel: #1495 and #1802, However, that hasn't stopped me! Take a look at these: The above issues do represent shortcomings though. They basically mean that call options are ignored by custom channel implementations (which means you can't get response headers and trailers from a unary RPC). And there is a similar issue on the server side: you cannot set response headers or trailers from a unary RPC implementation. @Random-Liu, I don't really understand why you can't use |
We were literally talking about this yesterday, so we haven't forgotten about it. However, it's still not at the top of our priority list. It will probably be another 6 months before we can get to it. Our other priorities right now are channelz, performance, and retry. It would be a fairly meaty project if an outside contributor wanted to take it on, but we would be able to advise.
Skipping serialization/deserialization seems dangerous in Go, because proto messages are mutable. This means that if the server modifies the request message, then the client would see the result of that modification. (The same is true for streaming in either direction.) This shouldn't typically be happening, but it's a notable difference between a real server and |
No, this does not happen. The library avoids serialization/de-serialization, but it does copy. |
@jhump Because we may also want to get rid of the serialization/de-serialization, :) With https://godoc.org/github.com/fullstorydev/grpchan/inprocgrpc, can I make the grpc server serve both remote requests and inproc requests at the same time? |
Aha, I see now. So you do a |
As per #241, it says there will be new interface provided that would make possible to add UDP transport as well.It would be really helpful for games. Is there any update on that? If not, is there anyway to use UDP client/transport underneath GRPC? |
This was sadly deprioritized before I could get it finished. There's a bit-rotting prototype of all the client-side API changes and a shim in the existing transport here. Aside from one minor thing I'd like to change in the design (namely the use of EDIT: Also, the prototype does not have the server-side implementation done, which could be quite complex. |
Thanks for this discussion. I actually would like to +1 on this heavily, however our use case might be different. We are leveraging gRPC a lot in Thanos projects and it helps enormously. The thing is that Thanos, similar to Google Monarch (if you are familiar), has this hierarchical node API strategy with gRPC in between. Let's take for example one gRPC service: /// Store represents API against instance that stores XOR encoded values with
// label set metadata (e.g Prometheus metrics).
service Store {
rpc Info(InfoRequest) returns (InfoResponse);
rpc Series(SeriesRequest) returns (stream SeriesResponse);
rpc LabelNames(LabelNamesRequest) returns (LabelNamesResponse);
rpc LabelValues(LabelValuesRequest) returns (LabelValuesResponse);
} Within this, we have many implementations, but one of those is a simple "fanout" that fanouts and merges responses together and proxies them to the caller (called proxy) Long story short, we have many cases when one microservice want to either:
I am having a hard time understanding why there is no existing logic for this in the current gRPC generate code? Because in-process transport is one thing, but I don't care about all interceptors or sometimes about headers, trailers, and metadata when I invoke the server method in the same process. So something like ServerAsClient generated converter code would be easy to create, no? What we use right now is something like this: Can't we just ensure the Go gRPC generator will generate such well tested, benchmarked etc converter for each method? (it's trivial for unary rpcs, bit more complex for streaming). WDYT? 🤗 EDIT: Testing is another use case we leverage on a lot as well (thanks @glerchundi for reminding) |
+1 to what @bwplotka is proposing. We're doing exactly that to cover two different use cases:
Thanks for raising 😊 |
@bwplotka did you gave a try to https://github.com/fullstorydev/grpchan/tree/master/inprocgrpc mentioned earlier in this thread? It's not exactly what you need but would simplify things as you could then just bind channels? Or maybe even just communicate directly between the far-ends, bypassing the proxy part at all. I did not spend much time reading through your current code so I may miss the idea of your architecture, but I'm very successful using grpchan (kudos @jhump !) and just thought you might find it helpful as I did |
Been a while since looked at or thought about this issues, but I popped up in my notifications so I got caught up. This isn’t necessarily a solution, but it is interesting and could be learned from. I stumbled across the https://pkg.go.dev/google.golang.org/grpc/test/bufconn package this week and am using it in testing (as it was intended based on the package name). Anyway, it enables your client and service to run in a single process using an in memory transport. Works great (for testing at least). You can see my use if it here https://github.com/textileio/textile/blob/asutula/fil-rewards-bookkeeping/api/filrewardsd/service/service_test.go#L411 |
Thanks all for your responses.
NOTE: I doubt in-process network for light e2e test purposes is a sensible request. For this, you want something like virtual |
…PC client Note that the generated gateway code has a comment about this: // RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to "mux". // UnaryRPC :call ImmuServiceServer directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead. func RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error {
…PC client Note that the generated gateway code has a comment about this: // RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to "mux". // UnaryRPC :call ImmuServiceServer directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead. func RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error {
…PC client Note that the generated gateway code has a comment about this: // RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to "mux". // UnaryRPC :call ImmuServiceServer directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead. func RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error {
…PC client Note that the generated gateway code has a comment about this: // RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to "mux". // UnaryRPC :call ImmuServiceServer directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead. func RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error {
* feat(pkg/server): embedded REST API / console server * chore: netgo tag is not required since Go 1.5 https://golang.org/doc/go1.5#net * feat: embedd webconsole with Go < 1.16 * chore: use go run to run statik * chore: add test for embedded API/web console server * chore(pkg/server/webserver): Use ImmuService directly instead of a gRPC client Note that the generated gateway code has a comment about this: // RegisterImmuServiceHandlerServer registers the http handlers for service ImmuService to "mux". // UnaryRPC :call ImmuServiceServer directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterImmuServiceHandlerFromEndpoint instead. func RegisterImmuServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server ImmuServiceServer) error { * chore: expose web server settings * chore: embed from a contained dist directory * chore: regenerate files * feat: add TLS support to the webconsole * chore: fix webserver test * chore: document webconsole build * chore: method not longer needed * chore: add test for webserver options * chore(pkg/server): increase coverage for Options * chore(pkg/server): increase coverage for Options
Ran `go run ./internal/cmd/fetcher plugins/grpc-ecosystem ` and updated the patch for the version. [upstream release changelog](grpc-ecosystem/grpc-gateway@v2.20.0...v2.21.0) Patch diff: ``` diff plugins/grpc-ecosystem/gateway/v2.21.0/separate_pkg_additional_imports.patch plugins/grpc-ecosystem/gateway/v2.20.0/separate_pkg_additional_imports.patch 71c71 < index 684fc5b6..c1b69363 100644 --- > index 421c0089..26bed31a 100644 88c88 < @@ -255,6 +262,9 @@ func (r *Registry) loadFile(filePath string, file *protogen.File) { --- > @@ -252,6 +259,9 @@ func (r *Registry) loadFile(filePath string, file *protogen.File) { 577c577 < index 9975e424..13332ef7 100644 --- > index ffe4d413..fdd9bc5b 100644 649c649 < @@ -606,6 +624,9 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct --- > @@ -606,12 +624,18 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct 659c659 < @@ -613,6 +634,9 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct --- > // UnaryRPC :call {{$svc.GetName}}Server directly. 662d661 < // GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. 669c668 < @@ -664,6 +688,7 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, --- > @@ -663,6 +687,7 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, 677c676 < @@ -671,6 +696,9 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, --- > @@ -670,6 +695,9 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, 687c686 < @@ -693,18 +721,26 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint(ctx context.Co --- > @@ -692,18 +720,26 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint(ctx context.Co 707c706 < // "{{$svc.InstanceName}}Client" to call the correct interceptors. This client ignores the HTTP middlewares. --- > // "{{$svc.InstanceName}}Client" to call the correct interceptors. 714c713 < @@ -785,5 +821,9 @@ var ( --- > @@ -784,5 +820,9 @@ var ( 726c725 < index 086a4624..5f65b894 100644 --- > index 5a1e6977..7e89ad1c 100644 737c736 < @@ -36,6 +37,7 @@ var ( --- > @@ -35,6 +36,7 @@ var ( 745c744 < @@ -73,15 +75,22 @@ func main() { --- > @@ -58,15 +60,22 @@ func main() { 772c771 < @@ -135,6 +144,7 @@ func applyFlags(reg *descriptor.Registry) error { --- > @@ -120,6 +129,7 @@ func applyFlags(reg *descriptor.Registry) error { ``` Upstream diff in plugin ``` git --no-pager diff v2.20.0..v2.21.0 protoc-gen-grpc-gateway diff --git a/protoc-gen-grpc-gateway/internal/gengateway/template.go b/protoc-gen-grpc-gateway/internal/gengateway/template.go index ffe4d413..9975e424 100644 --- a/protoc-gen-grpc-gateway/internal/gengateway/template.go +++ b/protoc-gen-grpc-gateway/internal/gengateway/template.go @@ -612,6 +612,7 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct // UnaryRPC :call {{$svc.GetName}}Server directly. // StreamingRPC :currently unsupported pending grpc/grpc-go#906. // Note that using this registration option will cause many gRPC library features to stop working. Consider using Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, mux *runtime.ServeMux, server {{$svc.InstanceName}}Server) error { {{range $m := $svc.Methods}} {{range $b := $m.Bindings}} @@ -703,7 +704,7 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}(ctx context.Context, mux * // to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "{{$svc.InstanceName}}Client". // Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "{{$svc.InstanceName}}Client" // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in -// "{{$svc.InstanceName}}Client" to call the correct interceptors. +// "{{$svc.InstanceName}}Client" to call the correct interceptors. This client ignores the HTTP middlewares. func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client(ctx context.Context, mux *runtime.ServeMux, client {{$svc.InstanceName}}Client) error { {{range $m := $svc.Methods}} {{range $b := $m.Bindings}} diff --git a/protoc-gen-grpc-gateway/main.go b/protoc-gen-grpc-gateway/main.go index 5a1e6977..086a4624 100644 --- a/protoc-gen-grpc-gateway/main.go +++ b/protoc-gen-grpc-gateway/main.go @@ -13,6 +13,7 @@ import ( "flag" "fmt" "os" + "runtime/debug" "strings" "github.com/grpc-ecosystem/grpc-gateway/v2/internal/codegenerator" @@ -50,6 +51,20 @@ func main() { flag.Parse() if *versionFlag { + if commit == "unknown" { + buildInfo, ok := debug.ReadBuildInfo() + if ok { + version = buildInfo.Main.Version + for _, setting := range buildInfo.Settings { + if setting.Key == "vcs.revision" { + commit = setting.Value + } + if setting.Key == "vcs.time" { + date = setting.Value + } + } + } + } fmt.Printf("Version %v, commit %v, built at %v\n", version, commit, date) os.Exit(0) } ```
Say I want, for example
So, I want to be able to implement a FooServer and then connect to it from the same process. Of course, I could just listen on localhost and connect to that or something like that, but then I'd pay the penalty of serializing and deserializing everything and running the bytes through the kernel (which is significant when it's in the path of talking to your database, for example).
Instead, it would be cool if grpc allowed me to get a "local" connection, like
func LocalPair() (*grpc.Client, *grpc.Server)
, which doesn't use a network at all and just directly passes theproto.Message
s around.I'd be willing to try to implement that myself, but first I wanted to ask if this is a use case you'd be willing to support.
The text was updated successfully, but these errors were encountered: