Coder Social home page Coder Social logo

Comments (3)

aarongable avatar aarongable commented on August 19, 2024

Slight change of plan. Setting require_unimplemented_servers=false and removing the foopb.UnimplementedFooServer embed from all of our implementation structs works, but it also means that those structs don't have an obvious thing declaring up-front that they are attempting to implement a gRPC server.

So instead I'm going to leave the require_unimplemented_servers=true (the default), but change all of our implementation structs to embed foopb.UnsafeFooServer, which only provides the mustEmbedUnimplementedFooServer method, without providing "unimplemented" versions of all the other API methods. This way our impl structs will all have an obvious declaration of intent upfront, but we'll still get the improvements to compile-time type checking discussed above.

from boulder.

aarongable avatar aarongable commented on August 19, 2024

Ah crudbuckets, and in the process of exploring this I have found a stumbling block: The compile-time type checking comes by virtue of calling the auto-generated RegisterFooServer function, which ensures that its argument fully implements the corresponding auto-generated FooServer interface. For example:

func RegisterAkamaiPurgerServer(s grpc.ServiceRegistrar, srv AkamaiPurgerServer) {
s.RegisterService(&AkamaiPurger_ServiceDesc, srv)
}

type AkamaiPurgerServer interface {
Purge(context.Context, *PurgeRequest) (*emptypb.Empty, error)
mustEmbedUnimplementedAkamaiPurgerServer()
}

But we don't actually call those RegisterFooServer functions. We add our server implementations to a server builder, and that builder skips straight to the inner RegisterService call:

start, err := bgrpc.NewServer(c.AkamaiPurger.GRPC, logger).Add(
&akamaipb.AkamaiPurger_ServiceDesc, ap).Build(tlsConfig, scope, clk)

boulder/grpc/server.go

Lines 181 to 183 in 5be3650

for _, service := range sb.services {
server.RegisterService(service.desc, service.impl)
}

I'm still looking into how best (how most elegantly) to get this type safety anyway.

from boulder.

aarongable avatar aarongable commented on August 19, 2024

Consolation prize: the integration tests will fail if any of our impl types don't actually implement the gRPC interface they want to, because the underlying server.RegisterService function uses reflect to check:

ht := reflect.TypeOf(sd.HandlerType).Elem()
st := reflect.TypeOf(ss)
if !st.Implements(ht) {
logger.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht)

But I'd still prefer to do this at compile time, to get editor integration and to prevent mistakes for any gRPC services which (somehow) aren't started in our integration tests.

One option, of course, is to just do it like everyone else does it, and add a line like this below all of our gRPC impl struct type declarations:

var _ akamaipb.AkamaiPurgerServer = (*akamaiPurger)(nil)

Upside: it just works, it's idiomatic. Downside: it doesn't work automatically -- if someone forgets to add a line like this, no compile-time errors for them. Since they have to register their server at some point, it would be nice to do the check then.

So I tried a much more convoluted (but very pleasing to me) approach using generics and curried functions! Wheee I'm not going overboard at all!

// curriedReg is a curried (i.e. some of the arguments have been populated)
// version of a gRPC auto-generated registration function like
// foopb.RegisterFooServer. Its second argument, the impl to be registered,
// has already been provided via CurryReg.
type curriedReg func(grpc.ServiceRegistrar)

// CurryReg takes an auto-generated service registration func (such as
// foopb.RegisterFooServer) and a gRPC service implementation (such as would be
// passed as the second argument to such a function) and returns a function
// which, when passed a service registrar, will use the registration function
// to register the impl on that registrar.
func CurryReg[T any](reg func(grpc.ServiceRegistrar, T), impl T) curriedReg {
	return func(s grpc.ServiceRegistrar) { reg(s, impl) }
}
	start, err := bgrpc.NewServer(c.AkamaiPurger.GRPC, logger).Add(
		&akamaipb.AkamaiPurger_ServiceDesc, grpc.CurryReg(akamaipb.RegisterAkamaiPurgerServer, ap), ap).Build(tlsConfig, scope, clk)

But it turns out this falls down in two ways:

  1. It becomes very verbose and redundant. It seems like we should be able to pass only the curried reg func into bgrpc.Server{}.Add(), but we actually need direct access to both the ServiceDesc and the impl itself in order to automatically instantiate health-check services.
  2. For reasons I still don't quite understand, go doesn't actually like grpc.CurryReg(akamaipb.RegisterAkamaiPurgerServer, ap). It throws a "CannotInferTypeArgs" error, meaning that it can't infer that this should be shorthand for grpc.CurryReg[akamaipb.AkamaiPurgerServer](akamaipb.RegisterAkamaiPurgerServer, ap), even though akamaipb.AkamaiPurgerServer is explicitly the type of the second argument to the registration function, and is an interface implemented by the impl. So it forces you to explicitly include the type parameter, and that makes the verbosity and redundancy problem in (1) even worse.

Still digging.

from boulder.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.