diff --git a/pkg/app/app_init.go b/pkg/app/app_init.go index 6b0fd1c..54763ff 100644 --- a/pkg/app/app_init.go +++ b/pkg/app/app_init.go @@ -10,7 +10,9 @@ import ( srvhttp "gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http" ) -func (a *App) initGRPC() { +// TODO: Implement +func (a *App) initGRPC(ctx context.Context) error { + return nil } func (a *App) initHTTP(ctx context.Context) error { diff --git a/pkg/app/app_types.go b/pkg/app/app_types.go index e260ce0..85d8af5 100644 --- a/pkg/app/app_types.go +++ b/pkg/app/app_types.go @@ -31,9 +31,10 @@ type AppGRPC struct { } type GRPCService struct { - Name string // Descriptive name of service - Type *grpc.ServiceDesc // Type (from protoc generated code) - Service any // Implementation of GRPCService.Type (ptr) + Name string // Descriptive name of service + Type *grpc.ServiceDesc // Type (from protoc generated code) + Service any // Implementation of GRPCService.Type (ptr) + grpcDone <-chan error } type AppHTTP struct { diff --git a/pkg/srv/grpc/grpc.go b/pkg/srv/grpc/grpc.go index 31a93cc..14949f0 100644 --- a/pkg/srv/grpc/grpc.go +++ b/pkg/srv/grpc/grpc.go @@ -16,18 +16,22 @@ import ( ) type appGRPCServer struct { - ctx context.Context - tracer trace.Tracer - meter metric.Meter - opts *opts.GRPCOpts - serverOpts []grpc.ServerOption - logger *zerolog.Logger - server *grpc.Server + ctx context.Context + tracer trace.Tracer + meter metric.Meter + opts *opts.GRPCOpts + serverOpts []grpc.ServerOption + logger *zerolog.Logger + server *grpc.Server + shutdownFunc func(context.Context) error + doneChan chan error } -// TODO: This probably needs to pass back an error chan and a shutdown -// func like the http server does -func InitGRPCServer(ctx context.Context, opts *opts.GRPCOpts) error { +// Returns a shutdown func, a channel indicating done / error, +// and an up-front error if server fails to start +func InitGRPCServer(ctx context.Context, opts *opts.GRPCOpts) ( + func(context.Context) error, <-chan error, error, +) { appGRPC := &appGRPCServer{ ctx: ctx, tracer: appotel.GetTracer(ctx, "grpc"), @@ -35,6 +39,7 @@ func InitGRPCServer(ctx context.Context, opts *opts.GRPCOpts) error { opts: opts, serverOpts: make([]grpc.ServerOption, 0), logger: zerolog.Ctx(ctx), + doneChan: make(chan error), } ctx, span := appGRPC.tracer.Start(ctx, "appgrpc.init") @@ -44,23 +49,21 @@ func InitGRPCServer(ctx context.Context, opts *opts.GRPCOpts) error { if err := appGRPC.prepGRPCServer(ctx); err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to prepare GRPC Server") - return err + return nil, nil, err } // Load span with server info span.SetAttributes(appGRPC.getServerAttributes()...) - // Run, returning a shutdown func and an error chan - // TODO: Implement shutdown func and error chan err := appGRPC.runGRPCServer(ctx) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "failed to start GRPC Server") - return err + return nil, nil, err } span.SetStatus(codes.Ok, "") - return nil + return appGRPC.shutdownFunc, appGRPC.doneChan, nil } // Convert grpc.ServiceInfo map to []attribute.KeyValue diff --git a/pkg/srv/grpc/grpc_run.go b/pkg/srv/grpc/grpc_run.go index b821702..3f4780f 100644 --- a/pkg/srv/grpc/grpc_run.go +++ b/pkg/srv/grpc/grpc_run.go @@ -2,7 +2,9 @@ package grpc import ( "context" + "net" + "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.27.0" ) @@ -17,5 +19,46 @@ func (a *appGRPCServer) runGRPCServer(spanCtx context.Context) error { semconv.ServerAddress(a.opts.Listen), ) + a.shutdownFunc = a.getShutdownFunc() + + l, err := net.Listen("tcp", a.opts.Listen) + if err != nil { + a.logger.Err(err).Send() + span.RecordError(err) + span.SetStatus(codes.Error, "failed to acquire net listener") + return err + } + + span.AddEvent("network listener acquired") + + // Launch GRPC Server + go func() { + if err := a.server.Serve(l); err != nil { + a.logger.Err(err).Send() + a.doneChan <- err + } + }() + + span.AddEvent("grpc server goroutine launched") + span.SetStatus(codes.Ok, "") + return nil } + +func (a *appGRPCServer) getShutdownFunc() func(context.Context) error { + return func(ctx context.Context) error { + stoppedChan := make(chan any) + + go func() { + a.server.GracefulStop() + stoppedChan <- nil + }() + + select { + case <-stoppedChan: + return nil + case <-ctx.Done(): + return ctx.Err() + } + } +}