Merge pull request #31 from cpuguy83/support_context_deadlines
Add support for request timeout propgation.
This commit is contained in:
		| @@ -24,6 +24,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"syscall" | 	"syscall" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/gogo/protobuf/proto" | 	"github.com/gogo/protobuf/proto" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| @@ -86,6 +87,10 @@ func (c *Client) Call(ctx context.Context, service, method string, req, resp int | |||||||
| 		cresp = &Response{} | 		cresp = &Response{} | ||||||
| 	) | 	) | ||||||
|  |  | ||||||
|  | 	if dl, ok := ctx.Deadline(); ok { | ||||||
|  | 		creq.TimeoutNano = dl.Sub(time.Now()).Nanoseconds() | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if err := c.dispatch(ctx, creq, cresp); err != nil { | 	if err := c.dispatch(ctx, creq, cresp); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -104,6 +109,7 @@ func (c *Client) Call(ctx context.Context, service, method string, req, resp int | |||||||
| func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { | func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error { | ||||||
| 	errs := make(chan error, 1) | 	errs := make(chan error, 1) | ||||||
| 	call := &callRequest{ | 	call := &callRequest{ | ||||||
|  | 		ctx:  ctx, | ||||||
| 		req:  req, | 		req:  req, | ||||||
| 		resp: resp, | 		resp: resp, | ||||||
| 		errs: errs, | 		errs: errs, | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								server.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								server.go
									
									
									
									
									
								
							| @@ -414,6 +414,9 @@ func (c *serverConn) run(sctx context.Context) { | |||||||
| 		case request := <-requests: | 		case request := <-requests: | ||||||
| 			active++ | 			active++ | ||||||
| 			go func(id uint32) { | 			go func(id uint32) { | ||||||
|  | 				ctx, cancel := getRequestContext(ctx, request.req) | ||||||
|  | 				defer cancel() | ||||||
|  |  | ||||||
| 				p, status := c.server.services.call(ctx, request.req.Service, request.req.Method, request.req.Payload) | 				p, status := c.server.services.call(ctx, request.req.Service, request.req.Method, request.req.Payload) | ||||||
| 				resp := &Response{ | 				resp := &Response{ | ||||||
| 					Status:  status.Proto(), | 					Status:  status.Proto(), | ||||||
| @@ -454,3 +457,15 @@ func (c *serverConn) run(sctx context.Context) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var noopFunc = func() {} | ||||||
|  |  | ||||||
|  | func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) { | ||||||
|  | 	cancel = noopFunc | ||||||
|  | 	if req.TimeoutNano == 0 { | ||||||
|  | 		return ctx, cancel | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano)) | ||||||
|  | 	return ctx, cancel | ||||||
|  | } | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/gogo/protobuf/proto" | 	"github.com/gogo/protobuf/proto" | ||||||
| 	"github.com/pkg/errors" | 	"github.com/pkg/errors" | ||||||
| @@ -58,6 +59,7 @@ func (tc *testingClient) Test(ctx context.Context, req *testPayload) (*testPaylo | |||||||
|  |  | ||||||
| type testPayload struct { | type testPayload struct { | ||||||
| 	Foo      string `protobuf:"bytes,1,opt,name=foo,proto3"` | 	Foo      string `protobuf:"bytes,1,opt,name=foo,proto3"` | ||||||
|  | 	Deadline int64  `protobuf:"varint,2,opt,name=deadline,proto3"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *testPayload) Reset()         { *r = testPayload{} } | func (r *testPayload) Reset()         { *r = testPayload{} } | ||||||
| @@ -68,7 +70,11 @@ func (r *testPayload) ProtoMessage()  {} | |||||||
| type testingServer struct{} | type testingServer struct{} | ||||||
|  |  | ||||||
| func (s *testingServer) Test(ctx context.Context, req *testPayload) (*testPayload, error) { | func (s *testingServer) Test(ctx context.Context, req *testPayload) (*testPayload, error) { | ||||||
| 	return &testPayload{Foo: strings.Repeat(req.Foo, 2)}, nil | 	tp := &testPayload{Foo: strings.Repeat(req.Foo, 2)} | ||||||
|  | 	if dl, ok := ctx.Deadline(); ok { | ||||||
|  | 		tp.Deadline = dl.UnixNano() | ||||||
|  | 	} | ||||||
|  | 	return tp, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // registerTestingService mocks more of what is generated code. Unlike grpc, we | // registerTestingService mocks more of what is generated code. Unlike grpc, we | ||||||
| @@ -376,6 +382,34 @@ func TestUnixSocketHandshake(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestServerRequestTimeout(t *testing.T) { | ||||||
|  | 	var ( | ||||||
|  | 		ctx, cancel     = context.WithDeadline(context.Background(), time.Now().Add(10*time.Minute)) | ||||||
|  | 		server          = mustServer(t)(NewServer()) | ||||||
|  | 		addr, listener  = newTestListener(t) | ||||||
|  | 		testImpl        = &testingServer{} | ||||||
|  | 		client, cleanup = newTestClient(t, addr) | ||||||
|  | 		result          testPayload | ||||||
|  | 	) | ||||||
|  | 	defer cancel() | ||||||
|  | 	defer cleanup() | ||||||
|  | 	defer listener.Close() | ||||||
|  |  | ||||||
|  | 	registerTestingService(server, testImpl) | ||||||
|  |  | ||||||
|  | 	go server.Serve(ctx, listener) | ||||||
|  | 	defer server.Shutdown(ctx) | ||||||
|  |  | ||||||
|  | 	if err := client.Call(ctx, serviceName, "Test", &testPayload{}, &result); err != nil { | ||||||
|  | 		t.Fatalf("unexpected error making call: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dl, _ := ctx.Deadline() | ||||||
|  | 	if result.Deadline != dl.UnixNano() { | ||||||
|  | 		t.Fatalf("expected deadline %v, actual: %v", dl, result.Deadline) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func BenchmarkRoundTrip(b *testing.B) { | func BenchmarkRoundTrip(b *testing.B) { | ||||||
| 	var ( | 	var ( | ||||||
| 		ctx             = context.Background() | 		ctx             = context.Background() | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								types.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								types.go
									
									
									
									
									
								
							| @@ -26,6 +26,7 @@ type Request struct { | |||||||
| 	Service     string `protobuf:"bytes,1,opt,name=service,proto3"` | 	Service     string `protobuf:"bytes,1,opt,name=service,proto3"` | ||||||
| 	Method      string `protobuf:"bytes,2,opt,name=method,proto3"` | 	Method      string `protobuf:"bytes,2,opt,name=method,proto3"` | ||||||
| 	Payload     []byte `protobuf:"bytes,3,opt,name=payload,proto3"` | 	Payload     []byte `protobuf:"bytes,3,opt,name=payload,proto3"` | ||||||
|  | 	TimeoutNano int64  `protobuf:"varint,4,opt,name=timeout_nano,proto3"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *Request) Reset()         { *r = Request{} } | func (r *Request) Reset()         { *r = Request{} } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stephen Day
					Stephen Day