cockroachdb / errors

Go error library with error portability over the network

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Explain "Network Portability"

tooolbox opened this issue · comments

The readme explains that this error package produces errors which have "network portability". The various theoretical use cases are detailed in the RFC and seem like a huge advantage for this library.

What are the transmission requirements?

Given the protobuf description in the RFC and the mention of gogoproto in the readme, use of protobuf is implied.

  1. Is protobuf RPC the only supported transmission method?
  2. How would these errors be used with gRPC?
  3. Is the use of gogoproto required?

Based on tests, it works to use gRPC middleware to encode & decode the errors:

package middleware

import (
	"context"

	"github.com/cockroachdb/errors"
	"github.com/gogo/status"

	"google.golang.org/grpc"
)

func UnaryClientInterceptor(
	ctx context.Context,
	method string,
	req interface{},
	reply interface{},
	cc *grpc.ClientConn,
	invoker grpc.UnaryInvoker,
	opts ...grpc.CallOption,
) error {

	err := invoker(ctx, method, req, reply, cc, opts...)

	st := status.Convert(err)
	var reconstituted error
	for _, det := range st.Details() {
		switch t := det.(type) {
		case *errors.EncodedError:
			reconstituted = errors.DecodeError(ctx, *t)
		}
	}

	if reconstituted != nil {
		err = reconstituted
	}

	return err
}

func UnaryServerInterceptor(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler,
) (interface{}, error) {

	resp, err := handler(ctx, req)

	st, ok := status.FromError(err)
	if !ok {
		enc := errors.EncodeError(ctx, err)
		st, err = st.WithDetails(&enc)
		if err != nil {
			panic(err) // Programmer error
		}
	}

	return resp, st.Err()
}

You can then recognize sentinels across gRPC boundaries, send wrapped errors and unwrap them on the other side, etc.

You do not have to be using gogoproto for your project to do this, unless you want to have custom error types. (The custom errors need to be registered with gogoproto's types package to encode & decode properly.)

The above code throws away the gRPC status. It could be kept using a custom wrapper a la exthttp, or a kind of generic wrapper Mark()ed as a "gRPC status code" sentinel, detected with a utility function calling .If()

@knz would you be interested in a PR with some of this functionality, or do you consider it to be out of scope for this package?

I think it's interesting. But can you expand a bit on the idea of "middleware"? Would this be a separate go package that gRPC users can "opt in" to to get the error transparency in their app? What would such an integration look like?

(We're particularly interested to use this in CockroachDB, as we have not completed this integration yet)

Given the above middleware, my test wireup looks like this:

package main

import (
	"net"
	"os"
	"testing"
	"time"

	"google.golang.org/grpc"

	"github.com/hydrogen18/memlistener"

	"greeter"
	"greeter/pb"
)

var (
	Client pb.GreeterClient
)

func TestMain(m *testing.M) {

	srv := &greeter.Server{}

	lis := memlistener.NewMemoryListener()

	grpcServer := grpc.NewServer(grpc.UnaryInterceptor(middleware.UnaryServerInterceptor))
	pb.RegisterGreeterServer(grpcServer, srv)

	go grpcServer.Serve(lis)

	dialOpts := []grpc.DialOption{
		grpc.WithDialer(func(target string, d time.Duration) (net.Conn, error) {
			return lis.Dial("", "")
		}),
		grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(middleware.UnaryClientInterceptor),
	}

	clientConn, e := grpc.Dial("", dialOpts...)
	if e != nil {
		panic(e)
	}

	Client = pb.NewGreeterClient(clientConn)

	code := m.Run()

	grpcServer.Stop()
	clientConn.Close()

	os.Exit(code)
}

You can see that you use the middleware as "options" for the dialer and the server. (You can also chain middleware as needed.) This creates the transparent error transmission with very little integration.

Yes, the middleware would be a separate go package. It could live in this repo under grpc/middleware, or another repo, as desired.

Wow that's very cool. Will definitely review that PR.

Alright, you have #14