vendor: google.golang.org/grpc v1.5.2

full diff: https://github.com/grpc/grpc-go/compare/v1.27.1...v1.38.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-08-12 17:11:44 +02:00
parent 2ef71e502c
commit 509cc32182
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
89 changed files with 5200 additions and 3844 deletions

View File

@ -79,7 +79,7 @@ golang.org/x/term f5c789dd3221ff39d752ac54467d
golang.org/x/text 23ae387dee1f90d29a23c0e87ee0b46038fbed0e # v0.3.3 golang.org/x/text 23ae387dee1f90d29a23c0e87ee0b46038fbed0e # v0.3.3
golang.org/x/time 3af7569d3a1e776fc2a3c1cec133b43105ea9c2e golang.org/x/time 3af7569d3a1e776fc2a3c1cec133b43105ea9c2e
google.golang.org/genproto 8816d57aaa9ad8cba31b2a8ecb6199c494bdf8b4 # v0.0.0-20201110150050-8816d57aaa9a google.golang.org/genproto 8816d57aaa9ad8cba31b2a8ecb6199c494bdf8b4 # v0.0.0-20201110150050-8816d57aaa9a
google.golang.org/grpc f495f5b15ae7ccda3b38c53a1bfcde4c1a58a2bc # v1.27.1 google.golang.org/grpc 0257c8657362b76f24e7a8cfb61df48d4cb735d3 # v1.38.0
google.golang.org/protobuf f2d1f6cbe10b90d22296ea09a7217081c2798009 # v1.26.0 google.golang.org/protobuf f2d1f6cbe10b90d22296ea09a7217081c2798009 # v1.26.0
gopkg.in/inf.v0 d2d2541c53f18d2a059457998ce2876cc8e67cbf # v0.9.1 gopkg.in/inf.v0 d2d2541c53f18d2a059457998ce2876cc8e67cbf # v0.9.1
gopkg.in/yaml.v2 53403b58ad1b561927d19068c655246f2db79d48 # v2.2.8 gopkg.in/yaml.v2 53403b58ad1b561927d19068c655246f2db79d48 # v2.2.8

View File

@ -1,64 +1,53 @@
# gRPC-Go # gRPC-Go
[![Build Status](https://travis-ci.org/grpc/grpc-go.svg)](https://travis-ci.org/grpc/grpc-go) [![Build Status](https://travis-ci.org/grpc/grpc-go.svg)](https://travis-ci.org/grpc/grpc-go)
[![GoDoc](https://godoc.org/google.golang.org/grpc?status.svg)](https://godoc.org/google.golang.org/grpc) [![GoDoc](https://pkg.go.dev/badge/google.golang.org/grpc)][API]
[![GoReportCard](https://goreportcard.com/badge/grpc/grpc-go)](https://goreportcard.com/report/github.com/grpc/grpc-go) [![GoReportCard](https://goreportcard.com/badge/grpc/grpc-go)](https://goreportcard.com/report/github.com/grpc/grpc-go)
The Go implementation of [gRPC](https://grpc.io/): A high performance, open The [Go][] implementation of [gRPC][]: A high performance, open source, general
source, general RPC framework that puts mobile and HTTP/2 first. For more RPC framework that puts mobile and HTTP/2 first. For more information see the
information see the [gRPC Quick Start: [Go gRPC docs][], or jump directly into the [quick start][].
Go](https://grpc.io/docs/quickstart/go.html) guide.
Installation ## Prerequisites
------------
To install this package, you need to install Go and setup your Go workspace on - **[Go][]**: any one of the **three latest major** [releases][go-releases].
your computer. The simplest way to install the library is to run:
## Installation
With [Go module][] support (Go 1.11+), simply add the following import
```go
import "google.golang.org/grpc"
``` ```
to your code, and then `go [build|run|test]` will automatically fetch the
necessary dependencies.
Otherwise, to install the `grpc-go` package, run the following command:
```console
$ go get -u google.golang.org/grpc $ go get -u google.golang.org/grpc
``` ```
With Go module support (Go 1.11+), simply `import "google.golang.org/grpc"` in > **Note:** If you are trying to access `grpc-go` from **China**, see the
your source code and `go [build|run|test]` will automatically download the > [FAQ](#FAQ) below.
necessary dependencies ([Go modules
ref](https://github.com/golang/go/wiki/Modules)).
If you are trying to access grpc-go from within China, please see the ## Learn more
[FAQ](#FAQ) below.
Prerequisites - [Go gRPC docs][], which include a [quick start][] and [API
------------- reference][API] among other resources
gRPC-Go requires Go 1.9 or later. - [Low-level technical docs](Documentation) from this repository
- [Performance benchmark][]
- [Examples](examples)
Documentation ## FAQ
-------------
- See [godoc](https://godoc.org/google.golang.org/grpc) for package and API
descriptions.
- Documentation on specific topics can be found in the [Documentation
directory](Documentation/).
- Examples can be found in the [examples directory](examples/).
Performance ### I/O Timeout Errors
-----------
Performance benchmark data for grpc-go and other languages is maintained in
[this
dashboard](https://performance-dot-grpc-testing.appspot.com/explore?dashboard=5652536396611584&widget=490377658&container=1286539696).
Status
------
General Availability [Google Cloud Platform Launch
Stages](https://cloud.google.com/terms/launch-stages).
FAQ
---
#### I/O Timeout Errors
The `golang.org` domain may be blocked from some countries. `go get` usually The `golang.org` domain may be blocked from some countries. `go get` usually
produces an error like the following when this happens: produces an error like the following when this happens:
``` ```console
$ go get -u google.golang.org/grpc $ go get -u google.golang.org/grpc
package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout) package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
``` ```
@ -69,7 +58,7 @@ To build Go code, there are several options:
- Without Go module support: `git clone` the repo manually: - Without Go module support: `git clone` the repo manually:
``` ```sh
git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
``` ```
@ -79,7 +68,7 @@ To build Go code, there are several options:
- With Go module support: it is possible to use the `replace` feature of `go - With Go module support: it is possible to use the `replace` feature of `go
mod` to create aliases for golang.org packages. In your project's directory: mod` to create aliases for golang.org packages. In your project's directory:
``` ```sh
go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest go mod edit -replace=google.golang.org/grpc=github.com/grpc/grpc-go@latest
go mod tidy go mod tidy
go mod vendor go mod vendor
@ -87,35 +76,66 @@ To build Go code, there are several options:
``` ```
Again, this will need to be done for all transitive dependencies hosted on Again, this will need to be done for all transitive dependencies hosted on
golang.org as well. Please refer to [this golang.org as well. For details, refer to [golang/go issue #28652](https://github.com/golang/go/issues/28652).
issue](https://github.com/golang/go/issues/28652) in the golang repo regarding
this concern.
#### Compiling error, undefined: grpc.SupportPackageIsVersion ### Compiling error, undefined: grpc.SupportPackageIsVersion
Please update proto package, gRPC package and rebuild the proto files: #### If you are using Go modules:
- `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`
- `go get -u google.golang.org/grpc`
- `protoc --go_out=plugins=grpc:. *.proto`
#### How to turn on logging Ensure your gRPC-Go version is `require`d at the appropriate version in
the same module containing the generated `.pb.go` files. For example,
`SupportPackageIsVersion6` needs `v1.27.0`, so in your `go.mod` file:
The default logger is controlled by the environment variables. Turn everything ```go
on by setting: module <your module name>
``` require (
GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info google.golang.org/grpc v1.27.0
)
``` ```
#### The RPC failed with error `"code = Unavailable desc = transport is closing"` #### If you are *not* using Go modules:
Update the `proto` package, gRPC package, and rebuild the `.proto` files:
```sh
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u google.golang.org/grpc
protoc --go_out=plugins=grpc:. *.proto
```
### How to turn on logging
The default logger is controlled by environment variables. Turn everything on
like this:
```console
$ export GRPC_GO_LOG_VERBOSITY_LEVEL=99
$ export GRPC_GO_LOG_SEVERITY_LEVEL=info
```
### The RPC failed with error `"code = Unavailable desc = transport is closing"`
This error means the connection the RPC is using was closed, and there are many This error means the connection the RPC is using was closed, and there are many
possible reasons, including: possible reasons, including:
1. mis-configured transport credentials, connection failed on handshaking 1. mis-configured transport credentials, connection failed on handshaking
1. bytes disrupted, possibly by a proxy in between 1. bytes disrupted, possibly by a proxy in between
1. server shutdown 1. server shutdown
1. Keepalive parameters caused connection shutdown, for example if you have configured
your server to terminate connections regularly to [trigger DNS lookups](https://github.com/grpc/grpc-go/issues/3170#issuecomment-552517779).
If this is the case, you may want to increase your [MaxConnectionAgeGrace](https://pkg.go.dev/google.golang.org/grpc/keepalive?tab=doc#ServerParameters),
to allow longer RPC calls to finish.
It can be tricky to debug this because the error happens on the client side but It can be tricky to debug this because the error happens on the client side but
the root cause of the connection being closed is on the server side. Turn on the root cause of the connection being closed is on the server side. Turn on
logging on __both client and server__, and see if there are any transport logging on __both client and server__, and see if there are any transport
errors. errors.
[API]: https://pkg.go.dev/google.golang.org/grpc
[Go]: https://golang.org
[Go module]: https://github.com/golang/go/wiki/Modules
[gRPC]: https://grpc.io
[Go gRPC docs]: https://grpc.io/docs/languages/go
[Performance benchmark]: https://performance-dot-grpc-testing.appspot.com/explore?dashboard=5652536396611584&widget=490377658&container=1286539696
[quick start]: https://grpc.io/docs/languages/go/quickstart
[go-releases]: https://golang.org/doc/devel/release.html

View File

@ -19,7 +19,10 @@
// Package attributes defines a generic key/value store used in various gRPC // Package attributes defines a generic key/value store used in various gRPC
// components. // components.
// //
// All APIs in this package are EXPERIMENTAL. // Experimental
//
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
// later release.
package attributes package attributes
import "fmt" import "fmt"
@ -50,6 +53,9 @@ func New(kvs ...interface{}) *Attributes {
// times, the last value overwrites all previous values for that key. To // times, the last value overwrites all previous values for that key. To
// remove an existing key, use a nil value. // remove an existing key, use a nil value.
func (a *Attributes) WithValues(kvs ...interface{}) *Attributes { func (a *Attributes) WithValues(kvs ...interface{}) *Attributes {
if a == nil {
return New(kvs...)
}
if len(kvs)%2 != 0 { if len(kvs)%2 != 0 {
panic(fmt.Sprintf("attributes.New called with unexpected input: len(kvs) = %v", len(kvs))) panic(fmt.Sprintf("attributes.New called with unexpected input: len(kvs) = %v", len(kvs)))
} }
@ -66,5 +72,8 @@ func (a *Attributes) WithValues(kvs ...interface{}) *Attributes {
// Value returns the value associated with these attributes for key, or nil if // Value returns the value associated with these attributes for key, or nil if
// no value is associated with key. // no value is associated with key.
func (a *Attributes) Value(key interface{}) interface{} { func (a *Attributes) Value(key interface{}) interface{} {
if a == nil {
return nil
}
return a.m[key] return a.m[key]
} }

View File

@ -48,7 +48,10 @@ type BackoffConfig struct {
// here for more details: // here for more details:
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type ConnectParams struct { type ConnectParams struct {
// Backoff specifies the configuration options for connection backoff. // Backoff specifies the configuration options for connection backoff.
Backoff backoff.Config Backoff backoff.Config

View File

@ -1,391 +0,0 @@
/*
*
* Copyright 2016 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpc
import (
"context"
"net"
"sync"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/naming"
"google.golang.org/grpc/status"
)
// Address represents a server the client connects to.
//
// Deprecated: please use package balancer.
type Address struct {
// Addr is the server address on which a connection will be established.
Addr string
// Metadata is the information associated with Addr, which may be used
// to make load balancing decision.
Metadata interface{}
}
// BalancerConfig specifies the configurations for Balancer.
//
// Deprecated: please use package balancer. May be removed in a future 1.x release.
type BalancerConfig struct {
// DialCreds is the transport credential the Balancer implementation can
// use to dial to a remote load balancer server. The Balancer implementations
// can ignore this if it does not need to talk to another party securely.
DialCreds credentials.TransportCredentials
// Dialer is the custom dialer the Balancer implementation can use to dial
// to a remote load balancer server. The Balancer implementations
// can ignore this if it doesn't need to talk to remote balancer.
Dialer func(context.Context, string) (net.Conn, error)
}
// BalancerGetOptions configures a Get call.
//
// Deprecated: please use package balancer. May be removed in a future 1.x release.
type BalancerGetOptions struct {
// BlockingWait specifies whether Get should block when there is no
// connected address.
BlockingWait bool
}
// Balancer chooses network addresses for RPCs.
//
// Deprecated: please use package balancer. May be removed in a future 1.x release.
type Balancer interface {
// Start does the initialization work to bootstrap a Balancer. For example,
// this function may start the name resolution and watch the updates. It will
// be called when dialing.
Start(target string, config BalancerConfig) error
// Up informs the Balancer that gRPC has a connection to the server at
// addr. It returns down which is called once the connection to addr gets
// lost or closed.
// TODO: It is not clear how to construct and take advantage of the meaningful error
// parameter for down. Need realistic demands to guide.
Up(addr Address) (down func(error))
// Get gets the address of a server for the RPC corresponding to ctx.
// i) If it returns a connected address, gRPC internals issues the RPC on the
// connection to this address;
// ii) If it returns an address on which the connection is under construction
// (initiated by Notify(...)) but not connected, gRPC internals
// * fails RPC if the RPC is fail-fast and connection is in the TransientFailure or
// Shutdown state;
// or
// * issues RPC on the connection otherwise.
// iii) If it returns an address on which the connection does not exist, gRPC
// internals treats it as an error and will fail the corresponding RPC.
//
// Therefore, the following is the recommended rule when writing a custom Balancer.
// If opts.BlockingWait is true, it should return a connected address or
// block if there is no connected address. It should respect the timeout or
// cancellation of ctx when blocking. If opts.BlockingWait is false (for fail-fast
// RPCs), it should return an address it has notified via Notify(...) immediately
// instead of blocking.
//
// The function returns put which is called once the rpc has completed or failed.
// put can collect and report RPC stats to a remote load balancer.
//
// This function should only return the errors Balancer cannot recover by itself.
// gRPC internals will fail the RPC if an error is returned.
Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error)
// Notify returns a channel that is used by gRPC internals to watch the addresses
// gRPC needs to connect. The addresses might be from a name resolver or remote
// load balancer. gRPC internals will compare it with the existing connected
// addresses. If the address Balancer notified is not in the existing connected
// addresses, gRPC starts to connect the address. If an address in the existing
// connected addresses is not in the notification list, the corresponding connection
// is shutdown gracefully. Otherwise, there are no operations to take. Note that
// the Address slice must be the full list of the Addresses which should be connected.
// It is NOT delta.
Notify() <-chan []Address
// Close shuts down the balancer.
Close() error
}
// RoundRobin returns a Balancer that selects addresses round-robin. It uses r to watch
// the name resolution updates and updates the addresses available correspondingly.
//
// Deprecated: please use package balancer/roundrobin. May be removed in a future 1.x release.
func RoundRobin(r naming.Resolver) Balancer {
return &roundRobin{r: r}
}
type addrInfo struct {
addr Address
connected bool
}
type roundRobin struct {
r naming.Resolver
w naming.Watcher
addrs []*addrInfo // all the addresses the client should potentially connect
mu sync.Mutex
addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
next int // index of the next address to return for Get()
waitCh chan struct{} // the channel to block when there is no connected address available
done bool // The Balancer is closed.
}
func (rr *roundRobin) watchAddrUpdates() error {
updates, err := rr.w.Next()
if err != nil {
grpclog.Warningf("grpc: the naming watcher stops working due to %v.", err)
return err
}
rr.mu.Lock()
defer rr.mu.Unlock()
for _, update := range updates {
addr := Address{
Addr: update.Addr,
Metadata: update.Metadata,
}
switch update.Op {
case naming.Add:
var exist bool
for _, v := range rr.addrs {
if addr == v.addr {
exist = true
grpclog.Infoln("grpc: The name resolver wanted to add an existing address: ", addr)
break
}
}
if exist {
continue
}
rr.addrs = append(rr.addrs, &addrInfo{addr: addr})
case naming.Delete:
for i, v := range rr.addrs {
if addr == v.addr {
copy(rr.addrs[i:], rr.addrs[i+1:])
rr.addrs = rr.addrs[:len(rr.addrs)-1]
break
}
}
default:
grpclog.Errorln("Unknown update.Op ", update.Op)
}
}
// Make a copy of rr.addrs and write it onto rr.addrCh so that gRPC internals gets notified.
open := make([]Address, len(rr.addrs))
for i, v := range rr.addrs {
open[i] = v.addr
}
if rr.done {
return ErrClientConnClosing
}
select {
case <-rr.addrCh:
default:
}
rr.addrCh <- open
return nil
}
func (rr *roundRobin) Start(target string, config BalancerConfig) error {
rr.mu.Lock()
defer rr.mu.Unlock()
if rr.done {
return ErrClientConnClosing
}
if rr.r == nil {
// If there is no name resolver installed, it is not needed to
// do name resolution. In this case, target is added into rr.addrs
// as the only address available and rr.addrCh stays nil.
rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
return nil
}
w, err := rr.r.Resolve(target)
if err != nil {
return err
}
rr.w = w
rr.addrCh = make(chan []Address, 1)
go func() {
for {
if err := rr.watchAddrUpdates(); err != nil {
return
}
}
}()
return nil
}
// Up sets the connected state of addr and sends notification if there are pending
// Get() calls.
func (rr *roundRobin) Up(addr Address) func(error) {
rr.mu.Lock()
defer rr.mu.Unlock()
var cnt int
for _, a := range rr.addrs {
if a.addr == addr {
if a.connected {
return nil
}
a.connected = true
}
if a.connected {
cnt++
}
}
// addr is only one which is connected. Notify the Get() callers who are blocking.
if cnt == 1 && rr.waitCh != nil {
close(rr.waitCh)
rr.waitCh = nil
}
return func(err error) {
rr.down(addr, err)
}
}
// down unsets the connected state of addr.
func (rr *roundRobin) down(addr Address, err error) {
rr.mu.Lock()
defer rr.mu.Unlock()
for _, a := range rr.addrs {
if addr == a.addr {
a.connected = false
break
}
}
}
// Get returns the next addr in the rotation.
func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) {
var ch chan struct{}
rr.mu.Lock()
if rr.done {
rr.mu.Unlock()
err = ErrClientConnClosing
return
}
if len(rr.addrs) > 0 {
if rr.next >= len(rr.addrs) {
rr.next = 0
}
next := rr.next
for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
}
}
if !opts.BlockingWait {
if len(rr.addrs) == 0 {
rr.mu.Unlock()
err = status.Errorf(codes.Unavailable, "there is no address available")
return
}
// Returns the next addr on rr.addrs for failfast RPCs.
addr = rr.addrs[rr.next].addr
rr.next++
rr.mu.Unlock()
return
}
// Wait on rr.waitCh for non-failfast RPCs.
if rr.waitCh == nil {
ch = make(chan struct{})
rr.waitCh = ch
} else {
ch = rr.waitCh
}
rr.mu.Unlock()
for {
select {
case <-ctx.Done():
err = ctx.Err()
return
case <-ch:
rr.mu.Lock()
if rr.done {
rr.mu.Unlock()
err = ErrClientConnClosing
return
}
if len(rr.addrs) > 0 {
if rr.next >= len(rr.addrs) {
rr.next = 0
}
next := rr.next
for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
}
}
// The newly added addr got removed by Down() again.
if rr.waitCh == nil {
ch = make(chan struct{})
rr.waitCh = ch
} else {
ch = rr.waitCh
}
rr.mu.Unlock()
}
}
}
func (rr *roundRobin) Notify() <-chan []Address {
return rr.addrCh
}
func (rr *roundRobin) Close() error {
rr.mu.Lock()
defer rr.mu.Unlock()
if rr.done {
return errBalancerClosed
}
rr.done = true
if rr.w != nil {
rr.w.Close()
}
if rr.waitCh != nil {
close(rr.waitCh)
rr.waitCh = nil
}
if rr.addrCh != nil {
close(rr.addrCh)
}
return nil
}
// pickFirst is used to test multi-addresses in one addrConn in which all addresses share the same addrConn.
// It is a wrapper around roundRobin balancer. The logic of all methods works fine because balancer.Get()
// returns the only address Up by resetTransport().
type pickFirst struct {
*roundRobin
}

View File

@ -101,6 +101,9 @@ type SubConn interface {
// a new connection will be created. // a new connection will be created.
// //
// This will trigger a state transition for the SubConn. // This will trigger a state transition for the SubConn.
//
// Deprecated: This method is now part of the ClientConn interface and will
// eventually be removed from here.
UpdateAddresses([]resolver.Address) UpdateAddresses([]resolver.Address)
// Connect starts the connecting for this SubConn. // Connect starts the connecting for this SubConn.
Connect() Connect()
@ -111,6 +114,9 @@ type NewSubConnOptions struct {
// CredsBundle is the credentials bundle that will be used in the created // CredsBundle is the credentials bundle that will be used in the created
// SubConn. If it's nil, the original creds from grpc DialOptions will be // SubConn. If it's nil, the original creds from grpc DialOptions will be
// used. // used.
//
// Deprecated: Use the Attributes field in resolver.Address to pass
// arbitrary data to the credential handshaker.
CredsBundle credentials.Bundle CredsBundle credentials.Bundle
// HealthCheckEnabled indicates whether health check service should be // HealthCheckEnabled indicates whether health check service should be
// enabled on this SubConn // enabled on this SubConn
@ -123,7 +129,7 @@ type State struct {
// determine the state of the ClientConn. // determine the state of the ClientConn.
ConnectivityState connectivity.State ConnectivityState connectivity.State
// Picker is used to choose connections (SubConns) for RPCs. // Picker is used to choose connections (SubConns) for RPCs.
Picker V2Picker Picker Picker
} }
// ClientConn represents a gRPC ClientConn. // ClientConn represents a gRPC ClientConn.
@ -140,21 +146,19 @@ type ClientConn interface {
// RemoveSubConn removes the SubConn from ClientConn. // RemoveSubConn removes the SubConn from ClientConn.
// The SubConn will be shutdown. // The SubConn will be shutdown.
RemoveSubConn(SubConn) RemoveSubConn(SubConn)
// UpdateAddresses updates the addresses used in the passed in SubConn.
// UpdateBalancerState is called by balancer to notify gRPC that some internal // gRPC checks if the currently connected address is still in the new list.
// state in balancer has changed. // If so, the connection will be kept. Else, the connection will be
// gracefully closed, and a new connection will be created.
// //
// gRPC will update the connectivity state of the ClientConn, and will call pick // This will trigger a state transition for the SubConn.
// on the new picker to pick new SubConn. UpdateAddresses(SubConn, []resolver.Address)
//
// Deprecated: use UpdateState instead
UpdateBalancerState(s connectivity.State, p Picker)
// UpdateState notifies gRPC that the balancer's internal state has // UpdateState notifies gRPC that the balancer's internal state has
// changed. // changed.
// //
// gRPC will update the connectivity state of the ClientConn, and will call pick // gRPC will update the connectivity state of the ClientConn, and will call
// on the new picker to pick new SubConns. // Pick on the new Picker to pick new SubConns.
UpdateState(State) UpdateState(State)
// ResolveNow is called by balancer to notify gRPC to do a name resolving. // ResolveNow is called by balancer to notify gRPC to do a name resolving.
@ -180,6 +184,10 @@ type BuildOptions struct {
Dialer func(context.Context, string) (net.Conn, error) Dialer func(context.Context, string) (net.Conn, error)
// ChannelzParentID is the entity parent's channelz unique identification number. // ChannelzParentID is the entity parent's channelz unique identification number.
ChannelzParentID int64 ChannelzParentID int64
// CustomUserAgent is the custom user agent set on the parent ClientConn.
// The balancer should set the same custom user agent if it creates a
// ClientConn.
CustomUserAgent string
// Target contains the parsed address info of the dial target. It is the same resolver.Target as // Target contains the parsed address info of the dial target. It is the same resolver.Target as
// passed to the resolver. // passed to the resolver.
// See the documentation for the resolver.Target type for details about what it contains. // See the documentation for the resolver.Target type for details about what it contains.
@ -232,56 +240,17 @@ type DoneInfo struct {
var ( var (
// ErrNoSubConnAvailable indicates no SubConn is available for pick(). // ErrNoSubConnAvailable indicates no SubConn is available for pick().
// gRPC will block the RPC until a new picker is available via UpdateBalancerState(). // gRPC will block the RPC until a new picker is available via UpdateState().
ErrNoSubConnAvailable = errors.New("no SubConn is available") ErrNoSubConnAvailable = errors.New("no SubConn is available")
// ErrTransientFailure indicates all SubConns are in TransientFailure. // ErrTransientFailure indicates all SubConns are in TransientFailure.
// WaitForReady RPCs will block, non-WaitForReady RPCs will fail. // WaitForReady RPCs will block, non-WaitForReady RPCs will fail.
ErrTransientFailure = TransientFailureError(errors.New("all SubConns are in TransientFailure")) //
// Deprecated: return an appropriate error based on the last resolution or
// connection attempt instead. The behavior is the same for any non-gRPC
// status error.
ErrTransientFailure = errors.New("all SubConns are in TransientFailure")
) )
// Picker is used by gRPC to pick a SubConn to send an RPC.
// Balancer is expected to generate a new picker from its snapshot every time its
// internal state has changed.
//
// The pickers used by gRPC can be updated by ClientConn.UpdateBalancerState().
//
// Deprecated: use V2Picker instead
type Picker interface {
// Pick returns the SubConn to be used to send the RPC.
// The returned SubConn must be one returned by NewSubConn().
//
// This functions is expected to return:
// - a SubConn that is known to be READY;
// - ErrNoSubConnAvailable if no SubConn is available, but progress is being
// made (for example, some SubConn is in CONNECTING mode);
// - other errors if no active connecting is happening (for example, all SubConn
// are in TRANSIENT_FAILURE mode).
//
// If a SubConn is returned:
// - If it is READY, gRPC will send the RPC on it;
// - If it is not ready, or becomes not ready after it's returned, gRPC will
// block until UpdateBalancerState() is called and will call pick on the
// new picker. The done function returned from Pick(), if not nil, will be
// called with nil error, no bytes sent and no bytes received.
//
// If the returned error is not nil:
// - If the error is ErrNoSubConnAvailable, gRPC will block until UpdateBalancerState()
// - If the error is ErrTransientFailure or implements IsTransientFailure()
// bool, returning true:
// - If the RPC is wait-for-ready, gRPC will block until UpdateBalancerState()
// is called to pick again;
// - Otherwise, RPC will fail with unavailable error.
// - Else (error is other non-nil error):
// - The RPC will fail with the error's status code, or Unknown if it is
// not a status error.
//
// The returned done() function will be called once the rpc has finished,
// with the final status of that RPC. If the SubConn returned is not a
// valid SubConn type, done may not be called. done may be nil if balancer
// doesn't care about the RPC status.
Pick(ctx context.Context, info PickInfo) (conn SubConn, done func(DoneInfo), err error)
}
// PickResult contains information related to a connection chosen for an RPC. // PickResult contains information related to a connection chosen for an RPC.
type PickResult struct { type PickResult struct {
// SubConn is the connection to use for this pick, if its state is Ready. // SubConn is the connection to use for this pick, if its state is Ready.
@ -297,24 +266,19 @@ type PickResult struct {
Done func(DoneInfo) Done func(DoneInfo)
} }
type transientFailureError struct { // TransientFailureError returns e. It exists for backward compatibility and
error // will be deleted soon.
} //
// Deprecated: no longer necessary, picker errors are treated this way by
// default.
func TransientFailureError(e error) error { return e }
func (e *transientFailureError) IsTransientFailure() bool { return true } // Picker is used by gRPC to pick a SubConn to send an RPC.
// TransientFailureError wraps err in an error implementing
// IsTransientFailure() bool, returning true.
func TransientFailureError(err error) error {
return &transientFailureError{error: err}
}
// V2Picker is used by gRPC to pick a SubConn to send an RPC.
// Balancer is expected to generate a new picker from its snapshot every time its // Balancer is expected to generate a new picker from its snapshot every time its
// internal state has changed. // internal state has changed.
// //
// The pickers used by gRPC can be updated by ClientConn.UpdateBalancerState(). // The pickers used by gRPC can be updated by ClientConn.UpdateState().
type V2Picker interface { type Picker interface {
// Pick returns the connection to use for this RPC and related information. // Pick returns the connection to use for this RPC and related information.
// //
// Pick should not block. If the balancer needs to do I/O or any blocking // Pick should not block. If the balancer needs to do I/O or any blocking
@ -327,14 +291,13 @@ type V2Picker interface {
// - If the error is ErrNoSubConnAvailable, gRPC will block until a new // - If the error is ErrNoSubConnAvailable, gRPC will block until a new
// Picker is provided by the balancer (using ClientConn.UpdateState). // Picker is provided by the balancer (using ClientConn.UpdateState).
// //
// - If the error implements IsTransientFailure() bool, returning true, // - If the error is a status error (implemented by the grpc/status
// wait for ready RPCs will wait, but non-wait for ready RPCs will be // package), gRPC will terminate the RPC with the code and message
// terminated with this error's Error() string and status code // provided.
// Unavailable.
// //
// - Any other errors terminate all RPCs with the code and message // - For all other errors, wait for ready RPCs will wait, but non-wait for
// provided. If the error is not a status error, it will be converted by // ready RPCs will be terminated with this error's Error() string and
// gRPC to a status error with code Unknown. // status code Unavailable.
Pick(info PickInfo) (PickResult, error) Pick(info PickInfo) (PickResult, error)
} }
@ -343,29 +306,21 @@ type V2Picker interface {
// //
// It also generates and updates the Picker used by gRPC to pick SubConns for RPCs. // It also generates and updates the Picker used by gRPC to pick SubConns for RPCs.
// //
// HandleSubConnectionStateChange, HandleResolvedAddrs and Close are guaranteed // UpdateClientConnState, ResolverError, UpdateSubConnState, and Close are
// to be called synchronously from the same goroutine. // guaranteed to be called synchronously from the same goroutine. There's no
// There's no guarantee on picker.Pick, it may be called anytime. // guarantee on picker.Pick, it may be called anytime.
type Balancer interface { type Balancer interface {
// HandleSubConnStateChange is called by gRPC when the connectivity state // UpdateClientConnState is called by gRPC when the state of the ClientConn
// of sc has changed. // changes. If the error returned is ErrBadResolverState, the ClientConn
// Balancer is expected to aggregate all the state of SubConn and report // will begin calling ResolveNow on the active name resolver with
// that back to gRPC. // exponential backoff until a subsequent call to UpdateClientConnState
// Balancer should also generate and update Pickers when its internal state has // returns a nil error. Any other errors are currently ignored.
// been changed by the new state. UpdateClientConnState(ClientConnState) error
// // ResolverError is called by gRPC when the name resolver reports an error.
// Deprecated: if V2Balancer is implemented by the Balancer, ResolverError(error)
// UpdateSubConnState will be called instead. // UpdateSubConnState is called by gRPC when the state of a SubConn
HandleSubConnStateChange(sc SubConn, state connectivity.State) // changes.
// HandleResolvedAddrs is called by gRPC to send updated resolved addresses to UpdateSubConnState(SubConn, SubConnState)
// balancers.
// Balancer can create new SubConn or remove SubConn with the addresses.
// An empty address slice and a non-nil error will be passed if the resolver returns
// non-nil error to gRPC.
//
// Deprecated: if V2Balancer is implemented by the Balancer,
// UpdateClientConnState will be called instead.
HandleResolvedAddrs([]resolver.Address, error)
// Close closes the balancer. The balancer is not required to call // Close closes the balancer. The balancer is not required to call
// ClientConn.RemoveSubConn for its existing SubConns. // ClientConn.RemoveSubConn for its existing SubConns.
Close() Close()
@ -393,27 +348,6 @@ type ClientConnState struct {
// problem with the provided name resolver data. // problem with the provided name resolver data.
var ErrBadResolverState = errors.New("bad resolver state") var ErrBadResolverState = errors.New("bad resolver state")
// V2Balancer is defined for documentation purposes. If a Balancer also
// implements V2Balancer, its UpdateClientConnState method will be called
// instead of HandleResolvedAddrs and its UpdateSubConnState will be called
// instead of HandleSubConnStateChange.
type V2Balancer interface {
// UpdateClientConnState is called by gRPC when the state of the ClientConn
// changes. If the error returned is ErrBadResolverState, the ClientConn
// will begin calling ResolveNow on the active name resolver with
// exponential backoff until a subsequent call to UpdateClientConnState
// returns a nil error. Any other errors are currently ignored.
UpdateClientConnState(ClientConnState) error
// ResolverError is called by gRPC when the name resolver reports an error.
ResolverError(error)
// UpdateSubConnState is called by gRPC when the state of a SubConn
// changes.
UpdateSubConnState(SubConn, SubConnState)
// Close closes the balancer. The balancer is not required to call
// ClientConn.RemoveSubConn for its existing SubConns.
Close()
}
// ConnectivityStateEvaluator takes the connectivity states of multiple SubConns // ConnectivityStateEvaluator takes the connectivity states of multiple SubConns
// and returns one aggregated connectivity state. // and returns one aggregated connectivity state.
// //

View File

@ -19,20 +19,21 @@
package base package base
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"google.golang.org/grpc/attributes"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
) )
var logger = grpclog.Component("balancer")
type baseBuilder struct { type baseBuilder struct {
name string name string
pickerBuilder PickerBuilder pickerBuilder PickerBuilder
v2PickerBuilder V2PickerBuilder
config Config config Config
} }
@ -40,9 +41,8 @@ func (bb *baseBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions)
bal := &baseBalancer{ bal := &baseBalancer{
cc: cc, cc: cc,
pickerBuilder: bb.pickerBuilder, pickerBuilder: bb.pickerBuilder,
v2PickerBuilder: bb.v2PickerBuilder,
subConns: make(map[resolver.Address]balancer.SubConn), subConns: make(map[resolver.Address]subConnInfo),
scStates: make(map[balancer.SubConn]connectivity.State), scStates: make(map[balancer.SubConn]connectivity.State),
csEvltr: &balancer.ConnectivityStateEvaluator{}, csEvltr: &balancer.ConnectivityStateEvaluator{},
config: bb.config, config: bb.config,
@ -50,11 +50,7 @@ func (bb *baseBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions)
// Initialize picker to a picker that always returns // Initialize picker to a picker that always returns
// ErrNoSubConnAvailable, because when state of a SubConn changes, we // ErrNoSubConnAvailable, because when state of a SubConn changes, we
// may call UpdateState with this picker. // may call UpdateState with this picker.
if bb.pickerBuilder != nil {
bal.picker = NewErrPicker(balancer.ErrNoSubConnAvailable) bal.picker = NewErrPicker(balancer.ErrNoSubConnAvailable)
} else {
bal.v2Picker = NewErrPickerV2(balancer.ErrNoSubConnAvailable)
}
return bal return bal
} }
@ -62,88 +58,111 @@ func (bb *baseBuilder) Name() string {
return bb.name return bb.name
} }
var _ balancer.V2Balancer = (*baseBalancer)(nil) // Assert that we implement V2Balancer type subConnInfo struct {
subConn balancer.SubConn
attrs *attributes.Attributes
}
type baseBalancer struct { type baseBalancer struct {
cc balancer.ClientConn cc balancer.ClientConn
pickerBuilder PickerBuilder pickerBuilder PickerBuilder
v2PickerBuilder V2PickerBuilder
csEvltr *balancer.ConnectivityStateEvaluator csEvltr *balancer.ConnectivityStateEvaluator
state connectivity.State state connectivity.State
subConns map[resolver.Address]balancer.SubConn subConns map[resolver.Address]subConnInfo // `attributes` is stripped from the keys of this map (the addresses)
scStates map[balancer.SubConn]connectivity.State scStates map[balancer.SubConn]connectivity.State
picker balancer.Picker picker balancer.Picker
v2Picker balancer.V2Picker
config Config config Config
resolverErr error // the last error reported by the resolver; cleared on successful resolution resolverErr error // the last error reported by the resolver; cleared on successful resolution
connErr error // the last connection error; cleared upon leaving TransientFailure connErr error // the last connection error; cleared upon leaving TransientFailure
} }
func (b *baseBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) {
panic("not implemented")
}
func (b *baseBalancer) ResolverError(err error) { func (b *baseBalancer) ResolverError(err error) {
b.resolverErr = err b.resolverErr = err
if len(b.subConns) == 0 { if len(b.subConns) == 0 {
b.state = connectivity.TransientFailure b.state = connectivity.TransientFailure
} }
if b.state != connectivity.TransientFailure { if b.state != connectivity.TransientFailure {
// The picker will not change since the balancer does not currently // The picker will not change since the balancer does not currently
// report an error. // report an error.
return return
} }
b.regeneratePicker() b.regeneratePicker()
if b.picker != nil {
b.cc.UpdateBalancerState(b.state, b.picker)
} else {
b.cc.UpdateState(balancer.State{ b.cc.UpdateState(balancer.State{
ConnectivityState: b.state, ConnectivityState: b.state,
Picker: b.v2Picker, Picker: b.picker,
}) })
}
} }
func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error { func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
// TODO: handle s.ResolverState.Err (log if not nil) once implemented.
// TODO: handle s.ResolverState.ServiceConfig? // TODO: handle s.ResolverState.ServiceConfig?
if grpclog.V(2) { if logger.V(2) {
grpclog.Infoln("base.baseBalancer: got new ClientConn state: ", s) logger.Info("base.baseBalancer: got new ClientConn state: ", s)
}
if len(s.ResolverState.Addresses) == 0 {
b.ResolverError(errors.New("produced zero addresses"))
return balancer.ErrBadResolverState
} }
// Successful resolution; clear resolver error and ensure we return nil. // Successful resolution; clear resolver error and ensure we return nil.
b.resolverErr = nil b.resolverErr = nil
// addrsSet is the set converted from addrs, it's used for quick lookup of an address. // addrsSet is the set converted from addrs, it's used for quick lookup of an address.
addrsSet := make(map[resolver.Address]struct{}) addrsSet := make(map[resolver.Address]struct{})
for _, a := range s.ResolverState.Addresses { for _, a := range s.ResolverState.Addresses {
addrsSet[a] = struct{}{} // Strip attributes from addresses before using them as map keys. So
if _, ok := b.subConns[a]; !ok { // that when two addresses only differ in attributes pointers (but with
// the same attribute content), they are considered the same address.
//
// Note that this doesn't handle the case where the attribute content is
// different. So if users want to set different attributes to create
// duplicate connections to the same backend, it doesn't work. This is
// fine for now, because duplicate is done by setting Metadata today.
//
// TODO: read attributes to handle duplicate connections.
aNoAttrs := a
aNoAttrs.Attributes = nil
addrsSet[aNoAttrs] = struct{}{}
if scInfo, ok := b.subConns[aNoAttrs]; !ok {
// a is a new address (not existing in b.subConns). // a is a new address (not existing in b.subConns).
//
// When creating SubConn, the original address with attributes is
// passed through. So that connection configurations in attributes
// (like creds) will be used.
sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: b.config.HealthCheck}) sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: b.config.HealthCheck})
if err != nil { if err != nil {
grpclog.Warningf("base.baseBalancer: failed to create new SubConn: %v", err) logger.Warningf("base.baseBalancer: failed to create new SubConn: %v", err)
continue continue
} }
b.subConns[a] = sc b.subConns[aNoAttrs] = subConnInfo{subConn: sc, attrs: a.Attributes}
b.scStates[sc] = connectivity.Idle b.scStates[sc] = connectivity.Idle
sc.Connect() sc.Connect()
} else {
// Always update the subconn's address in case the attributes
// changed.
//
// The SubConn does a reflect.DeepEqual of the new and old
// addresses. So this is a noop if the current address is the same
// as the old one (including attributes).
scInfo.attrs = a.Attributes
b.subConns[aNoAttrs] = scInfo
b.cc.UpdateAddresses(scInfo.subConn, []resolver.Address{a})
} }
} }
for a, sc := range b.subConns { for a, scInfo := range b.subConns {
// a was removed by resolver. // a was removed by resolver.
if _, ok := addrsSet[a]; !ok { if _, ok := addrsSet[a]; !ok {
b.cc.RemoveSubConn(sc) b.cc.RemoveSubConn(scInfo.subConn)
delete(b.subConns, a) delete(b.subConns, a)
// Keep the state of this sc in b.scStates until sc's state becomes Shutdown. // Keep the state of this sc in b.scStates until sc's state becomes Shutdown.
// The entry will be deleted in HandleSubConnStateChange. // The entry will be deleted in UpdateSubConnState.
} }
} }
// If resolver state contains no addresses, return an error so ClientConn
// will trigger re-resolve. Also records this as an resolver error, so when
// the overall state turns transient failure, the error message will have
// the zero address information.
if len(s.ResolverState.Addresses) == 0 {
b.ResolverError(errors.New("produced zero addresses"))
return balancer.ErrBadResolverState
}
return nil return nil
} }
@ -167,52 +186,39 @@ func (b *baseBalancer) mergeErrors() error {
// - built by the pickerBuilder with all READY SubConns otherwise. // - built by the pickerBuilder with all READY SubConns otherwise.
func (b *baseBalancer) regeneratePicker() { func (b *baseBalancer) regeneratePicker() {
if b.state == connectivity.TransientFailure { if b.state == connectivity.TransientFailure {
if b.pickerBuilder != nil { b.picker = NewErrPicker(b.mergeErrors())
b.picker = NewErrPicker(balancer.ErrTransientFailure)
} else {
b.v2Picker = NewErrPickerV2(balancer.TransientFailureError(b.mergeErrors()))
}
return return
} }
if b.pickerBuilder != nil {
readySCs := make(map[resolver.Address]balancer.SubConn)
// Filter out all ready SCs from full subConn map.
for addr, sc := range b.subConns {
if st, ok := b.scStates[sc]; ok && st == connectivity.Ready {
readySCs[addr] = sc
}
}
b.picker = b.pickerBuilder.Build(readySCs)
} else {
readySCs := make(map[balancer.SubConn]SubConnInfo) readySCs := make(map[balancer.SubConn]SubConnInfo)
// Filter out all ready SCs from full subConn map. // Filter out all ready SCs from full subConn map.
for addr, sc := range b.subConns { for addr, scInfo := range b.subConns {
if st, ok := b.scStates[sc]; ok && st == connectivity.Ready { if st, ok := b.scStates[scInfo.subConn]; ok && st == connectivity.Ready {
readySCs[sc] = SubConnInfo{Address: addr} addr.Attributes = scInfo.attrs
readySCs[scInfo.subConn] = SubConnInfo{Address: addr}
} }
} }
b.v2Picker = b.v2PickerBuilder.Build(PickerBuildInfo{ReadySCs: readySCs}) b.picker = b.pickerBuilder.Build(PickerBuildInfo{ReadySCs: readySCs})
}
}
func (b *baseBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
panic("not implemented")
} }
func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) { func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
s := state.ConnectivityState s := state.ConnectivityState
if grpclog.V(2) { if logger.V(2) {
grpclog.Infof("base.baseBalancer: handle SubConn state change: %p, %v", sc, s) logger.Infof("base.baseBalancer: handle SubConn state change: %p, %v", sc, s)
} }
oldS, ok := b.scStates[sc] oldS, ok := b.scStates[sc]
if !ok { if !ok {
if grpclog.V(2) { if logger.V(2) {
grpclog.Infof("base.baseBalancer: got state changes for an unknown SubConn: %p, %v", sc, s) logger.Infof("base.baseBalancer: got state changes for an unknown SubConn: %p, %v", sc, s)
} }
return return
} }
if oldS == connectivity.TransientFailure && s == connectivity.Connecting {
// Once a subconn enters TRANSIENT_FAILURE, ignore subsequent
// CONNECTING transitions to prevent the aggregated state from being
// always CONNECTING when many backends exist but are all down.
return
}
b.scStates[sc] = s b.scStates[sc] = s
switch s { switch s {
case connectivity.Idle: case connectivity.Idle:
@ -221,29 +227,23 @@ func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.Su
// When an address was removed by resolver, b called RemoveSubConn but // When an address was removed by resolver, b called RemoveSubConn but
// kept the sc's state in scStates. Remove state for this sc here. // kept the sc's state in scStates. Remove state for this sc here.
delete(b.scStates, sc) delete(b.scStates, sc)
case connectivity.TransientFailure:
// Save error to be reported via picker.
b.connErr = state.ConnectionError
} }
oldAggrState := b.state
b.state = b.csEvltr.RecordTransition(oldS, s) b.state = b.csEvltr.RecordTransition(oldS, s)
// Set or clear the last connection error accordingly.
b.connErr = state.ConnectionError
// Regenerate picker when one of the following happens: // Regenerate picker when one of the following happens:
// - this sc became ready from not-ready // - this sc entered or left ready
// - this sc became not-ready from ready // - the aggregated state of balancer is TransientFailure
// - the aggregated state of balancer became TransientFailure from non-TransientFailure // (may need to update error message)
// - the aggregated state of balancer became non-TransientFailure from TransientFailure
if (s == connectivity.Ready) != (oldS == connectivity.Ready) || if (s == connectivity.Ready) != (oldS == connectivity.Ready) ||
(b.state == connectivity.TransientFailure) != (oldAggrState == connectivity.TransientFailure) { b.state == connectivity.TransientFailure {
b.regeneratePicker() b.regeneratePicker()
} }
if b.picker != nil { b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})
b.cc.UpdateBalancerState(b.state, b.picker)
} else {
b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.v2Picker})
}
} }
// Close is a nop because base balancer doesn't have internal state to clean up, // Close is a nop because base balancer doesn't have internal state to clean up,
@ -251,28 +251,20 @@ func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.Su
func (b *baseBalancer) Close() { func (b *baseBalancer) Close() {
} }
// NewErrPicker returns a picker that always returns err on Pick(). // NewErrPicker returns a Picker that always returns err on Pick().
func NewErrPicker(err error) balancer.Picker { func NewErrPicker(err error) balancer.Picker {
return &errPicker{err: err} return &errPicker{err: err}
} }
// NewErrPickerV2 is temporarily defined for backward compatibility reasons.
//
// Deprecated: use NewErrPicker instead.
var NewErrPickerV2 = NewErrPicker
type errPicker struct { type errPicker struct {
err error // Pick() always returns this err. err error // Pick() always returns this err.
} }
func (p *errPicker) Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) { func (p *errPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
return nil, nil, p.err
}
// NewErrPickerV2 returns a V2Picker that always returns err on Pick().
func NewErrPickerV2(err error) balancer.V2Picker {
return &errPickerV2{err: err}
}
type errPickerV2 struct {
err error // Pick() always returns this err.
}
func (p *errPickerV2) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
return balancer.PickResult{}, p.err return balancer.PickResult{}, p.err
} }

View File

@ -37,15 +37,8 @@ import (
// PickerBuilder creates balancer.Picker. // PickerBuilder creates balancer.Picker.
type PickerBuilder interface { type PickerBuilder interface {
// Build takes a slice of ready SubConns, and returns a picker that will be
// used by gRPC to pick a SubConn.
Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker
}
// V2PickerBuilder creates balancer.V2Picker.
type V2PickerBuilder interface {
// Build returns a picker that will be used by gRPC to pick a SubConn. // Build returns a picker that will be used by gRPC to pick a SubConn.
Build(info PickerBuildInfo) balancer.V2Picker Build(info PickerBuildInfo) balancer.Picker
} }
// PickerBuildInfo contains information needed by the picker builder to // PickerBuildInfo contains information needed by the picker builder to
@ -62,32 +55,17 @@ type SubConnInfo struct {
Address resolver.Address // the address used to create this SubConn Address resolver.Address // the address used to create this SubConn
} }
// NewBalancerBuilder returns a balancer builder. The balancers
// built by this builder will use the picker builder to build pickers.
func NewBalancerBuilder(name string, pb PickerBuilder) balancer.Builder {
return NewBalancerBuilderWithConfig(name, pb, Config{})
}
// Config contains the config info about the base balancer builder. // Config contains the config info about the base balancer builder.
type Config struct { type Config struct {
// HealthCheck indicates whether health checking should be enabled for this specific balancer. // HealthCheck indicates whether health checking should be enabled for this specific balancer.
HealthCheck bool HealthCheck bool
} }
// NewBalancerBuilderWithConfig returns a base balancer builder configured by the provided config. // NewBalancerBuilder returns a base balancer builder configured by the provided config.
func NewBalancerBuilderWithConfig(name string, pb PickerBuilder, config Config) balancer.Builder { func NewBalancerBuilder(name string, pb PickerBuilder, config Config) balancer.Builder {
return &baseBuilder{ return &baseBuilder{
name: name, name: name,
pickerBuilder: pb, pickerBuilder: pb,
config: config, config: config,
} }
} }
// NewBalancerBuilderV2 returns a base balancer builder configured by the provided config.
func NewBalancerBuilderV2(name string, pb V2PickerBuilder, config Config) balancer.Builder {
return &baseBuilder{
name: name,
v2PickerBuilder: pb,
config: config,
}
}

View File

@ -0,0 +1,51 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package state declares grpclb types to be set by resolvers wishing to pass
// information to grpclb via resolver.State Attributes.
package state
import (
"google.golang.org/grpc/resolver"
)
// keyType is the key to use for storing State in Attributes.
type keyType string
const key = keyType("grpc.grpclb.state")
// State contains gRPCLB-relevant data passed from the name resolver.
type State struct {
// BalancerAddresses contains the remote load balancer address(es). If
// set, overrides any resolver-provided addresses with Type of GRPCLB.
BalancerAddresses []resolver.Address
}
// Set returns a copy of the provided state with attributes containing s. s's
// data should not be mutated after calling Set.
func Set(state resolver.State, s *State) resolver.State {
state.Attributes = state.Attributes.WithValues(key, s)
return state
}
// Get returns the grpclb State in the resolver.State, or nil if not present.
// The returned data should not be mutated.
func Get(state resolver.State) *State {
s, _ := state.Attributes.Value(key).(*State)
return s
}

View File

@ -33,9 +33,11 @@ import (
// Name is the name of round_robin balancer. // Name is the name of round_robin balancer.
const Name = "round_robin" const Name = "round_robin"
var logger = grpclog.Component("roundrobin")
// newBuilder creates a new roundrobin balancer builder. // newBuilder creates a new roundrobin balancer builder.
func newBuilder() balancer.Builder { func newBuilder() balancer.Builder {
return base.NewBalancerBuilderV2(Name, &rrPickerBuilder{}, base.Config{HealthCheck: true}) return base.NewBalancerBuilder(Name, &rrPickerBuilder{}, base.Config{HealthCheck: true})
} }
func init() { func init() {
@ -44,10 +46,10 @@ func init() {
type rrPickerBuilder struct{} type rrPickerBuilder struct{}
func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.V2Picker { func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
grpclog.Infof("roundrobinPicker: newPicker called with info: %v", info) logger.Infof("roundrobinPicker: newPicker called with info: %v", info)
if len(info.ReadySCs) == 0 { if len(info.ReadySCs) == 0 {
return base.NewErrPickerV2(balancer.ErrNoSubConnAvailable) return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
} }
var scs []balancer.SubConn var scs []balancer.SubConn
for sc := range info.ReadySCs { for sc := range info.ReadySCs {

View File

@ -24,8 +24,8 @@ import (
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/buffer" "google.golang.org/grpc/internal/buffer"
"google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
) )
@ -74,11 +74,7 @@ func (ccb *ccBalancerWrapper) watcher() {
} }
ccb.balancerMu.Lock() ccb.balancerMu.Lock()
su := t.(*scStateUpdate) su := t.(*scStateUpdate)
if ub, ok := ccb.balancer.(balancer.V2Balancer); ok { ccb.balancer.UpdateSubConnState(su.sc, balancer.SubConnState{ConnectivityState: su.state, ConnectionError: su.err})
ub.UpdateSubConnState(su.sc, balancer.SubConnState{ConnectivityState: su.state, ConnectionError: su.err})
} else {
ccb.balancer.HandleSubConnStateChange(su.sc, su.state)
}
ccb.balancerMu.Unlock() ccb.balancerMu.Unlock()
case <-ccb.done.Done(): case <-ccb.done.Done():
} }
@ -123,19 +119,13 @@ func (ccb *ccBalancerWrapper) handleSubConnStateChange(sc balancer.SubConn, s co
func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error { func (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error {
ccb.balancerMu.Lock() ccb.balancerMu.Lock()
defer ccb.balancerMu.Unlock() defer ccb.balancerMu.Unlock()
if ub, ok := ccb.balancer.(balancer.V2Balancer); ok { return ccb.balancer.UpdateClientConnState(*ccs)
return ub.UpdateClientConnState(*ccs)
}
ccb.balancer.HandleResolvedAddrs(ccs.ResolverState.Addresses, nil)
return nil
} }
func (ccb *ccBalancerWrapper) resolverError(err error) { func (ccb *ccBalancerWrapper) resolverError(err error) {
if ub, ok := ccb.balancer.(balancer.V2Balancer); ok {
ccb.balancerMu.Lock() ccb.balancerMu.Lock()
ub.ResolverError(err) ccb.balancer.ResolverError(err)
ccb.balancerMu.Unlock() ccb.balancerMu.Unlock()
}
} }
func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
@ -173,19 +163,12 @@ func (ccb *ccBalancerWrapper) RemoveSubConn(sc balancer.SubConn) {
ccb.cc.removeAddrConn(acbw.getAddrConn(), errConnDrain) ccb.cc.removeAddrConn(acbw.getAddrConn(), errConnDrain)
} }
func (ccb *ccBalancerWrapper) UpdateBalancerState(s connectivity.State, p balancer.Picker) { func (ccb *ccBalancerWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Address) {
ccb.mu.Lock() acbw, ok := sc.(*acBalancerWrapper)
defer ccb.mu.Unlock() if !ok {
if ccb.subConns == nil {
return return
} }
// Update picker before updating state. Even though the ordering here does acbw.UpdateAddresses(addrs)
// not matter, it can lead to multiple calls of Pick in the common start-up
// case where we wait for ready and then perform an RPC. If the picker is
// updated later, we could call the "connecting" picker when the state is
// updated, and then call the "ready" picker after the picker gets updated.
ccb.cc.blockingpicker.updatePicker(p)
ccb.cc.csMgr.updateState(s)
} }
func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) { func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) {
@ -199,7 +182,7 @@ func (ccb *ccBalancerWrapper) UpdateState(s balancer.State) {
// case where we wait for ready and then perform an RPC. If the picker is // case where we wait for ready and then perform an RPC. If the picker is
// updated later, we could call the "connecting" picker when the state is // updated later, we could call the "connecting" picker when the state is
// updated, and then call the "ready" picker after the picker gets updated. // updated, and then call the "ready" picker after the picker gets updated.
ccb.cc.blockingpicker.updatePickerV2(s.Picker) ccb.cc.blockingpicker.updatePicker(s.Picker)
ccb.cc.csMgr.updateState(s.ConnectivityState) ccb.cc.csMgr.updateState(s.ConnectivityState)
} }
@ -222,7 +205,7 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) {
acbw.mu.Lock() acbw.mu.Lock()
defer acbw.mu.Unlock() defer acbw.mu.Unlock()
if len(addrs) <= 0 { if len(addrs) <= 0 {
acbw.ac.tearDown(errConnDrain) acbw.ac.cc.removeAddrConn(acbw.ac, errConnDrain)
return return
} }
if !acbw.ac.tryUpdateAddrs(addrs) { if !acbw.ac.tryUpdateAddrs(addrs) {
@ -237,7 +220,7 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) {
acbw.ac.acbw = nil acbw.ac.acbw = nil
acbw.ac.mu.Unlock() acbw.ac.mu.Unlock()
acState := acbw.ac.getState() acState := acbw.ac.getState()
acbw.ac.tearDown(errConnDrain) acbw.ac.cc.removeAddrConn(acbw.ac, errConnDrain)
if acState == connectivity.Shutdown { if acState == connectivity.Shutdown {
return return
@ -245,7 +228,7 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) {
ac, err := cc.newAddrConn(addrs, opts) ac, err := cc.newAddrConn(addrs, opts)
if err != nil { if err != nil {
grpclog.Warningf("acBalancerWrapper: UpdateAddresses: failed to newAddrConn: %v", err) channelz.Warningf(logger, acbw.ac.channelzID, "acBalancerWrapper: UpdateAddresses: failed to newAddrConn: %v", err)
return return
} }
acbw.ac = ac acbw.ac = ac

View File

@ -1,334 +0,0 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpc
import (
"sync"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/resolver"
)
type balancerWrapperBuilder struct {
b Balancer // The v1 balancer.
}
func (bwb *balancerWrapperBuilder) Build(cc balancer.ClientConn, opts balancer.BuildOptions) balancer.Balancer {
bwb.b.Start(opts.Target.Endpoint, BalancerConfig{
DialCreds: opts.DialCreds,
Dialer: opts.Dialer,
})
_, pickfirst := bwb.b.(*pickFirst)
bw := &balancerWrapper{
balancer: bwb.b,
pickfirst: pickfirst,
cc: cc,
targetAddr: opts.Target.Endpoint,
startCh: make(chan struct{}),
conns: make(map[resolver.Address]balancer.SubConn),
connSt: make(map[balancer.SubConn]*scState),
csEvltr: &balancer.ConnectivityStateEvaluator{},
state: connectivity.Idle,
}
cc.UpdateState(balancer.State{ConnectivityState: connectivity.Idle, Picker: bw})
go bw.lbWatcher()
return bw
}
func (bwb *balancerWrapperBuilder) Name() string {
return "wrapper"
}
type scState struct {
addr Address // The v1 address type.
s connectivity.State
down func(error)
}
type balancerWrapper struct {
balancer Balancer // The v1 balancer.
pickfirst bool
cc balancer.ClientConn
targetAddr string // Target without the scheme.
mu sync.Mutex
conns map[resolver.Address]balancer.SubConn
connSt map[balancer.SubConn]*scState
// This channel is closed when handling the first resolver result.
// lbWatcher blocks until this is closed, to avoid race between
// - NewSubConn is created, cc wants to notify balancer of state changes;
// - Build hasn't return, cc doesn't have access to balancer.
startCh chan struct{}
// To aggregate the connectivity state.
csEvltr *balancer.ConnectivityStateEvaluator
state connectivity.State
}
// lbWatcher watches the Notify channel of the balancer and manages
// connections accordingly.
func (bw *balancerWrapper) lbWatcher() {
<-bw.startCh
notifyCh := bw.balancer.Notify()
if notifyCh == nil {
// There's no resolver in the balancer. Connect directly.
a := resolver.Address{
Addr: bw.targetAddr,
Type: resolver.Backend,
}
sc, err := bw.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{})
if err != nil {
grpclog.Warningf("Error creating connection to %v. Err: %v", a, err)
} else {
bw.mu.Lock()
bw.conns[a] = sc
bw.connSt[sc] = &scState{
addr: Address{Addr: bw.targetAddr},
s: connectivity.Idle,
}
bw.mu.Unlock()
sc.Connect()
}
return
}
for addrs := range notifyCh {
grpclog.Infof("balancerWrapper: got update addr from Notify: %v", addrs)
if bw.pickfirst {
var (
oldA resolver.Address
oldSC balancer.SubConn
)
bw.mu.Lock()
for oldA, oldSC = range bw.conns {
break
}
bw.mu.Unlock()
if len(addrs) <= 0 {
if oldSC != nil {
// Teardown old sc.
bw.mu.Lock()
delete(bw.conns, oldA)
delete(bw.connSt, oldSC)
bw.mu.Unlock()
bw.cc.RemoveSubConn(oldSC)
}
continue
}
var newAddrs []resolver.Address
for _, a := range addrs {
newAddr := resolver.Address{
Addr: a.Addr,
Type: resolver.Backend, // All addresses from balancer are all backends.
ServerName: "",
Metadata: a.Metadata,
}
newAddrs = append(newAddrs, newAddr)
}
if oldSC == nil {
// Create new sc.
sc, err := bw.cc.NewSubConn(newAddrs, balancer.NewSubConnOptions{})
if err != nil {
grpclog.Warningf("Error creating connection to %v. Err: %v", newAddrs, err)
} else {
bw.mu.Lock()
// For pickfirst, there should be only one SubConn, so the
// address doesn't matter. All states updating (up and down)
// and picking should all happen on that only SubConn.
bw.conns[resolver.Address{}] = sc
bw.connSt[sc] = &scState{
addr: addrs[0], // Use the first address.
s: connectivity.Idle,
}
bw.mu.Unlock()
sc.Connect()
}
} else {
bw.mu.Lock()
bw.connSt[oldSC].addr = addrs[0]
bw.mu.Unlock()
oldSC.UpdateAddresses(newAddrs)
}
} else {
var (
add []resolver.Address // Addresses need to setup connections.
del []balancer.SubConn // Connections need to tear down.
)
resAddrs := make(map[resolver.Address]Address)
for _, a := range addrs {
resAddrs[resolver.Address{
Addr: a.Addr,
Type: resolver.Backend, // All addresses from balancer are all backends.
ServerName: "",
Metadata: a.Metadata,
}] = a
}
bw.mu.Lock()
for a := range resAddrs {
if _, ok := bw.conns[a]; !ok {
add = append(add, a)
}
}
for a, c := range bw.conns {
if _, ok := resAddrs[a]; !ok {
del = append(del, c)
delete(bw.conns, a)
// Keep the state of this sc in bw.connSt until its state becomes Shutdown.
}
}
bw.mu.Unlock()
for _, a := range add {
sc, err := bw.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{})
if err != nil {
grpclog.Warningf("Error creating connection to %v. Err: %v", a, err)
} else {
bw.mu.Lock()
bw.conns[a] = sc
bw.connSt[sc] = &scState{
addr: resAddrs[a],
s: connectivity.Idle,
}
bw.mu.Unlock()
sc.Connect()
}
}
for _, c := range del {
bw.cc.RemoveSubConn(c)
}
}
}
}
func (bw *balancerWrapper) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
bw.mu.Lock()
defer bw.mu.Unlock()
scSt, ok := bw.connSt[sc]
if !ok {
return
}
if s == connectivity.Idle {
sc.Connect()
}
oldS := scSt.s
scSt.s = s
if oldS != connectivity.Ready && s == connectivity.Ready {
scSt.down = bw.balancer.Up(scSt.addr)
} else if oldS == connectivity.Ready && s != connectivity.Ready {
if scSt.down != nil {
scSt.down(errConnClosing)
}
}
sa := bw.csEvltr.RecordTransition(oldS, s)
if bw.state != sa {
bw.state = sa
}
bw.cc.UpdateState(balancer.State{ConnectivityState: bw.state, Picker: bw})
if s == connectivity.Shutdown {
// Remove state for this sc.
delete(bw.connSt, sc)
}
}
func (bw *balancerWrapper) HandleResolvedAddrs([]resolver.Address, error) {
bw.mu.Lock()
defer bw.mu.Unlock()
select {
case <-bw.startCh:
default:
close(bw.startCh)
}
// There should be a resolver inside the balancer.
// All updates here, if any, are ignored.
}
func (bw *balancerWrapper) Close() {
bw.mu.Lock()
defer bw.mu.Unlock()
select {
case <-bw.startCh:
default:
close(bw.startCh)
}
bw.balancer.Close()
}
// The picker is the balancerWrapper itself.
// It either blocks or returns error, consistent with v1 balancer Get().
func (bw *balancerWrapper) Pick(info balancer.PickInfo) (result balancer.PickResult, err error) {
failfast := true // Default failfast is true.
if ss, ok := rpcInfoFromContext(info.Ctx); ok {
failfast = ss.failfast
}
a, p, err := bw.balancer.Get(info.Ctx, BalancerGetOptions{BlockingWait: !failfast})
if err != nil {
return balancer.PickResult{}, toRPCErr(err)
}
if p != nil {
result.Done = func(balancer.DoneInfo) { p() }
defer func() {
if err != nil {
p()
}
}()
}
bw.mu.Lock()
defer bw.mu.Unlock()
if bw.pickfirst {
// Get the first sc in conns.
for _, result.SubConn = range bw.conns {
return result, nil
}
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
}
var ok1 bool
result.SubConn, ok1 = bw.conns[resolver.Address{
Addr: a.Addr,
Type: resolver.Backend,
ServerName: "",
Metadata: a.Metadata,
}]
s, ok2 := bw.connSt[result.SubConn]
if !ok1 || !ok2 {
// This can only happen due to a race where Get() returned an address
// that was subsequently removed by Notify. In this case we should
// retry always.
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
}
switch s.s {
case connectivity.Ready, connectivity.Idle:
return result, nil
case connectivity.Shutdown, connectivity.TransientFailure:
// If the returned sc has been shut down or is in transient failure,
// return error, and this RPC will fail or wait for another picker (if
// non-failfast).
return balancer.PickResult{}, balancer.ErrTransientFailure
default:
// For other states (connecting or unknown), the v1 balancer would
// traditionally wait until ready and then issue the RPC. Returning
// ErrNoSubConnAvailable will be a slight improvement in that it will
// allow the balancer to choose another address in case others are
// connected.
return balancer.PickResult{}, balancer.ErrNoSubConnAvailable
}
}

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@ -35,10 +34,11 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/backoff" "google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/grpcutil"
iresolver "google.golang.org/grpc/internal/resolver"
"google.golang.org/grpc/internal/transport" "google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
@ -48,6 +48,7 @@ import (
_ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin. _ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin.
_ "google.golang.org/grpc/internal/resolver/dns" // To register dns resolver. _ "google.golang.org/grpc/internal/resolver/dns" // To register dns resolver.
_ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver. _ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver.
_ "google.golang.org/grpc/internal/resolver/unix" // To register unix resolver.
) )
const ( const (
@ -68,8 +69,6 @@ var (
errConnDrain = errors.New("grpc: the connection is drained") errConnDrain = errors.New("grpc: the connection is drained")
// errConnClosing indicates that the connection is closing. // errConnClosing indicates that the connection is closing.
errConnClosing = errors.New("grpc: the connection is closing") errConnClosing = errors.New("grpc: the connection is closing")
// errBalancerClosed indicates that the balancer is closed.
errBalancerClosed = errors.New("grpc: balancer is closed")
// invalidDefaultServiceConfigErrPrefix is used to prefix the json parsing error for the default // invalidDefaultServiceConfigErrPrefix is used to prefix the json parsing error for the default
// service config. // service config.
invalidDefaultServiceConfigErrPrefix = "grpc: the provided default service config is invalid" invalidDefaultServiceConfigErrPrefix = "grpc: the provided default service config is invalid"
@ -106,6 +105,17 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) {
return DialContext(context.Background(), target, opts...) return DialContext(context.Background(), target, opts...)
} }
type defaultConfigSelector struct {
sc *ServiceConfig
}
func (dcs *defaultConfigSelector) SelectConfig(rpcInfo iresolver.RPCInfo) (*iresolver.RPCConfig, error) {
return &iresolver.RPCConfig{
Context: rpcInfo.Context,
MethodConfig: getMethodConfig(dcs.sc, rpcInfo.Method),
}, nil
}
// DialContext creates a client connection to the given target. By default, it's // DialContext creates a client connection to the given target. By default, it's
// a non-blocking dial (the function won't wait for connections to be // a non-blocking dial (the function won't wait for connections to be
// established, and connecting happens in the background). To make it a blocking // established, and connecting happens in the background). To make it a blocking
@ -133,6 +143,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
firstResolveEvent: grpcsync.NewEvent(), firstResolveEvent: grpcsync.NewEvent(),
} }
cc.retryThrottler.Store((*retryThrottler)(nil)) cc.retryThrottler.Store((*retryThrottler)(nil))
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
cc.ctx, cc.cancel = context.WithCancel(context.Background()) cc.ctx, cc.cancel = context.WithCancel(context.Background())
for _, opt := range opts { for _, opt := range opts {
@ -151,20 +162,17 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
if channelz.IsOn() { if channelz.IsOn() {
if cc.dopts.channelzParentID != 0 { if cc.dopts.channelzParentID != 0 {
cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, cc.dopts.channelzParentID, target) cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, cc.dopts.channelzParentID, target)
channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{ channelz.AddTraceEvent(logger, cc.channelzID, 0, &channelz.TraceEventDesc{
Desc: "Channel Created", Desc: "Channel Created",
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
Parent: &channelz.TraceEventDesc{ Parent: &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Nested Channel(id:%d) created", cc.channelzID), Desc: fmt.Sprintf("Nested Channel(id:%d) created", cc.channelzID),
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
}, },
}) })
} else { } else {
cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, 0, target) cc.channelzID = channelz.RegisterChannel(&channelzChannel{cc}, 0, target)
channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{ channelz.Info(logger, cc.channelzID, "Channel Created")
Desc: "Channel Created",
Severity: channelz.CtINFO,
})
} }
cc.csMgr.channelzID = cc.channelzID cc.csMgr.channelzID = cc.channelzID
} }
@ -196,15 +204,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
} }
cc.mkp = cc.dopts.copts.KeepaliveParams cc.mkp = cc.dopts.copts.KeepaliveParams
if cc.dopts.copts.Dialer == nil {
cc.dopts.copts.Dialer = newProxyDialer(
func(ctx context.Context, addr string) (net.Conn, error) {
network, addr := parseDialTarget(addr)
return (&net.Dialer{}).DialContext(ctx, network, addr)
},
)
}
if cc.dopts.copts.UserAgent != "" { if cc.dopts.copts.UserAgent != "" {
cc.dopts.copts.UserAgent += " " + grpcUA cc.dopts.copts.UserAgent += " " + grpcUA
} else { } else {
@ -219,7 +218,14 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
defer func() { defer func() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
switch {
case ctx.Err() == err:
conn = nil
case err == nil || !cc.dopts.returnLastError:
conn, err = nil, ctx.Err() conn, err = nil, ctx.Err()
default:
conn, err = nil, fmt.Errorf("%v: %v", ctx.Err(), err)
}
default: default:
} }
}() }()
@ -231,6 +237,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
case sc, ok := <-cc.dopts.scChan: case sc, ok := <-cc.dopts.scChan:
if ok { if ok {
cc.sc = &sc cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
scSet = true scSet = true
} }
default: default:
@ -241,14 +248,14 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
} }
// Determine the resolver to use. // Determine the resolver to use.
cc.parsedTarget = parseTarget(cc.target) cc.parsedTarget = grpcutil.ParseTarget(cc.target, cc.dopts.copts.Dialer != nil)
grpclog.Infof("parsed scheme: %q", cc.parsedTarget.Scheme) channelz.Infof(logger, cc.channelzID, "parsed scheme: %q", cc.parsedTarget.Scheme)
resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme) resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)
if resolverBuilder == nil { if resolverBuilder == nil {
// If resolver builder is still nil, the parsed target's scheme is // If resolver builder is still nil, the parsed target's scheme is
// not registered. Fallback to default resolver and set Endpoint to // not registered. Fallback to default resolver and set Endpoint to
// the original target. // the original target.
grpclog.Infof("scheme %q not registered, fallback to default scheme", cc.parsedTarget.Scheme) channelz.Infof(logger, cc.channelzID, "scheme %q not registered, fallback to default scheme", cc.parsedTarget.Scheme)
cc.parsedTarget = resolver.Target{ cc.parsedTarget = resolver.Target{
Scheme: resolver.GetDefaultScheme(), Scheme: resolver.GetDefaultScheme(),
Endpoint: target, Endpoint: target,
@ -264,6 +271,10 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
cc.authority = creds.Info().ServerName cc.authority = creds.Info().ServerName
} else if cc.dopts.insecure && cc.dopts.authority != "" { } else if cc.dopts.insecure && cc.dopts.authority != "" {
cc.authority = cc.dopts.authority cc.authority = cc.dopts.authority
} else if strings.HasPrefix(cc.target, "unix:") || strings.HasPrefix(cc.target, "unix-abstract:") {
cc.authority = "localhost"
} else if strings.HasPrefix(cc.parsedTarget.Endpoint, ":") {
cc.authority = "localhost" + cc.parsedTarget.Endpoint
} else { } else {
// Use endpoint from "scheme://authority/endpoint" as the default // Use endpoint from "scheme://authority/endpoint" as the default
// authority for ClientConn. // authority for ClientConn.
@ -276,6 +287,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
case sc, ok := <-cc.dopts.scChan: case sc, ok := <-cc.dopts.scChan:
if ok { if ok {
cc.sc = &sc cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
} }
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
@ -293,6 +305,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
DialCreds: credsClone, DialCreds: credsClone,
CredsBundle: cc.dopts.copts.CredsBundle, CredsBundle: cc.dopts.copts.CredsBundle,
Dialer: cc.dopts.copts.Dialer, Dialer: cc.dopts.copts.Dialer,
CustomUserAgent: cc.dopts.copts.UserAgent,
ChannelzParentID: cc.channelzID, ChannelzParentID: cc.channelzID,
Target: cc.parsedTarget, Target: cc.parsedTarget,
} }
@ -313,7 +326,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
if s == connectivity.Ready { if s == connectivity.Ready {
break break
} else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure { } else if cc.dopts.copts.FailOnNonTempDialError && s == connectivity.TransientFailure {
if err = cc.blockingpicker.connectionError(); err != nil { if err = cc.connectionError(); err != nil {
terr, ok := err.(interface { terr, ok := err.(interface {
Temporary() bool Temporary() bool
}) })
@ -324,6 +337,9 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
} }
if !cc.WaitForStateChange(ctx, s) { if !cc.WaitForStateChange(ctx, s) {
// ctx got timeout or canceled. // ctx got timeout or canceled.
if err = cc.connectionError(); err != nil && cc.dopts.returnLastError {
return nil, err
}
return nil, ctx.Err() return nil, ctx.Err()
} }
} }
@ -416,12 +432,7 @@ func (csm *connectivityStateManager) updateState(state connectivity.State) {
return return
} }
csm.state = state csm.state = state
if channelz.IsOn() { channelz.Infof(logger, csm.channelzID, "Channel Connectivity change to %v", state)
channelz.AddTraceEvent(csm.channelzID, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Channel Connectivity change to %v", state),
Severity: channelz.CtINFO,
})
}
if csm.notifyChan != nil { if csm.notifyChan != nil {
// There are other goroutines waiting on this channel. // There are other goroutines waiting on this channel.
close(csm.notifyChan) close(csm.notifyChan)
@ -483,6 +494,8 @@ type ClientConn struct {
balancerBuildOpts balancer.BuildOptions balancerBuildOpts balancer.BuildOptions
blockingpicker *pickerWrapper blockingpicker *pickerWrapper
safeConfigSelector iresolver.SafeConfigSelector
mu sync.RWMutex mu sync.RWMutex
resolverWrapper *ccResolverWrapper resolverWrapper *ccResolverWrapper
sc *ServiceConfig sc *ServiceConfig
@ -497,11 +510,18 @@ type ClientConn struct {
channelzID int64 // channelz unique identification number channelzID int64 // channelz unique identification number
czData *channelzData czData *channelzData
lceMu sync.Mutex // protects lastConnectionError
lastConnectionError error
} }
// WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or // WaitForStateChange waits until the connectivity.State of ClientConn changes from sourceState or
// ctx expires. A true value is returned in former case and false in latter. // ctx expires. A true value is returned in former case and false in latter.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connectivity.State) bool { func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connectivity.State) bool {
ch := cc.csMgr.getNotifyChan() ch := cc.csMgr.getNotifyChan()
if cc.csMgr.getState() != sourceState { if cc.csMgr.getState() != sourceState {
@ -516,7 +536,11 @@ func (cc *ClientConn) WaitForStateChange(ctx context.Context, sourceState connec
} }
// GetState returns the connectivity.State of ClientConn. // GetState returns the connectivity.State of ClientConn.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func (cc *ClientConn) GetState() connectivity.State { func (cc *ClientConn) GetState() connectivity.State {
return cc.csMgr.getState() return cc.csMgr.getState()
} }
@ -532,6 +556,7 @@ func (cc *ClientConn) scWatcher() {
// TODO: load balance policy runtime change is ignored. // TODO: load balance policy runtime change is ignored.
// We may revisit this decision in the future. // We may revisit this decision in the future.
cc.sc = &sc cc.sc = &sc
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{&sc})
cc.mu.Unlock() cc.mu.Unlock()
case <-cc.ctx.Done(): case <-cc.ctx.Done():
return return
@ -570,13 +595,13 @@ func init() {
func (cc *ClientConn) maybeApplyDefaultServiceConfig(addrs []resolver.Address) { func (cc *ClientConn) maybeApplyDefaultServiceConfig(addrs []resolver.Address) {
if cc.sc != nil { if cc.sc != nil {
cc.applyServiceConfigAndBalancer(cc.sc, addrs) cc.applyServiceConfigAndBalancer(cc.sc, nil, addrs)
return return
} }
if cc.dopts.defaultServiceConfig != nil { if cc.dopts.defaultServiceConfig != nil {
cc.applyServiceConfigAndBalancer(cc.dopts.defaultServiceConfig, addrs) cc.applyServiceConfigAndBalancer(cc.dopts.defaultServiceConfig, &defaultConfigSelector{cc.dopts.defaultServiceConfig}, addrs)
} else { } else {
cc.applyServiceConfigAndBalancer(emptyServiceConfig, addrs) cc.applyServiceConfigAndBalancer(emptyServiceConfig, &defaultConfigSelector{emptyServiceConfig}, addrs)
} }
} }
@ -613,7 +638,15 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
// default, per the error handling design? // default, per the error handling design?
} else { } else {
if sc, ok := s.ServiceConfig.Config.(*ServiceConfig); s.ServiceConfig.Err == nil && ok { if sc, ok := s.ServiceConfig.Config.(*ServiceConfig); s.ServiceConfig.Err == nil && ok {
cc.applyServiceConfigAndBalancer(sc, s.Addresses) configSelector := iresolver.GetConfigSelector(s)
if configSelector != nil {
if len(s.ServiceConfig.Config.(*ServiceConfig).Methods) != 0 {
channelz.Infof(logger, cc.channelzID, "method configs in service config will be ignored due to presence of config selector")
}
} else {
configSelector = &defaultConfigSelector{sc}
}
cc.applyServiceConfigAndBalancer(sc, configSelector, s.Addresses)
} else { } else {
ret = balancer.ErrBadResolverState ret = balancer.ErrBadResolverState
if cc.balancerWrapper == nil { if cc.balancerWrapper == nil {
@ -623,6 +656,7 @@ func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
} else { } else {
err = status.Errorf(codes.Unavailable, "illegal service config type: %T", s.ServiceConfig.Config) err = status.Errorf(codes.Unavailable, "illegal service config type: %T", s.ServiceConfig.Config)
} }
cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{cc.sc})
cc.blockingpicker.updatePicker(base.NewErrPicker(err)) cc.blockingpicker.updatePicker(base.NewErrPicker(err))
cc.csMgr.updateState(connectivity.TransientFailure) cc.csMgr.updateState(connectivity.TransientFailure)
cc.mu.Unlock() cc.mu.Unlock()
@ -671,9 +705,9 @@ func (cc *ClientConn) switchBalancer(name string) {
return return
} }
grpclog.Infof("ClientConn switching balancer to %q", name) channelz.Infof(logger, cc.channelzID, "ClientConn switching balancer to %q", name)
if cc.dopts.balancerBuilder != nil { if cc.dopts.balancerBuilder != nil {
grpclog.Infoln("ignoring balancer switching: Balancer DialOption used instead") channelz.Info(logger, cc.channelzID, "ignoring balancer switching: Balancer DialOption used instead")
return return
} }
if cc.balancerWrapper != nil { if cc.balancerWrapper != nil {
@ -681,22 +715,12 @@ func (cc *ClientConn) switchBalancer(name string) {
} }
builder := balancer.Get(name) builder := balancer.Get(name)
if channelz.IsOn() {
if builder == nil { if builder == nil {
channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{ channelz.Warningf(logger, cc.channelzID, "Channel switches to new LB policy %q due to fallback from invalid balancer name", PickFirstBalancerName)
Desc: fmt.Sprintf("Channel switches to new LB policy %q due to fallback from invalid balancer name", PickFirstBalancerName), channelz.Infof(logger, cc.channelzID, "failed to get balancer builder for: %v, using pick_first instead", name)
Severity: channelz.CtWarning,
})
} else {
channelz.AddTraceEvent(cc.channelzID, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Channel switches to new LB policy %q", name),
Severity: channelz.CtINFO,
})
}
}
if builder == nil {
grpclog.Infof("failed to get balancer builder for: %v, using pick_first instead", name)
builder = newPickfirstBuilder() builder = newPickfirstBuilder()
} else {
channelz.Infof(logger, cc.channelzID, "Channel switches to new LB policy %q", name)
} }
cc.curBalancerName = builder.Name() cc.curBalancerName = builder.Name()
@ -720,6 +744,7 @@ func (cc *ClientConn) handleSubConnStateChange(sc balancer.SubConn, s connectivi
// Caller needs to make sure len(addrs) > 0. // Caller needs to make sure len(addrs) > 0.
func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (*addrConn, error) { func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (*addrConn, error) {
ac := &addrConn{ ac := &addrConn{
state: connectivity.Idle,
cc: cc, cc: cc,
addrs: addrs, addrs: addrs,
scopts: opts, scopts: opts,
@ -736,12 +761,12 @@ func (cc *ClientConn) newAddrConn(addrs []resolver.Address, opts balancer.NewSub
} }
if channelz.IsOn() { if channelz.IsOn() {
ac.channelzID = channelz.RegisterSubChannel(ac, cc.channelzID, "") ac.channelzID = channelz.RegisterSubChannel(ac, cc.channelzID, "")
channelz.AddTraceEvent(ac.channelzID, &channelz.TraceEventDesc{ channelz.AddTraceEvent(logger, ac.channelzID, 0, &channelz.TraceEventDesc{
Desc: "Subchannel Created", Desc: "Subchannel Created",
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
Parent: &channelz.TraceEventDesc{ Parent: &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Subchannel(id:%d) created", ac.channelzID), Desc: fmt.Sprintf("Subchannel(id:%d) created", ac.channelzID),
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
}, },
}) })
} }
@ -775,7 +800,11 @@ func (cc *ClientConn) channelzMetric() *channelz.ChannelInternalMetric {
} }
// Target returns the target string of the ClientConn. // Target returns the target string of the ClientConn.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func (cc *ClientConn) Target() string { func (cc *ClientConn) Target() string {
return cc.target return cc.target
} }
@ -834,7 +863,7 @@ func (ac *addrConn) connect() error {
func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool { func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
ac.mu.Lock() ac.mu.Lock()
defer ac.mu.Unlock() defer ac.mu.Unlock()
grpclog.Infof("addrConn: tryUpdateAddrs curAddr: %v, addrs: %v", ac.curAddr, addrs) channelz.Infof(logger, ac.channelzID, "addrConn: tryUpdateAddrs curAddr: %v, addrs: %v", ac.curAddr, addrs)
if ac.state == connectivity.Shutdown || if ac.state == connectivity.Shutdown ||
ac.state == connectivity.TransientFailure || ac.state == connectivity.TransientFailure ||
ac.state == connectivity.Idle { ac.state == connectivity.Idle {
@ -854,7 +883,7 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
break break
} }
} }
grpclog.Infof("addrConn: tryUpdateAddrs curAddrFound: %v", curAddrFound) channelz.Infof(logger, ac.channelzID, "addrConn: tryUpdateAddrs curAddrFound: %v", curAddrFound)
if curAddrFound { if curAddrFound {
ac.addrs = addrs ac.addrs = addrs
} }
@ -862,26 +891,33 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
return curAddrFound return curAddrFound
} }
func getMethodConfig(sc *ServiceConfig, method string) MethodConfig {
if sc == nil {
return MethodConfig{}
}
if m, ok := sc.Methods[method]; ok {
return m
}
i := strings.LastIndex(method, "/")
if m, ok := sc.Methods[method[:i+1]]; ok {
return m
}
return sc.Methods[""]
}
// GetMethodConfig gets the method config of the input method. // GetMethodConfig gets the method config of the input method.
// If there's an exact match for input method (i.e. /service/method), we return // If there's an exact match for input method (i.e. /service/method), we return
// the corresponding MethodConfig. // the corresponding MethodConfig.
// If there isn't an exact match for the input method, we look for the default config // If there isn't an exact match for the input method, we look for the service's default
// under the service (i.e /service/). If there is a default MethodConfig for // config under the service (i.e /service/) and then for the default for all services (empty string).
// the service, we return it. //
// If there is a default MethodConfig for the service, we return it.
// Otherwise, we return an empty MethodConfig. // Otherwise, we return an empty MethodConfig.
func (cc *ClientConn) GetMethodConfig(method string) MethodConfig { func (cc *ClientConn) GetMethodConfig(method string) MethodConfig {
// TODO: Avoid the locking here. // TODO: Avoid the locking here.
cc.mu.RLock() cc.mu.RLock()
defer cc.mu.RUnlock() defer cc.mu.RUnlock()
if cc.sc == nil { return getMethodConfig(cc.sc, method)
return MethodConfig{}
}
m, ok := cc.sc.Methods[method]
if !ok {
i := strings.LastIndex(method, "/")
m = cc.sc.Methods[method[:i+1]]
}
return m
} }
func (cc *ClientConn) healthCheckConfig() *healthCheckConfig { func (cc *ClientConn) healthCheckConfig() *healthCheckConfig {
@ -904,12 +940,15 @@ func (cc *ClientConn) getTransport(ctx context.Context, failfast bool, method st
return t, done, nil return t, done, nil
} }
func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, addrs []resolver.Address) { func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, configSelector iresolver.ConfigSelector, addrs []resolver.Address) {
if sc == nil { if sc == nil {
// should never reach here. // should never reach here.
return return
} }
cc.sc = sc cc.sc = sc
if configSelector != nil {
cc.safeConfigSelector.UpdateConfigSelector(configSelector)
}
if cc.sc.retryThrottling != nil { if cc.sc.retryThrottling != nil {
newThrottler := &retryThrottler{ newThrottler := &retryThrottler{
@ -973,7 +1012,10 @@ func (cc *ClientConn) resolveNow(o resolver.ResolveNowOptions) {
// However, if a previously unavailable network becomes available, this may be // However, if a previously unavailable network becomes available, this may be
// used to trigger an immediate reconnect. // used to trigger an immediate reconnect.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func (cc *ClientConn) ResetConnectBackoff() { func (cc *ClientConn) ResetConnectBackoff() {
cc.mu.Lock() cc.mu.Lock()
conns := cc.conns conns := cc.conns
@ -1017,15 +1059,15 @@ func (cc *ClientConn) Close() error {
if channelz.IsOn() { if channelz.IsOn() {
ted := &channelz.TraceEventDesc{ ted := &channelz.TraceEventDesc{
Desc: "Channel Deleted", Desc: "Channel Deleted",
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
} }
if cc.dopts.channelzParentID != 0 { if cc.dopts.channelzParentID != 0 {
ted.Parent = &channelz.TraceEventDesc{ ted.Parent = &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Nested channel(id:%d) deleted", cc.channelzID), Desc: fmt.Sprintf("Nested channel(id:%d) deleted", cc.channelzID),
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
} }
} }
channelz.AddTraceEvent(cc.channelzID, ted) channelz.AddTraceEvent(logger, cc.channelzID, 0, ted)
// TraceEvent needs to be called before RemoveEntry, as TraceEvent may add trace reference to // TraceEvent needs to be called before RemoveEntry, as TraceEvent may add trace reference to
// the entity being deleted, and thus prevent it from being deleted right away. // the entity being deleted, and thus prevent it from being deleted right away.
channelz.RemoveEntry(cc.channelzID) channelz.RemoveEntry(cc.channelzID)
@ -1068,15 +1110,8 @@ func (ac *addrConn) updateConnectivityState(s connectivity.State, lastErr error)
if ac.state == s { if ac.state == s {
return return
} }
updateMsg := fmt.Sprintf("Subchannel Connectivity change to %v", s)
ac.state = s ac.state = s
if channelz.IsOn() { channelz.Infof(logger, ac.channelzID, "Subchannel Connectivity change to %v", s)
channelz.AddTraceEvent(ac.channelzID, &channelz.TraceEventDesc{
Desc: updateMsg,
Severity: channelz.CtINFO,
})
}
ac.cc.handleSubConnStateChange(ac.acbw, s, lastErr) ac.cc.handleSubConnStateChange(ac.acbw, s, lastErr)
} }
@ -1163,7 +1198,7 @@ func (ac *addrConn) resetTransport() {
ac.mu.Lock() ac.mu.Lock()
if ac.state == connectivity.Shutdown { if ac.state == connectivity.Shutdown {
ac.mu.Unlock() ac.mu.Unlock()
newTr.Close() newTr.Close(fmt.Errorf("reached connectivity state: SHUTDOWN"))
return return
} }
ac.curAddr = addr ac.curAddr = addr
@ -1213,12 +1248,7 @@ func (ac *addrConn) tryAllAddrs(addrs []resolver.Address, connectDeadline time.T
} }
ac.mu.Unlock() ac.mu.Unlock()
if channelz.IsOn() { channelz.Infof(logger, ac.channelzID, "Subchannel picks a new address %q to connect", addr.Addr)
channelz.AddTraceEvent(ac.channelzID, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Subchannel picks a new address %q to connect", addr.Addr),
Severity: channelz.CtINFO,
})
}
newTr, reconnect, err := ac.createTransport(addr, copts, connectDeadline) newTr, reconnect, err := ac.createTransport(addr, copts, connectDeadline)
if err == nil { if err == nil {
@ -1227,7 +1257,7 @@ func (ac *addrConn) tryAllAddrs(addrs []resolver.Address, connectDeadline time.T
if firstConnErr == nil { if firstConnErr == nil {
firstConnErr = err firstConnErr = err
} }
ac.cc.blockingpicker.updateConnectionError(err) ac.cc.updateConnectionError(err)
} }
// Couldn't connect to any address. // Couldn't connect to any address.
@ -1242,16 +1272,9 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
onCloseCalled := make(chan struct{}) onCloseCalled := make(chan struct{})
reconnect := grpcsync.NewEvent() reconnect := grpcsync.NewEvent()
authority := ac.cc.authority
// addr.ServerName takes precedent over ClientConn authority, if present. // addr.ServerName takes precedent over ClientConn authority, if present.
if addr.ServerName != "" { if addr.ServerName == "" {
authority = addr.ServerName addr.ServerName = ac.cc.authority
}
target := transport.TargetInfo{
Addr: addr.Addr,
Metadata: addr.Metadata,
Authority: authority,
} }
once := sync.Once{} once := sync.Once{}
@ -1297,18 +1320,18 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
copts.ChannelzParentID = ac.channelzID copts.ChannelzParentID = ac.channelzID
} }
newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, target, copts, onPrefaceReceipt, onGoAway, onClose) newTr, err := transport.NewClientTransport(connectCtx, ac.cc.ctx, addr, copts, onPrefaceReceipt, onGoAway, onClose)
if err != nil { if err != nil {
// newTr is either nil, or closed. // newTr is either nil, or closed.
grpclog.Warningf("grpc: addrConn.createTransport failed to connect to %v. Err :%v. Reconnecting...", addr, err) channelz.Warningf(logger, ac.channelzID, "grpc: addrConn.createTransport failed to connect to %v. Err: %v. Reconnecting...", addr, err)
return nil, nil, err return nil, nil, err
} }
select { select {
case <-time.After(time.Until(connectDeadline)): case <-time.After(time.Until(connectDeadline)):
// We didn't get the preface in time. // We didn't get the preface in time.
newTr.Close() newTr.Close(fmt.Errorf("failed to receive server preface within timeout"))
grpclog.Warningf("grpc: addrConn.createTransport failed to connect to %v: didn't receive server preface in time. Reconnecting...", addr) channelz.Warningf(logger, ac.channelzID, "grpc: addrConn.createTransport failed to connect to %v: didn't receive server preface in time. Reconnecting...", addr)
return nil, nil, errors.New("timed out waiting for server handshake") return nil, nil, errors.New("timed out waiting for server handshake")
case <-prefaceReceived: case <-prefaceReceived:
// We got the preface - huzzah! things are good. // We got the preface - huzzah! things are good.
@ -1325,7 +1348,7 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
// //
// LB channel health checking is enabled when all requirements below are met: // LB channel health checking is enabled when all requirements below are met:
// 1. it is not disabled by the user with the WithDisableHealthCheck DialOption // 1. it is not disabled by the user with the WithDisableHealthCheck DialOption
// 2. internal.HealthCheckFunc is set by importing the grpc/healthcheck package // 2. internal.HealthCheckFunc is set by importing the grpc/health package
// 3. a service config with non-empty healthCheckConfig field is provided // 3. a service config with non-empty healthCheckConfig field is provided
// 4. the load balancer requests it // 4. the load balancer requests it
// //
@ -1355,7 +1378,7 @@ func (ac *addrConn) startHealthCheck(ctx context.Context) {
// The health package is not imported to set health check function. // The health package is not imported to set health check function.
// //
// TODO: add a link to the health check doc in the error message. // TODO: add a link to the health check doc in the error message.
grpclog.Error("Health check is requested but health check function is not set.") channelz.Error(logger, ac.channelzID, "Health check is requested but health check function is not set.")
return return
} }
@ -1385,15 +1408,9 @@ func (ac *addrConn) startHealthCheck(ctx context.Context) {
err := ac.cc.dopts.healthCheckFunc(ctx, newStream, setConnectivityState, healthCheckConfig.ServiceName) err := ac.cc.dopts.healthCheckFunc(ctx, newStream, setConnectivityState, healthCheckConfig.ServiceName)
if err != nil { if err != nil {
if status.Code(err) == codes.Unimplemented { if status.Code(err) == codes.Unimplemented {
if channelz.IsOn() { channelz.Error(logger, ac.channelzID, "Subchannel health check is unimplemented at server side, thus health check is disabled")
channelz.AddTraceEvent(ac.channelzID, &channelz.TraceEventDesc{
Desc: "Subchannel health check is unimplemented at server side, thus health check is disabled",
Severity: channelz.CtError,
})
}
grpclog.Error("Subchannel health check is unimplemented at server side, thus health check is disabled")
} else { } else {
grpclog.Errorf("HealthCheckFunc exits with unexpected error %v", err) channelz.Errorf(logger, ac.channelzID, "HealthCheckFunc exits with unexpected error %v", err)
} }
} }
}() }()
@ -1430,10 +1447,9 @@ func (ac *addrConn) getReadyTransport() (transport.ClientTransport, bool) {
} }
// tearDown starts to tear down the addrConn. // tearDown starts to tear down the addrConn.
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in //
// some edge cases (e.g., the caller opens and closes many addrConn's in a // Note that tearDown doesn't remove ac from ac.cc.conns, so the addrConn struct
// tight loop. // will leak. In most cases, call cc.removeAddrConn() instead.
// tearDown doesn't remove ac from ac.cc.conns.
func (ac *addrConn) tearDown(err error) { func (ac *addrConn) tearDown(err error) {
ac.mu.Lock() ac.mu.Lock()
if ac.state == connectivity.Shutdown { if ac.state == connectivity.Shutdown {
@ -1458,12 +1474,12 @@ func (ac *addrConn) tearDown(err error) {
ac.mu.Lock() ac.mu.Lock()
} }
if channelz.IsOn() { if channelz.IsOn() {
channelz.AddTraceEvent(ac.channelzID, &channelz.TraceEventDesc{ channelz.AddTraceEvent(logger, ac.channelzID, 0, &channelz.TraceEventDesc{
Desc: "Subchannel Deleted", Desc: "Subchannel Deleted",
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
Parent: &channelz.TraceEventDesc{ Parent: &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Subchanel(id:%d) deleted", ac.channelzID), Desc: fmt.Sprintf("Subchanel(id:%d) deleted", ac.channelzID),
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
}, },
}) })
// TraceEvent needs to be called before RemoveEntry, as TraceEvent may add trace reference to // TraceEvent needs to be called before RemoveEntry, as TraceEvent may add trace reference to
@ -1560,9 +1576,21 @@ var ErrClientConnTimeout = errors.New("grpc: timed out when dialing")
func (cc *ClientConn) getResolver(scheme string) resolver.Builder { func (cc *ClientConn) getResolver(scheme string) resolver.Builder {
for _, rb := range cc.dopts.resolvers { for _, rb := range cc.dopts.resolvers {
if cc.parsedTarget.Scheme == rb.Scheme() { if scheme == rb.Scheme() {
return rb return rb
} }
} }
return resolver.Get(cc.parsedTarget.Scheme) return resolver.Get(scheme)
}
func (cc *ClientConn) updateConnectionError(err error) {
cc.lceMu.Lock()
cc.lastConnectionError = err
cc.lceMu.Unlock()
}
func (cc *ClientConn) connectionError() error {
cc.lceMu.Lock()
defer cc.lceMu.Unlock()
return cc.lastConnectionError
} }

View File

@ -33,6 +33,9 @@ const (
OK Code = 0 OK Code = 0
// Canceled indicates the operation was canceled (typically by the caller). // Canceled indicates the operation was canceled (typically by the caller).
//
// The gRPC framework will generate this error code when cancellation
// is requested.
Canceled Code = 1 Canceled Code = 1
// Unknown error. An example of where this error may be returned is // Unknown error. An example of where this error may be returned is
@ -40,12 +43,17 @@ const (
// an error-space that is not known in this address space. Also // an error-space that is not known in this address space. Also
// errors raised by APIs that do not return enough error information // errors raised by APIs that do not return enough error information
// may be converted to this error. // may be converted to this error.
//
// The gRPC framework will generate this error code in the above two
// mentioned cases.
Unknown Code = 2 Unknown Code = 2
// InvalidArgument indicates client specified an invalid argument. // InvalidArgument indicates client specified an invalid argument.
// Note that this differs from FailedPrecondition. It indicates arguments // Note that this differs from FailedPrecondition. It indicates arguments
// that are problematic regardless of the state of the system // that are problematic regardless of the state of the system
// (e.g., a malformed file name). // (e.g., a malformed file name).
//
// This error code will not be generated by the gRPC framework.
InvalidArgument Code = 3 InvalidArgument Code = 3
// DeadlineExceeded means operation expired before completion. // DeadlineExceeded means operation expired before completion.
@ -53,14 +61,21 @@ const (
// returned even if the operation has completed successfully. For // returned even if the operation has completed successfully. For
// example, a successful response from a server could have been delayed // example, a successful response from a server could have been delayed
// long enough for the deadline to expire. // long enough for the deadline to expire.
//
// The gRPC framework will generate this error code when the deadline is
// exceeded.
DeadlineExceeded Code = 4 DeadlineExceeded Code = 4
// NotFound means some requested entity (e.g., file or directory) was // NotFound means some requested entity (e.g., file or directory) was
// not found. // not found.
//
// This error code will not be generated by the gRPC framework.
NotFound Code = 5 NotFound Code = 5
// AlreadyExists means an attempt to create an entity failed because one // AlreadyExists means an attempt to create an entity failed because one
// already exists. // already exists.
//
// This error code will not be generated by the gRPC framework.
AlreadyExists Code = 6 AlreadyExists Code = 6
// PermissionDenied indicates the caller does not have permission to // PermissionDenied indicates the caller does not have permission to
@ -69,10 +84,17 @@ const (
// instead for those errors). It must not be // instead for those errors). It must not be
// used if the caller cannot be identified (use Unauthenticated // used if the caller cannot be identified (use Unauthenticated
// instead for those errors). // instead for those errors).
//
// This error code will not be generated by the gRPC core framework,
// but expect authentication middleware to use it.
PermissionDenied Code = 7 PermissionDenied Code = 7
// ResourceExhausted indicates some resource has been exhausted, perhaps // ResourceExhausted indicates some resource has been exhausted, perhaps
// a per-user quota, or perhaps the entire file system is out of space. // a per-user quota, or perhaps the entire file system is out of space.
//
// This error code will be generated by the gRPC framework in
// out-of-memory and server overload situations, or when a message is
// larger than the configured maximum size.
ResourceExhausted Code = 8 ResourceExhausted Code = 8
// FailedPrecondition indicates operation was rejected because the // FailedPrecondition indicates operation was rejected because the
@ -94,6 +116,8 @@ const (
// REST Get/Update/Delete on a resource and the resource on the // REST Get/Update/Delete on a resource and the resource on the
// server does not match the condition. E.g., conflicting // server does not match the condition. E.g., conflicting
// read-modify-write on the same resource. // read-modify-write on the same resource.
//
// This error code will not be generated by the gRPC framework.
FailedPrecondition Code = 9 FailedPrecondition Code = 9
// Aborted indicates the operation was aborted, typically due to a // Aborted indicates the operation was aborted, typically due to a
@ -102,6 +126,8 @@ const (
// //
// See litmus test above for deciding between FailedPrecondition, // See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable. // Aborted, and Unavailable.
//
// This error code will not be generated by the gRPC framework.
Aborted Code = 10 Aborted Code = 10
// OutOfRange means operation was attempted past the valid range. // OutOfRange means operation was attempted past the valid range.
@ -119,15 +145,26 @@ const (
// error) when it applies so that callers who are iterating through // error) when it applies so that callers who are iterating through
// a space can easily look for an OutOfRange error to detect when // a space can easily look for an OutOfRange error to detect when
// they are done. // they are done.
//
// This error code will not be generated by the gRPC framework.
OutOfRange Code = 11 OutOfRange Code = 11
// Unimplemented indicates operation is not implemented or not // Unimplemented indicates operation is not implemented or not
// supported/enabled in this service. // supported/enabled in this service.
//
// This error code will be generated by the gRPC framework. Most
// commonly, you will see this error code when a method implementation
// is missing on the server. It can also be generated for unknown
// compression algorithms or a disagreement as to whether an RPC should
// be streaming.
Unimplemented Code = 12 Unimplemented Code = 12
// Internal errors. Means some invariants expected by underlying // Internal errors. Means some invariants expected by underlying
// system has been broken. If you see one of these errors, // system has been broken. If you see one of these errors,
// something is very broken. // something is very broken.
//
// This error code will be generated by the gRPC framework in several
// internal error conditions.
Internal Code = 13 Internal Code = 13
// Unavailable indicates the service is currently unavailable. // Unavailable indicates the service is currently unavailable.
@ -137,13 +174,22 @@ const (
// //
// See litmus test above for deciding between FailedPrecondition, // See litmus test above for deciding between FailedPrecondition,
// Aborted, and Unavailable. // Aborted, and Unavailable.
//
// This error code will be generated by the gRPC framework during
// abrupt shutdown of a server process or network connection.
Unavailable Code = 14 Unavailable Code = 14
// DataLoss indicates unrecoverable data loss or corruption. // DataLoss indicates unrecoverable data loss or corruption.
//
// This error code will not be generated by the gRPC framework.
DataLoss Code = 15 DataLoss Code = 15
// Unauthenticated indicates the request does not have valid // Unauthenticated indicates the request does not have valid
// authentication credentials for the operation. // authentication credentials for the operation.
//
// The gRPC framework will generate this error code when the
// authentication metadata is invalid or a Credentials callback fails,
// but also expect authentication middleware to generate it.
Unauthenticated Code = 16 Unauthenticated Code = 16
_maxCode = 17 _maxCode = 17

View File

@ -22,11 +22,11 @@
package connectivity package connectivity
import ( import (
"context"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )
var logger = grpclog.Component("core")
// State indicates the state of connectivity. // State indicates the state of connectivity.
// It can be the state of a ClientConn or SubConn. // It can be the state of a ClientConn or SubConn.
type State int type State int
@ -44,7 +44,7 @@ func (s State) String() string {
case Shutdown: case Shutdown:
return "SHUTDOWN" return "SHUTDOWN"
default: default:
grpclog.Errorf("unknown connectivity state: %d", s) logger.Errorf("unknown connectivity state: %d", s)
return "Invalid-State" return "Invalid-State"
} }
} }
@ -61,13 +61,3 @@ const (
// Shutdown indicates the ClientConn has started shutting down. // Shutdown indicates the ClientConn has started shutting down.
Shutdown Shutdown
) )
// Reporter reports the connectivity states.
type Reporter interface {
// CurrentState returns the current state of the reporter.
CurrentState() State
// WaitForStateChange blocks until the reporter's state is different from the given state,
// and returns true.
// It returns false if <-ctx.Done() can proceed (ctx got timeout or got canceled).
WaitForStateChange(context.Context, State) bool
}

View File

@ -29,7 +29,8 @@ import (
"net" "net"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"google.golang.org/grpc/internal" "google.golang.org/grpc/attributes"
icredentials "google.golang.org/grpc/internal/credentials"
) )
// PerRPCCredentials defines the common interface for the credentials which need to // PerRPCCredentials defines the common interface for the credentials which need to
@ -57,9 +58,11 @@ type PerRPCCredentials interface {
type SecurityLevel int type SecurityLevel int
const ( const (
// NoSecurity indicates a connection is insecure. // InvalidSecurityLevel indicates an invalid security level.
// The zero SecurityLevel value is invalid for backward compatibility. // The zero SecurityLevel value is invalid for backward compatibility.
NoSecurity SecurityLevel = iota + 1 InvalidSecurityLevel SecurityLevel = iota
// NoSecurity indicates a connection is insecure.
NoSecurity
// IntegrityOnly indicates a connection only provides integrity protection. // IntegrityOnly indicates a connection only provides integrity protection.
IntegrityOnly IntegrityOnly
// PrivacyAndIntegrity indicates a connection provides both privacy and integrity protection. // PrivacyAndIntegrity indicates a connection provides both privacy and integrity protection.
@ -89,7 +92,7 @@ type CommonAuthInfo struct {
} }
// GetCommonAuthInfo returns the pointer to CommonAuthInfo struct. // GetCommonAuthInfo returns the pointer to CommonAuthInfo struct.
func (c *CommonAuthInfo) GetCommonAuthInfo() *CommonAuthInfo { func (c CommonAuthInfo) GetCommonAuthInfo() CommonAuthInfo {
return c return c
} }
@ -100,7 +103,11 @@ type ProtocolInfo struct {
ProtocolVersion string ProtocolVersion string
// SecurityProtocol is the security protocol in use. // SecurityProtocol is the security protocol in use.
SecurityProtocol string SecurityProtocol string
// SecurityVersion is the security protocol version. // SecurityVersion is the security protocol version. It is a static version string from the
// credentials, not a value that reflects per-connection protocol negotiation. To retrieve
// details about the credentials used for a connection, use the Peer's AuthInfo field instead.
//
// Deprecated: please use Peer.AuthInfo.
SecurityVersion string SecurityVersion string
// ServerName is the user-configured server name. // ServerName is the user-configured server name.
ServerName string ServerName string
@ -120,15 +127,18 @@ var ErrConnDispatched = errors.New("credentials: rawConn is dispatched out of gR
// TransportCredentials defines the common interface for all the live gRPC wire // TransportCredentials defines the common interface for all the live gRPC wire
// protocols and supported transport security protocols (e.g., TLS, SSL). // protocols and supported transport security protocols (e.g., TLS, SSL).
type TransportCredentials interface { type TransportCredentials interface {
// ClientHandshake does the authentication handshake specified by the corresponding // ClientHandshake does the authentication handshake specified by the
// authentication protocol on rawConn for clients. It returns the authenticated // corresponding authentication protocol on rawConn for clients. It returns
// connection and the corresponding auth information about the connection. // the authenticated connection and the corresponding auth information
// The auth information should embed CommonAuthInfo to return additional information about // about the connection. The auth information should embed CommonAuthInfo
// the credentials. Implementations must use the provided context to implement timely cancellation. // to return additional information about the credentials. Implementations
// gRPC will try to reconnect if the error returned is a temporary error // must use the provided context to implement timely cancellation. gRPC
// (io.EOF, context.DeadlineExceeded or err.Temporary() == true). // will try to reconnect if the error returned is a temporary error
// If the returned error is a wrapper error, implementations should make sure that // (io.EOF, context.DeadlineExceeded or err.Temporary() == true). If the
// returned error is a wrapper error, implementations should make sure that
// the error implements Temporary() to have the correct retry behaviors. // the error implements Temporary() to have the correct retry behaviors.
// Additionally, ClientHandshakeInfo data will be available via the context
// passed to this call.
// //
// If the returned net.Conn is closed, it MUST close the net.Conn provided. // If the returned net.Conn is closed, it MUST close the net.Conn provided.
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error) ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
@ -178,15 +188,33 @@ type RequestInfo struct {
AuthInfo AuthInfo AuthInfo AuthInfo
} }
// requestInfoKey is a struct to be used as the key when attaching a RequestInfo to a context object.
type requestInfoKey struct{}
// RequestInfoFromContext extracts the RequestInfo from the context if it exists. // RequestInfoFromContext extracts the RequestInfo from the context if it exists.
// //
// This API is experimental. // This API is experimental.
func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) { func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) {
ri, ok = ctx.Value(requestInfoKey{}).(RequestInfo) ri, ok = icredentials.RequestInfoFromContext(ctx).(RequestInfo)
return return ri, ok
}
// ClientHandshakeInfo holds data to be passed to ClientHandshake. This makes
// it possible to pass arbitrary data to the handshaker from gRPC, resolver,
// balancer etc. Individual credential implementations control the actual
// format of the data that they are willing to receive.
//
// This API is experimental.
type ClientHandshakeInfo struct {
// Attributes contains the attributes for the address. It could be provided
// by the gRPC, resolver, balancer etc.
Attributes *attributes.Attributes
}
// ClientHandshakeInfoFromContext returns the ClientHandshakeInfo struct stored
// in ctx.
//
// This API is experimental.
func ClientHandshakeInfoFromContext(ctx context.Context) ClientHandshakeInfo {
chi, _ := icredentials.ClientHandshakeInfoFromContext(ctx).(ClientHandshakeInfo)
return chi
} }
// CheckSecurityLevel checks if a connection's security level is greater than or equal to the specified one. // CheckSecurityLevel checks if a connection's security level is greater than or equal to the specified one.
@ -194,17 +222,16 @@ func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) {
// or 3) CommonAuthInfo.SecurityLevel has an invalid zero value. For 2) and 3), it is for the purpose of backward-compatibility. // or 3) CommonAuthInfo.SecurityLevel has an invalid zero value. For 2) and 3), it is for the purpose of backward-compatibility.
// //
// This API is experimental. // This API is experimental.
func CheckSecurityLevel(ctx context.Context, level SecurityLevel) error { func CheckSecurityLevel(ai AuthInfo, level SecurityLevel) error {
type internalInfo interface { type internalInfo interface {
GetCommonAuthInfo() *CommonAuthInfo GetCommonAuthInfo() CommonAuthInfo
} }
ri, _ := RequestInfoFromContext(ctx) if ai == nil {
if ri.AuthInfo == nil { return errors.New("AuthInfo is nil")
return errors.New("unable to obtain SecurityLevel from context")
} }
if ci, ok := ri.AuthInfo.(internalInfo); ok { if ci, ok := ai.(internalInfo); ok {
// CommonAuthInfo.SecurityLevel has an invalid value. // CommonAuthInfo.SecurityLevel has an invalid value.
if ci.GetCommonAuthInfo().SecurityLevel == 0 { if ci.GetCommonAuthInfo().SecurityLevel == InvalidSecurityLevel {
return nil return nil
} }
if ci.GetCommonAuthInfo().SecurityLevel < level { if ci.GetCommonAuthInfo().SecurityLevel < level {
@ -215,12 +242,6 @@ func CheckSecurityLevel(ctx context.Context, level SecurityLevel) error {
return nil return nil
} }
func init() {
internal.NewRequestInfoContext = func(ctx context.Context, ri RequestInfo) context.Context {
return context.WithValue(ctx, requestInfoKey{}, ri)
}
}
// ChannelzSecurityInfo defines the interface that security protocols should implement // ChannelzSecurityInfo defines the interface that security protocols should implement
// in order to provide security info to channelz. // in order to provide security info to channelz.
// //

View File

@ -25,8 +25,9 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"net/url"
"google.golang.org/grpc/credentials/internal" credinternal "google.golang.org/grpc/internal/credentials"
) )
// TLSInfo contains the auth information for a TLS authenticated connection. // TLSInfo contains the auth information for a TLS authenticated connection.
@ -34,6 +35,8 @@ import (
type TLSInfo struct { type TLSInfo struct {
State tls.ConnectionState State tls.ConnectionState
CommonAuthInfo CommonAuthInfo
// This API is experimental.
SPIFFEID *url.URL
} }
// AuthType returns the type of TLSInfo as a string. // AuthType returns the type of TLSInfo as a string.
@ -69,7 +72,7 @@ func (c tlsCreds) Info() ProtocolInfo {
func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) { func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) {
// use local cfg to avoid clobbering ServerName if using multiple endpoints // use local cfg to avoid clobbering ServerName if using multiple endpoints
cfg := cloneTLSConfig(c.config) cfg := credinternal.CloneTLSConfig(c.config)
if cfg.ServerName == "" { if cfg.ServerName == "" {
serverName, _, err := net.SplitHostPort(authority) serverName, _, err := net.SplitHostPort(authority)
if err != nil { if err != nil {
@ -94,7 +97,17 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawCon
conn.Close() conn.Close()
return nil, nil, ctx.Err() return nil, nil, ctx.Err()
} }
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil tlsInfo := TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: CommonAuthInfo{
SecurityLevel: PrivacyAndIntegrity,
},
}
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
if id != nil {
tlsInfo.SPIFFEID = id
}
return credinternal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
} }
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) { func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
@ -103,7 +116,17 @@ func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error)
conn.Close() conn.Close()
return nil, nil, err return nil, nil, err
} }
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil tlsInfo := TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: CommonAuthInfo{
SecurityLevel: PrivacyAndIntegrity,
},
}
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
if id != nil {
tlsInfo.SPIFFEID = id
}
return credinternal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
} }
func (c *tlsCreds) Clone() TransportCredentials { func (c *tlsCreds) Clone() TransportCredentials {
@ -115,36 +138,33 @@ func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
return nil return nil
} }
const alpnProtoStrH2 = "h2"
func appendH2ToNextProtos(ps []string) []string {
for _, p := range ps {
if p == alpnProtoStrH2 {
return ps
}
}
ret := make([]string, 0, len(ps)+1)
ret = append(ret, ps...)
return append(ret, alpnProtoStrH2)
}
// NewTLS uses c to construct a TransportCredentials based on TLS. // NewTLS uses c to construct a TransportCredentials based on TLS.
func NewTLS(c *tls.Config) TransportCredentials { func NewTLS(c *tls.Config) TransportCredentials {
tc := &tlsCreds{cloneTLSConfig(c)} tc := &tlsCreds{credinternal.CloneTLSConfig(c)}
tc.config.NextProtos = appendH2ToNextProtos(tc.config.NextProtos) tc.config.NextProtos = credinternal.AppendH2ToNextProtos(tc.config.NextProtos)
return tc return tc
} }
// NewClientTLSFromCert constructs TLS credentials from the input certificate for client. // NewClientTLSFromCert constructs TLS credentials from the provided root
// certificate authority certificate(s) to validate server connections. If
// certificates to establish the identity of the client need to be included in
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
// tls.Config can be specified.
// serverNameOverride is for testing only. If set to a non empty string, // serverNameOverride is for testing only. If set to a non empty string,
// it will override the virtual host name of authority (e.g. :authority header field) in requests. // it will override the virtual host name of authority (e.g. :authority header
// field) in requests.
func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials { func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials {
return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}) return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp})
} }
// NewClientTLSFromFile constructs TLS credentials from the input certificate file for client. // NewClientTLSFromFile constructs TLS credentials from the provided root
// certificate authority certificate file(s) to validate server connections. If
// certificates to establish the identity of the client need to be included in
// the credentials (eg: for mTLS), use NewTLS instead, where a complete
// tls.Config can be specified.
// serverNameOverride is for testing only. If set to a non empty string, // serverNameOverride is for testing only. If set to a non empty string,
// it will override the virtual host name of authority (e.g. :authority header field) in requests. // it will override the virtual host name of authority (e.g. :authority header
// field) in requests.
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) { func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
b, err := ioutil.ReadFile(certFile) b, err := ioutil.ReadFile(certFile)
if err != nil { if err != nil {
@ -175,7 +195,10 @@ func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error
// TLSChannelzSecurityValue defines the struct that TLS protocol should return // TLSChannelzSecurityValue defines the struct that TLS protocol should return
// from GetSecurityValue(), containing security info like cipher and certificate used. // from GetSecurityValue(), containing security info like cipher and certificate used.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type TLSChannelzSecurityValue struct { type TLSChannelzSecurityValue struct {
ChannelzSecurityValue ChannelzSecurityValue
StandardName string StandardName string
@ -208,18 +231,3 @@ var cipherSuiteLookup = map[uint16]string{
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
} }
// cloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// If cfg is nil, a new zero tls.Config is returned.
//
// TODO: inline this function if possible.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@ -27,7 +27,6 @@ import (
"google.golang.org/grpc/backoff" "google.golang.org/grpc/backoff"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal" "google.golang.org/grpc/internal"
internalbackoff "google.golang.org/grpc/internal/backoff" internalbackoff "google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/envconfig"
@ -50,14 +49,14 @@ type dialOptions struct {
dc Decompressor dc Decompressor
bs internalbackoff.Strategy bs internalbackoff.Strategy
block bool block bool
returnLastError bool
insecure bool insecure bool
timeout time.Duration timeout time.Duration
scChan <-chan ServiceConfig scChan <-chan ServiceConfig
authority string authority string
copts transport.ConnectOptions copts transport.ConnectOptions
callOptions []CallOption callOptions []CallOption
// This is used by v1 balancer dial option WithBalancer to support v1 // This is used by WithBalancerName dial option.
// balancer, and also by WithBalancerName dial option.
balancerBuilder balancer.Builder balancerBuilder balancer.Builder
channelzParentID int64 channelzParentID int64
disableServiceConfig bool disableServiceConfig bool
@ -67,10 +66,6 @@ type dialOptions struct {
minConnectTimeout func() time.Duration minConnectTimeout func() time.Duration
defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON. defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
defaultServiceConfigRawJSON *string defaultServiceConfigRawJSON *string
// This is used by ccResolverWrapper to backoff between successive calls to
// resolver.ResolveNow(). The user will have no need to configure this, but
// we need to be able to configure this in tests.
resolveNowBackoff func(int) time.Duration
resolvers []resolver.Builder resolvers []resolver.Builder
} }
@ -82,7 +77,10 @@ type DialOption interface {
// EmptyDialOption does not alter the dial configuration. It can be embedded in // EmptyDialOption does not alter the dial configuration. It can be embedded in
// another structure to build custom dial options. // another structure to build custom dial options.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type EmptyDialOption struct{} type EmptyDialOption struct{}
func (EmptyDialOption) apply(*dialOptions) {} func (EmptyDialOption) apply(*dialOptions) {}
@ -198,19 +196,6 @@ func WithDecompressor(dc Decompressor) DialOption {
}) })
} }
// WithBalancer returns a DialOption which sets a load balancer with the v1 API.
// Name resolver will be ignored if this DialOption is specified.
//
// Deprecated: use the new balancer APIs in balancer package and
// WithBalancerName. Will be removed in a future 1.x release.
func WithBalancer(b Balancer) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.balancerBuilder = &balancerWrapperBuilder{
b: b,
}
})
}
// WithBalancerName sets the balancer that the ClientConn will be initialized // WithBalancerName sets the balancer that the ClientConn will be initialized
// with. Balancer registered with balancerName will be used. This function // with. Balancer registered with balancerName will be used. This function
// panics if no balancer was registered by balancerName. // panics if no balancer was registered by balancerName.
@ -251,7 +236,10 @@ func WithServiceConfig(c <-chan ServiceConfig) DialOption {
// using the backoff.DefaultConfig as a base, in cases where you want to // using the backoff.DefaultConfig as a base, in cases where you want to
// override only a subset of the backoff configuration. // override only a subset of the backoff configuration.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithConnectParams(p ConnectParams) DialOption { func WithConnectParams(p ConnectParams) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.bs = internalbackoff.Exponential{Config: p.Backoff} o.bs = internalbackoff.Exponential{Config: p.Backoff}
@ -298,6 +286,22 @@ func WithBlock() DialOption {
}) })
} }
// WithReturnConnectionError returns a DialOption which makes the client connection
// return a string containing both the last connection error that occurred and
// the context.DeadlineExceeded error.
// Implies WithBlock()
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithReturnConnectionError() DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.block = true
o.returnLastError = true
})
}
// WithInsecure returns a DialOption which disables transport security for this // WithInsecure returns a DialOption which disables transport security for this
// ClientConn. Note that transport security is required unless WithInsecure is // ClientConn. Note that transport security is required unless WithInsecure is
// set. // set.
@ -307,6 +311,19 @@ func WithInsecure() DialOption {
}) })
} }
// WithNoProxy returns a DialOption which disables the use of proxies for this
// ClientConn. This is ignored if WithDialer or WithContextDialer are used.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithNoProxy() DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.UseProxy = false
})
}
// WithTransportCredentials returns a DialOption which configures a connection // WithTransportCredentials returns a DialOption which configures a connection
// level security credentials (e.g., TLS/SSL). This should not be used together // level security credentials (e.g., TLS/SSL). This should not be used together
// with WithCredentialsBundle. // with WithCredentialsBundle.
@ -328,7 +345,10 @@ func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption {
// the ClientConn.WithCreds. This should not be used together with // the ClientConn.WithCreds. This should not be used together with
// WithTransportCredentials. // WithTransportCredentials.
// //
// This API is experimental. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithCredentialsBundle(b credentials.Bundle) DialOption { func WithCredentialsBundle(b credentials.Bundle) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.copts.CredsBundle = b o.copts.CredsBundle = b
@ -393,7 +413,10 @@ func WithStatsHandler(h stats.Handler) DialOption {
// FailOnNonTempDialError only affects the initial dial, and does not do // FailOnNonTempDialError only affects the initial dial, and does not do
// anything useful unless you are also using WithBlock(). // anything useful unless you are also using WithBlock().
// //
// This is an EXPERIMENTAL API. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func FailOnNonTempDialError(f bool) DialOption { func FailOnNonTempDialError(f bool) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.copts.FailOnNonTempDialError = f o.copts.FailOnNonTempDialError = f
@ -412,7 +435,7 @@ func WithUserAgent(s string) DialOption {
// for the client transport. // for the client transport.
func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption { func WithKeepaliveParams(kp keepalive.ClientParameters) DialOption {
if kp.Time < internal.KeepaliveMinPingTime { if kp.Time < internal.KeepaliveMinPingTime {
grpclog.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime) logger.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime)
kp.Time = internal.KeepaliveMinPingTime kp.Time = internal.KeepaliveMinPingTime
} }
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
@ -448,7 +471,7 @@ func WithStreamInterceptor(f StreamClientInterceptor) DialOption {
} }
// WithChainStreamInterceptor returns a DialOption that specifies the chained // WithChainStreamInterceptor returns a DialOption that specifies the chained
// interceptor for unary RPCs. The first interceptor will be the outer most, // interceptor for streaming RPCs. The first interceptor will be the outer most,
// while the last interceptor will be the inner most wrapper around the real call. // while the last interceptor will be the inner most wrapper around the real call.
// All interceptors added by this method will be chained, and the interceptor // All interceptors added by this method will be chained, and the interceptor
// defined by WithStreamInterceptor will always be prepended to the chain. // defined by WithStreamInterceptor will always be prepended to the chain.
@ -471,7 +494,10 @@ func WithAuthority(a string) DialOption {
// current ClientConn's parent. This function is used in nested channel creation // current ClientConn's parent. This function is used in nested channel creation
// (e.g. grpclb dial). // (e.g. grpclb dial).
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithChannelzParentID(id int64) DialOption { func WithChannelzParentID(id int64) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.channelzParentID = id o.channelzParentID = id
@ -497,7 +523,10 @@ func WithDisableServiceConfig() DialOption {
// 2. Resolver does not return a service config or if the resolver returns an // 2. Resolver does not return a service config or if the resolver returns an
// invalid service config. // invalid service config.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithDefaultServiceConfig(s string) DialOption { func WithDefaultServiceConfig(s string) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.defaultServiceConfigRawJSON = &s o.defaultServiceConfigRawJSON = &s
@ -513,7 +542,10 @@ func WithDefaultServiceConfig(s string) DialOption {
// default in the future. Until then, it may be enabled by setting the // default in the future. Until then, it may be enabled by setting the
// environment variable "GRPC_GO_RETRY" to "on". // environment variable "GRPC_GO_RETRY" to "on".
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithDisableRetry() DialOption { func WithDisableRetry() DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.disableRetry = true o.disableRetry = true
@ -531,7 +563,10 @@ func WithMaxHeaderListSize(s uint32) DialOption {
// WithDisableHealthCheck disables the LB channel health checking for all // WithDisableHealthCheck disables the LB channel health checking for all
// SubConns of this ClientConn. // SubConns of this ClientConn.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithDisableHealthCheck() DialOption { func WithDisableHealthCheck() DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.disableHealthCheck = true o.disableHealthCheck = true
@ -555,8 +590,8 @@ func defaultDialOptions() dialOptions {
copts: transport.ConnectOptions{ copts: transport.ConnectOptions{
WriteBufferSize: defaultWriteBufSize, WriteBufferSize: defaultWriteBufSize,
ReadBufferSize: defaultReadBufSize, ReadBufferSize: defaultReadBufSize,
UseProxy: true,
}, },
resolveNowBackoff: internalbackoff.DefaultExponential.Backoff,
} }
} }
@ -571,22 +606,15 @@ func withMinConnectDeadline(f func() time.Duration) DialOption {
}) })
} }
// withResolveNowBackoff specifies the function that clientconn uses to backoff
// between successive calls to resolver.ResolveNow().
//
// For testing purpose only.
func withResolveNowBackoff(f func(int) time.Duration) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.resolveNowBackoff = f
})
}
// WithResolvers allows a list of resolver implementations to be registered // WithResolvers allows a list of resolver implementations to be registered
// locally with the ClientConn without needing to be globally registered via // locally with the ClientConn without needing to be globally registered via
// resolver.Register. They will be matched against the scheme used for the // resolver.Register. They will be matched against the scheme used for the
// current Dial only, and will take precedence over the global registry. // current Dial only, and will take precedence over the global registry.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func WithResolvers(rs ...resolver.Builder) DialOption { func WithResolvers(rs ...resolver.Builder) DialOption {
return newFuncDialOption(func(o *dialOptions) { return newFuncDialOption(func(o *dialOptions) {
o.resolvers = append(o.resolvers, rs...) o.resolvers = append(o.resolvers, rs...)

View File

@ -16,6 +16,8 @@
* *
*/ */
//go:generate ./regenerate.sh
/* /*
Package grpc implements an RPC system called gRPC. Package grpc implements an RPC system called gRPC.

View File

@ -19,7 +19,10 @@
// Package encoding defines the interface for the compressor and codec, and // Package encoding defines the interface for the compressor and codec, and
// functions to register and retrieve compressors and codecs. // functions to register and retrieve compressors and codecs.
// //
// This package is EXPERIMENTAL. // Experimental
//
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
// later release.
package encoding package encoding
import ( import (
@ -46,10 +49,15 @@ type Compressor interface {
// coding header. The result must be static; the result cannot change // coding header. The result must be static; the result cannot change
// between calls. // between calls.
Name() string Name() string
// EXPERIMENTAL: if a Compressor implements // If a Compressor implements
// DecompressedSize(compressedBytes []byte) int, gRPC will call it // DecompressedSize(compressedBytes []byte) int, gRPC will call it
// to determine the size of the buffer allocated for the result of decompression. // to determine the size of the buffer allocated for the result of decompression.
// Return -1 to indicate unknown size. // Return -1 to indicate unknown size.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
} }
var registeredCompressor = make(map[string]Compressor) var registeredCompressor = make(map[string]Compressor)

View File

@ -21,8 +21,7 @@
package proto package proto
import ( import (
"math" "fmt"
"sync"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"
@ -38,73 +37,22 @@ func init() {
// codec is a Codec implementation with protobuf. It is the default codec for gRPC. // codec is a Codec implementation with protobuf. It is the default codec for gRPC.
type codec struct{} type codec struct{}
type cachedProtoBuffer struct {
lastMarshaledSize uint32
proto.Buffer
}
func capToMaxInt32(val int) uint32 {
if val > math.MaxInt32 {
return uint32(math.MaxInt32)
}
return uint32(val)
}
func marshal(v interface{}, cb *cachedProtoBuffer) ([]byte, error) {
protoMsg := v.(proto.Message)
newSlice := make([]byte, 0, cb.lastMarshaledSize)
cb.SetBuf(newSlice)
cb.Reset()
if err := cb.Marshal(protoMsg); err != nil {
return nil, err
}
out := cb.Bytes()
cb.lastMarshaledSize = capToMaxInt32(len(out))
return out, nil
}
func (codec) Marshal(v interface{}) ([]byte, error) { func (codec) Marshal(v interface{}) ([]byte, error) {
if pm, ok := v.(proto.Marshaler); ok { vv, ok := v.(proto.Message)
// object can marshal itself, no need for buffer if !ok {
return pm.Marshal() return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
} }
return proto.Marshal(vv)
cb := protoBufferPool.Get().(*cachedProtoBuffer)
out, err := marshal(v, cb)
// put back buffer and lose the ref to the slice
cb.SetBuf(nil)
protoBufferPool.Put(cb)
return out, err
} }
func (codec) Unmarshal(data []byte, v interface{}) error { func (codec) Unmarshal(data []byte, v interface{}) error {
protoMsg := v.(proto.Message) vv, ok := v.(proto.Message)
protoMsg.Reset() if !ok {
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
if pu, ok := protoMsg.(proto.Unmarshaler); ok {
// object can unmarshal itself, no need for buffer
return pu.Unmarshal(data)
} }
return proto.Unmarshal(data, vv)
cb := protoBufferPool.Get().(*cachedProtoBuffer)
cb.SetBuf(data)
err := cb.Unmarshal(protoMsg)
cb.SetBuf(nil)
protoBufferPool.Put(cb)
return err
} }
func (codec) Name() string { func (codec) Name() string {
return Name return Name
} }
var protoBufferPool = &sync.Pool{
New: func() interface{} {
return &cachedProtoBuffer{
Buffer: proto.Buffer{},
lastMarshaledSize: 16,
}
},
}

13
vendor/google.golang.org/grpc/go.mod generated vendored
View File

@ -3,14 +3,15 @@ module google.golang.org/grpc
go 1.11 go 1.11
require ( require (
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403
github.com/envoyproxy/protoc-gen-validate v0.1.0 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.1.1 github.com/golang/protobuf v1.4.2
github.com/golang/protobuf v1.3.2 github.com/google/go-cmp v0.5.0
github.com/google/go-cmp v0.2.0 github.com/google/uuid v1.1.2
golang.org/x/net v0.0.0-20190311183353-d8887717615a golang.org/x/net v0.0.0-20190311183353-d8887717615a
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/protobuf v1.25.0
) )

117
vendor/google.golang.org/grpc/grpclog/component.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpclog
import (
"fmt"
"google.golang.org/grpc/internal/grpclog"
)
// componentData records the settings for a component.
type componentData struct {
name string
}
var cache = map[string]*componentData{}
func (c *componentData) InfoDepth(depth int, args ...interface{}) {
args = append([]interface{}{"[" + string(c.name) + "]"}, args...)
grpclog.InfoDepth(depth+1, args...)
}
func (c *componentData) WarningDepth(depth int, args ...interface{}) {
args = append([]interface{}{"[" + string(c.name) + "]"}, args...)
grpclog.WarningDepth(depth+1, args...)
}
func (c *componentData) ErrorDepth(depth int, args ...interface{}) {
args = append([]interface{}{"[" + string(c.name) + "]"}, args...)
grpclog.ErrorDepth(depth+1, args...)
}
func (c *componentData) FatalDepth(depth int, args ...interface{}) {
args = append([]interface{}{"[" + string(c.name) + "]"}, args...)
grpclog.FatalDepth(depth+1, args...)
}
func (c *componentData) Info(args ...interface{}) {
c.InfoDepth(1, args...)
}
func (c *componentData) Warning(args ...interface{}) {
c.WarningDepth(1, args...)
}
func (c *componentData) Error(args ...interface{}) {
c.ErrorDepth(1, args...)
}
func (c *componentData) Fatal(args ...interface{}) {
c.FatalDepth(1, args...)
}
func (c *componentData) Infof(format string, args ...interface{}) {
c.InfoDepth(1, fmt.Sprintf(format, args...))
}
func (c *componentData) Warningf(format string, args ...interface{}) {
c.WarningDepth(1, fmt.Sprintf(format, args...))
}
func (c *componentData) Errorf(format string, args ...interface{}) {
c.ErrorDepth(1, fmt.Sprintf(format, args...))
}
func (c *componentData) Fatalf(format string, args ...interface{}) {
c.FatalDepth(1, fmt.Sprintf(format, args...))
}
func (c *componentData) Infoln(args ...interface{}) {
c.InfoDepth(1, args...)
}
func (c *componentData) Warningln(args ...interface{}) {
c.WarningDepth(1, args...)
}
func (c *componentData) Errorln(args ...interface{}) {
c.ErrorDepth(1, args...)
}
func (c *componentData) Fatalln(args ...interface{}) {
c.FatalDepth(1, args...)
}
func (c *componentData) V(l int) bool {
return V(l)
}
// Component creates a new component and returns it for logging. If a component
// with the name already exists, nothing will be created and it will be
// returned. SetLoggerV2 will panic if it is called with a logger created by
// Component.
func Component(componentName string) DepthLoggerV2 {
if cData, ok := cache[componentName]; ok {
return cData
}
c := &componentData{componentName}
cache[componentName] = c
return c
}

View File

@ -26,64 +26,70 @@
// verbosity level can be set by GRPC_GO_LOG_VERBOSITY_LEVEL. // verbosity level can be set by GRPC_GO_LOG_VERBOSITY_LEVEL.
package grpclog // import "google.golang.org/grpc/grpclog" package grpclog // import "google.golang.org/grpc/grpclog"
import "os" import (
"os"
var logger = newLoggerV2() "google.golang.org/grpc/internal/grpclog"
)
func init() {
SetLoggerV2(newLoggerV2())
}
// V reports whether verbosity level l is at least the requested verbose level. // V reports whether verbosity level l is at least the requested verbose level.
func V(l int) bool { func V(l int) bool {
return logger.V(l) return grpclog.Logger.V(l)
} }
// Info logs to the INFO log. // Info logs to the INFO log.
func Info(args ...interface{}) { func Info(args ...interface{}) {
logger.Info(args...) grpclog.Logger.Info(args...)
} }
// Infof logs to the INFO log. Arguments are handled in the manner of fmt.Printf. // Infof logs to the INFO log. Arguments are handled in the manner of fmt.Printf.
func Infof(format string, args ...interface{}) { func Infof(format string, args ...interface{}) {
logger.Infof(format, args...) grpclog.Logger.Infof(format, args...)
} }
// Infoln logs to the INFO log. Arguments are handled in the manner of fmt.Println. // Infoln logs to the INFO log. Arguments are handled in the manner of fmt.Println.
func Infoln(args ...interface{}) { func Infoln(args ...interface{}) {
logger.Infoln(args...) grpclog.Logger.Infoln(args...)
} }
// Warning logs to the WARNING log. // Warning logs to the WARNING log.
func Warning(args ...interface{}) { func Warning(args ...interface{}) {
logger.Warning(args...) grpclog.Logger.Warning(args...)
} }
// Warningf logs to the WARNING log. Arguments are handled in the manner of fmt.Printf. // Warningf logs to the WARNING log. Arguments are handled in the manner of fmt.Printf.
func Warningf(format string, args ...interface{}) { func Warningf(format string, args ...interface{}) {
logger.Warningf(format, args...) grpclog.Logger.Warningf(format, args...)
} }
// Warningln logs to the WARNING log. Arguments are handled in the manner of fmt.Println. // Warningln logs to the WARNING log. Arguments are handled in the manner of fmt.Println.
func Warningln(args ...interface{}) { func Warningln(args ...interface{}) {
logger.Warningln(args...) grpclog.Logger.Warningln(args...)
} }
// Error logs to the ERROR log. // Error logs to the ERROR log.
func Error(args ...interface{}) { func Error(args ...interface{}) {
logger.Error(args...) grpclog.Logger.Error(args...)
} }
// Errorf logs to the ERROR log. Arguments are handled in the manner of fmt.Printf. // Errorf logs to the ERROR log. Arguments are handled in the manner of fmt.Printf.
func Errorf(format string, args ...interface{}) { func Errorf(format string, args ...interface{}) {
logger.Errorf(format, args...) grpclog.Logger.Errorf(format, args...)
} }
// Errorln logs to the ERROR log. Arguments are handled in the manner of fmt.Println. // Errorln logs to the ERROR log. Arguments are handled in the manner of fmt.Println.
func Errorln(args ...interface{}) { func Errorln(args ...interface{}) {
logger.Errorln(args...) grpclog.Logger.Errorln(args...)
} }
// Fatal logs to the FATAL log. Arguments are handled in the manner of fmt.Print. // Fatal logs to the FATAL log. Arguments are handled in the manner of fmt.Print.
// It calls os.Exit() with exit code 1. // It calls os.Exit() with exit code 1.
func Fatal(args ...interface{}) { func Fatal(args ...interface{}) {
logger.Fatal(args...) grpclog.Logger.Fatal(args...)
// Make sure fatal logs will exit. // Make sure fatal logs will exit.
os.Exit(1) os.Exit(1)
} }
@ -91,7 +97,7 @@ func Fatal(args ...interface{}) {
// Fatalf logs to the FATAL log. Arguments are handled in the manner of fmt.Printf. // Fatalf logs to the FATAL log. Arguments are handled in the manner of fmt.Printf.
// It calls os.Exit() with exit code 1. // It calls os.Exit() with exit code 1.
func Fatalf(format string, args ...interface{}) { func Fatalf(format string, args ...interface{}) {
logger.Fatalf(format, args...) grpclog.Logger.Fatalf(format, args...)
// Make sure fatal logs will exit. // Make sure fatal logs will exit.
os.Exit(1) os.Exit(1)
} }
@ -99,7 +105,7 @@ func Fatalf(format string, args ...interface{}) {
// Fatalln logs to the FATAL log. Arguments are handled in the manner of fmt.Println. // Fatalln logs to the FATAL log. Arguments are handled in the manner of fmt.Println.
// It calle os.Exit()) with exit code 1. // It calle os.Exit()) with exit code 1.
func Fatalln(args ...interface{}) { func Fatalln(args ...interface{}) {
logger.Fatalln(args...) grpclog.Logger.Fatalln(args...)
// Make sure fatal logs will exit. // Make sure fatal logs will exit.
os.Exit(1) os.Exit(1)
} }
@ -108,19 +114,19 @@ func Fatalln(args ...interface{}) {
// //
// Deprecated: use Info. // Deprecated: use Info.
func Print(args ...interface{}) { func Print(args ...interface{}) {
logger.Info(args...) grpclog.Logger.Info(args...)
} }
// Printf prints to the logger. Arguments are handled in the manner of fmt.Printf. // Printf prints to the logger. Arguments are handled in the manner of fmt.Printf.
// //
// Deprecated: use Infof. // Deprecated: use Infof.
func Printf(format string, args ...interface{}) { func Printf(format string, args ...interface{}) {
logger.Infof(format, args...) grpclog.Logger.Infof(format, args...)
} }
// Println prints to the logger. Arguments are handled in the manner of fmt.Println. // Println prints to the logger. Arguments are handled in the manner of fmt.Println.
// //
// Deprecated: use Infoln. // Deprecated: use Infoln.
func Println(args ...interface{}) { func Println(args ...interface{}) {
logger.Infoln(args...) grpclog.Logger.Infoln(args...)
} }

View File

@ -18,6 +18,8 @@
package grpclog package grpclog
import "google.golang.org/grpc/internal/grpclog"
// Logger mimics golang's standard Logger as an interface. // Logger mimics golang's standard Logger as an interface.
// //
// Deprecated: use LoggerV2. // Deprecated: use LoggerV2.
@ -35,7 +37,7 @@ type Logger interface {
// //
// Deprecated: use SetLoggerV2. // Deprecated: use SetLoggerV2.
func SetLogger(l Logger) { func SetLogger(l Logger) {
logger = &loggerWrapper{Logger: l} grpclog.Logger = &loggerWrapper{Logger: l}
} }
// loggerWrapper wraps Logger into a LoggerV2. // loggerWrapper wraps Logger into a LoggerV2.

View File

@ -24,6 +24,8 @@ import (
"log" "log"
"os" "os"
"strconv" "strconv"
"google.golang.org/grpc/internal/grpclog"
) )
// LoggerV2 does underlying logging work for grpclog. // LoggerV2 does underlying logging work for grpclog.
@ -65,7 +67,11 @@ type LoggerV2 interface {
// SetLoggerV2 sets logger that is used in grpc to a V2 logger. // SetLoggerV2 sets logger that is used in grpc to a V2 logger.
// Not mutex-protected, should be called before any gRPC functions. // Not mutex-protected, should be called before any gRPC functions.
func SetLoggerV2(l LoggerV2) { func SetLoggerV2(l LoggerV2) {
logger = l if _, ok := l.(*componentData); ok {
panic("cannot use component logger as grpclog logger")
}
grpclog.Logger = l
grpclog.DepthLogger, _ = l.(grpclog.DepthLoggerV2)
} }
const ( const (
@ -193,3 +199,23 @@ func (g *loggerT) Fatalf(format string, args ...interface{}) {
func (g *loggerT) V(l int) bool { func (g *loggerT) V(l int) bool {
return l <= g.v return l <= g.v
} }
// DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
// DepthLoggerV2, the below functions will be called with the appropriate stack
// depth set for trivial functions the logger may ignore.
//
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type DepthLoggerV2 interface {
LoggerV2
// InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Print.
InfoDepth(depth int, args ...interface{})
// WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Print.
WarningDepth(depth int, args ...interface{})
// ErrorDetph logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Print.
ErrorDepth(depth int, args ...interface{})
// FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Print.
FatalDepth(depth int, args ...interface{})
}

View File

@ -1,28 +1,46 @@
// Copyright 2015 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The canonical version of this proto can be found at
// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0
// protoc v3.14.0
// source: grpc/health/v1/health.proto // source: grpc/health/v1/health.proto
package grpc_health_v1 package grpc_health_v1
import ( import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto" proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
codes "google.golang.org/grpc/codes" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
status "google.golang.org/grpc/status" reflect "reflect"
math "math" sync "sync"
) )
// Reference imports to suppress errors if they are not otherwise used. const (
var _ = proto.Marshal // Verify that this generated code is sufficiently up-to-date.
var _ = fmt.Errorf _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
var _ = math.Inf // Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion that a sufficiently up-to-date version
// is compatible with the proto package it is being compiled against. // of the legacy proto package is being used.
// A compilation error at this line likely means your copy of the const _ = proto.ProtoPackageIsVersion4
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type HealthCheckResponse_ServingStatus int32 type HealthCheckResponse_ServingStatus int32
@ -30,314 +48,266 @@ const (
HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0
HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1
HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2
HealthCheckResponse_SERVICE_UNKNOWN HealthCheckResponse_ServingStatus = 3 HealthCheckResponse_SERVICE_UNKNOWN HealthCheckResponse_ServingStatus = 3 // Used only by the Watch method.
) )
var HealthCheckResponse_ServingStatus_name = map[int32]string{ // Enum value maps for HealthCheckResponse_ServingStatus.
var (
HealthCheckResponse_ServingStatus_name = map[int32]string{
0: "UNKNOWN", 0: "UNKNOWN",
1: "SERVING", 1: "SERVING",
2: "NOT_SERVING", 2: "NOT_SERVING",
3: "SERVICE_UNKNOWN", 3: "SERVICE_UNKNOWN",
} }
HealthCheckResponse_ServingStatus_value = map[string]int32{
var HealthCheckResponse_ServingStatus_value = map[string]int32{
"UNKNOWN": 0, "UNKNOWN": 0,
"SERVING": 1, "SERVING": 1,
"NOT_SERVING": 2, "NOT_SERVING": 2,
"SERVICE_UNKNOWN": 3, "SERVICE_UNKNOWN": 3,
}
)
func (x HealthCheckResponse_ServingStatus) Enum() *HealthCheckResponse_ServingStatus {
p := new(HealthCheckResponse_ServingStatus)
*p = x
return p
} }
func (x HealthCheckResponse_ServingStatus) String() string { func (x HealthCheckResponse_ServingStatus) String() string {
return proto.EnumName(HealthCheckResponse_ServingStatus_name, int32(x)) return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
} }
func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor {
return file_grpc_health_v1_health_proto_enumTypes[0].Descriptor()
}
func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType {
return &file_grpc_health_v1_health_proto_enumTypes[0]
}
func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use HealthCheckResponse_ServingStatus.Descriptor instead.
func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_e265fd9d4e077217, []int{1, 0} return file_grpc_health_v1_health_proto_rawDescGZIP(), []int{1, 0}
} }
type HealthCheckRequest struct { type HealthCheckRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *HealthCheckRequest) Reset() { *m = HealthCheckRequest{} } func (x *HealthCheckRequest) Reset() {
func (m *HealthCheckRequest) String() string { return proto.CompactTextString(m) } *x = HealthCheckRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_health_v1_health_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HealthCheckRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HealthCheckRequest) ProtoMessage() {} func (*HealthCheckRequest) ProtoMessage() {}
func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message {
mi := &file_grpc_health_v1_health_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead.
func (*HealthCheckRequest) Descriptor() ([]byte, []int) { func (*HealthCheckRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_e265fd9d4e077217, []int{0} return file_grpc_health_v1_health_proto_rawDescGZIP(), []int{0}
} }
func (m *HealthCheckRequest) XXX_Unmarshal(b []byte) error { func (x *HealthCheckRequest) GetService() string {
return xxx_messageInfo_HealthCheckRequest.Unmarshal(m, b) if x != nil {
} return x.Service
func (m *HealthCheckRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HealthCheckRequest.Marshal(b, m, deterministic)
}
func (m *HealthCheckRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_HealthCheckRequest.Merge(m, src)
}
func (m *HealthCheckRequest) XXX_Size() int {
return xxx_messageInfo_HealthCheckRequest.Size(m)
}
func (m *HealthCheckRequest) XXX_DiscardUnknown() {
xxx_messageInfo_HealthCheckRequest.DiscardUnknown(m)
}
var xxx_messageInfo_HealthCheckRequest proto.InternalMessageInfo
func (m *HealthCheckRequest) GetService() string {
if m != nil {
return m.Service
} }
return "" return ""
} }
type HealthCheckResponse struct { type HealthCheckResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=grpc.health.v1.HealthCheckResponse_ServingStatus" json:"status,omitempty"` Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=grpc.health.v1.HealthCheckResponse_ServingStatus" json:"status,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
} }
func (m *HealthCheckResponse) Reset() { *m = HealthCheckResponse{} } func (x *HealthCheckResponse) Reset() {
func (m *HealthCheckResponse) String() string { return proto.CompactTextString(m) } *x = HealthCheckResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_grpc_health_v1_health_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *HealthCheckResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*HealthCheckResponse) ProtoMessage() {} func (*HealthCheckResponse) ProtoMessage() {}
func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message {
mi := &file_grpc_health_v1_health_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead.
func (*HealthCheckResponse) Descriptor() ([]byte, []int) { func (*HealthCheckResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_e265fd9d4e077217, []int{1} return file_grpc_health_v1_health_proto_rawDescGZIP(), []int{1}
} }
func (m *HealthCheckResponse) XXX_Unmarshal(b []byte) error { func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus {
return xxx_messageInfo_HealthCheckResponse.Unmarshal(m, b) if x != nil {
} return x.Status
func (m *HealthCheckResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HealthCheckResponse.Marshal(b, m, deterministic)
}
func (m *HealthCheckResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_HealthCheckResponse.Merge(m, src)
}
func (m *HealthCheckResponse) XXX_Size() int {
return xxx_messageInfo_HealthCheckResponse.Size(m)
}
func (m *HealthCheckResponse) XXX_DiscardUnknown() {
xxx_messageInfo_HealthCheckResponse.DiscardUnknown(m)
}
var xxx_messageInfo_HealthCheckResponse proto.InternalMessageInfo
func (m *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus {
if m != nil {
return m.Status
} }
return HealthCheckResponse_UNKNOWN return HealthCheckResponse_UNKNOWN
} }
func init() { var File_grpc_health_v1_health_proto protoreflect.FileDescriptor
proto.RegisterEnum("grpc.health.v1.HealthCheckResponse_ServingStatus", HealthCheckResponse_ServingStatus_name, HealthCheckResponse_ServingStatus_value)
proto.RegisterType((*HealthCheckRequest)(nil), "grpc.health.v1.HealthCheckRequest") var file_grpc_health_v1_health_proto_rawDesc = []byte{
proto.RegisterType((*HealthCheckResponse)(nil), "grpc.health.v1.HealthCheckResponse") 0x0a, 0x1b, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2f, 0x76, 0x31,
0x2f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x67,
0x72, 0x70, 0x63, 0x2e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x22, 0x2e, 0x0a,
0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xb1, 0x01,
0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x31, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x68, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69,
0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75,
0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b,
0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e,
0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f,
0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x03, 0x32, 0xae, 0x01, 0x0a, 0x06, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x50, 0x0a, 0x05,
0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x68, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65,
0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x72, 0x70, 0x63,
0x2e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74,
0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52,
0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x68,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43,
0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x72,
0x70, 0x63, 0x2e, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x61,
0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x30, 0x01, 0x42, 0x61, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x68, 0x65,
0x61, 0x6c, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x50,
0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67,
0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x68,
0x65, 0x61, 0x6c, 0x74, 0x68, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74,
0x68, 0x5f, 0x76, 0x31, 0xaa, 0x02, 0x0e, 0x47, 0x72, 0x70, 0x63, 0x2e, 0x48, 0x65, 0x61, 0x6c,
0x74, 0x68, 0x2e, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
func init() { proto.RegisterFile("grpc/health/v1/health.proto", fileDescriptor_e265fd9d4e077217) } var (
file_grpc_health_v1_health_proto_rawDescOnce sync.Once
file_grpc_health_v1_health_proto_rawDescData = file_grpc_health_v1_health_proto_rawDesc
)
var fileDescriptor_e265fd9d4e077217 = []byte{ func file_grpc_health_v1_health_proto_rawDescGZIP() []byte {
// 297 bytes of a gzipped FileDescriptorProto file_grpc_health_v1_health_proto_rawDescOnce.Do(func() {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4e, 0x2f, 0x2a, 0x48, file_grpc_health_v1_health_proto_rawDescData = protoimpl.X.CompressGZIP(file_grpc_health_v1_health_proto_rawDescData)
0xd6, 0xcf, 0x48, 0x4d, 0xcc, 0x29, 0xc9, 0xd0, 0x2f, 0x33, 0x84, 0xb2, 0xf4, 0x0a, 0x8a, 0xf2, })
0x4b, 0xf2, 0x85, 0xf8, 0x40, 0x92, 0x7a, 0x50, 0xa1, 0x32, 0x43, 0x25, 0x3d, 0x2e, 0x21, 0x0f, return file_grpc_health_v1_health_proto_rawDescData
0x30, 0xc7, 0x39, 0x23, 0x35, 0x39, 0x3b, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0x48, 0x82,
0x8b, 0xbd, 0x38, 0xb5, 0xa8, 0x2c, 0x33, 0x39, 0x55, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08,
0xc6, 0x55, 0xda, 0xc8, 0xc8, 0x25, 0x8c, 0xa2, 0xa1, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0xc8,
0x93, 0x8b, 0xad, 0xb8, 0x24, 0xb1, 0xa4, 0xb4, 0x18, 0xac, 0x81, 0xcf, 0xc8, 0x50, 0x0f, 0xd5,
0x22, 0x3d, 0x2c, 0x9a, 0xf4, 0x82, 0x41, 0x86, 0xe6, 0xa5, 0x07, 0x83, 0x35, 0x06, 0x41, 0x0d,
0x50, 0xf2, 0xe7, 0xe2, 0x45, 0x91, 0x10, 0xe2, 0xe6, 0x62, 0x0f, 0xf5, 0xf3, 0xf6, 0xf3, 0x0f,
0xf7, 0x13, 0x60, 0x00, 0x71, 0x82, 0x5d, 0x83, 0xc2, 0x3c, 0xfd, 0xdc, 0x05, 0x18, 0x85, 0xf8,
0xb9, 0xb8, 0xfd, 0xfc, 0x43, 0xe2, 0x61, 0x02, 0x4c, 0x42, 0xc2, 0x5c, 0xfc, 0x60, 0x8e, 0xb3,
0x6b, 0x3c, 0x4c, 0x0b, 0xb3, 0xd1, 0x3a, 0x46, 0x2e, 0x36, 0x88, 0xf5, 0x42, 0x01, 0x5c, 0xac,
0x60, 0x27, 0x08, 0x29, 0xe1, 0x75, 0x1f, 0x38, 0x14, 0xa4, 0x94, 0x89, 0xf0, 0x83, 0x50, 0x10,
0x17, 0x6b, 0x78, 0x62, 0x49, 0x72, 0x06, 0xd5, 0x4c, 0x34, 0x60, 0x74, 0x4a, 0xe4, 0x12, 0xcc,
0xcc, 0x47, 0x53, 0xea, 0xc4, 0x0d, 0x51, 0x1b, 0x00, 0x8a, 0xc6, 0x00, 0xc6, 0x28, 0x9d, 0xf4,
0xfc, 0xfc, 0xf4, 0x9c, 0x54, 0xbd, 0xf4, 0xfc, 0x9c, 0xc4, 0xbc, 0x74, 0xbd, 0xfc, 0xa2, 0x74,
0x7d, 0xe4, 0x78, 0x07, 0xb1, 0xe3, 0x21, 0xec, 0xf8, 0x32, 0xc3, 0x55, 0x4c, 0x7c, 0xee, 0x20,
0xd3, 0x20, 0x46, 0xe8, 0x85, 0x19, 0x26, 0xb1, 0x81, 0x93, 0x83, 0x31, 0x20, 0x00, 0x00, 0xff,
0xff, 0x12, 0x7d, 0x96, 0xcb, 0x2d, 0x02, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. var file_grpc_health_v1_health_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var _ context.Context var file_grpc_health_v1_health_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var _ grpc.ClientConn var file_grpc_health_v1_health_proto_goTypes = []interface{}{
(HealthCheckResponse_ServingStatus)(0), // 0: grpc.health.v1.HealthCheckResponse.ServingStatus
// This is a compile-time assertion to ensure that this generated file (*HealthCheckRequest)(nil), // 1: grpc.health.v1.HealthCheckRequest
// is compatible with the grpc package it is being compiled against. (*HealthCheckResponse)(nil), // 2: grpc.health.v1.HealthCheckResponse
const _ = grpc.SupportPackageIsVersion4 }
var file_grpc_health_v1_health_proto_depIdxs = []int32{
// HealthClient is the client API for Health service. 0, // 0: grpc.health.v1.HealthCheckResponse.status:type_name -> grpc.health.v1.HealthCheckResponse.ServingStatus
// 1, // 1: grpc.health.v1.Health.Check:input_type -> grpc.health.v1.HealthCheckRequest
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 1, // 2: grpc.health.v1.Health.Watch:input_type -> grpc.health.v1.HealthCheckRequest
type HealthClient interface { 2, // 3: grpc.health.v1.Health.Check:output_type -> grpc.health.v1.HealthCheckResponse
// If the requested service is unknown, the call will fail with status 2, // 4: grpc.health.v1.Health.Watch:output_type -> grpc.health.v1.HealthCheckResponse
// NOT_FOUND. 3, // [3:5] is the sub-list for method output_type
Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) 1, // [1:3] is the sub-list for method input_type
// Performs a watch for the serving status of the requested service. 1, // [1:1] is the sub-list for extension type_name
// The server will immediately send back a message indicating the current 1, // [1:1] is the sub-list for extension extendee
// serving status. It will then subsequently send a new message whenever 0, // [0:1] is the sub-list for field type_name
// the service's serving status changes.
//
// If the requested service is unknown when the call is received, the
// server will send a message setting the serving status to
// SERVICE_UNKNOWN but will *not* terminate the call. If at some
// future point, the serving status of the service becomes known, the
// server will send a new message with the service's serving status.
//
// If the call terminates with status UNIMPLEMENTED, then clients
// should assume this method is not supported and should not retry the
// call. If the call terminates with any other status (including OK),
// clients should retry the call with appropriate exponential backoff.
Watch(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (Health_WatchClient, error)
} }
type healthClient struct { func init() { file_grpc_health_v1_health_proto_init() }
cc *grpc.ClientConn func file_grpc_health_v1_health_proto_init() {
} if File_grpc_health_v1_health_proto != nil {
return
func NewHealthClient(cc *grpc.ClientConn) HealthClient {
return &healthClient{cc}
}
func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) {
out := new(HealthCheckResponse)
err := c.cc.Invoke(ctx, "/grpc.health.v1.Health/Check", in, out, opts...)
if err != nil {
return nil, err
} }
return out, nil if !protoimpl.UnsafeEnabled {
} file_grpc_health_v1_health_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*HealthCheckRequest); i {
func (c *healthClient) Watch(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (Health_WatchClient, error) { case 0:
stream, err := c.cc.NewStream(ctx, &_Health_serviceDesc.Streams[0], "/grpc.health.v1.Health/Watch", opts...) return &v.state
if err != nil { case 1:
return nil, err return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
} }
x := &healthWatchClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
} }
if err := x.ClientStream.CloseSend(); err != nil { file_grpc_health_v1_health_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
return nil, err switch v := v.(*HealthCheckResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
} }
return x, nil
}
type Health_WatchClient interface {
Recv() (*HealthCheckResponse, error)
grpc.ClientStream
}
type healthWatchClient struct {
grpc.ClientStream
}
func (x *healthWatchClient) Recv() (*HealthCheckResponse, error) {
m := new(HealthCheckResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
} }
return m, nil
}
// HealthServer is the server API for Health service.
type HealthServer interface {
// If the requested service is unknown, the call will fail with status
// NOT_FOUND.
Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error)
// Performs a watch for the serving status of the requested service.
// The server will immediately send back a message indicating the current
// serving status. It will then subsequently send a new message whenever
// the service's serving status changes.
//
// If the requested service is unknown when the call is received, the
// server will send a message setting the serving status to
// SERVICE_UNKNOWN but will *not* terminate the call. If at some
// future point, the serving status of the service becomes known, the
// server will send a new message with the service's serving status.
//
// If the call terminates with status UNIMPLEMENTED, then clients
// should assume this method is not supported and should not retry the
// call. If the call terminates with any other status (including OK),
// clients should retry the call with appropriate exponential backoff.
Watch(*HealthCheckRequest, Health_WatchServer) error
}
// UnimplementedHealthServer can be embedded to have forward compatible implementations.
type UnimplementedHealthServer struct {
}
func (*UnimplementedHealthServer) Check(ctx context.Context, req *HealthCheckRequest) (*HealthCheckResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Check not implemented")
}
func (*UnimplementedHealthServer) Watch(req *HealthCheckRequest, srv Health_WatchServer) error {
return status.Errorf(codes.Unimplemented, "method Watch not implemented")
}
func RegisterHealthServer(s *grpc.Server, srv HealthServer) {
s.RegisterService(&_Health_serviceDesc, srv)
}
func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthCheckRequest)
if err := dec(in); err != nil {
return nil, err
} }
if interceptor == nil { type x struct{}
return srv.(HealthServer).Check(ctx, in) out := protoimpl.TypeBuilder{
} File: protoimpl.DescBuilder{
info := &grpc.UnaryServerInfo{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
Server: srv, RawDescriptor: file_grpc_health_v1_health_proto_rawDesc,
FullMethod: "/grpc.health.v1.Health/Check", NumEnums: 1,
} NumMessages: 2,
handler := func(ctx context.Context, req interface{}) (interface{}, error) { NumExtensions: 0,
return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest)) NumServices: 1,
}
return interceptor(ctx, in, info, handler)
}
func _Health_Watch_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(HealthCheckRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(HealthServer).Watch(m, &healthWatchServer{stream})
}
type Health_WatchServer interface {
Send(*HealthCheckResponse) error
grpc.ServerStream
}
type healthWatchServer struct {
grpc.ServerStream
}
func (x *healthWatchServer) Send(m *HealthCheckResponse) error {
return x.ServerStream.SendMsg(m)
}
var _Health_serviceDesc = grpc.ServiceDesc{
ServiceName: "grpc.health.v1.Health",
HandlerType: (*HealthServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Check",
Handler: _Health_Check_Handler,
}, },
}, GoTypes: file_grpc_health_v1_health_proto_goTypes,
Streams: []grpc.StreamDesc{ DependencyIndexes: file_grpc_health_v1_health_proto_depIdxs,
{ EnumInfos: file_grpc_health_v1_health_proto_enumTypes,
StreamName: "Watch", MessageInfos: file_grpc_health_v1_health_proto_msgTypes,
Handler: _Health_Watch_Handler, }.Build()
ServerStreams: true, File_grpc_health_v1_health_proto = out.File
}, file_grpc_health_v1_health_proto_rawDesc = nil
}, file_grpc_health_v1_health_proto_goTypes = nil
Metadata: "grpc/health/v1/health.proto", file_grpc_health_v1_health_proto_depIdxs = nil
} }

View File

@ -0,0 +1,201 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.1.0
// - protoc v3.14.0
// source: grpc/health/v1/health.proto
package grpc_health_v1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// HealthClient is the client API for Health service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type HealthClient interface {
// If the requested service is unknown, the call will fail with status
// NOT_FOUND.
Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error)
// Performs a watch for the serving status of the requested service.
// The server will immediately send back a message indicating the current
// serving status. It will then subsequently send a new message whenever
// the service's serving status changes.
//
// If the requested service is unknown when the call is received, the
// server will send a message setting the serving status to
// SERVICE_UNKNOWN but will *not* terminate the call. If at some
// future point, the serving status of the service becomes known, the
// server will send a new message with the service's serving status.
//
// If the call terminates with status UNIMPLEMENTED, then clients
// should assume this method is not supported and should not retry the
// call. If the call terminates with any other status (including OK),
// clients should retry the call with appropriate exponential backoff.
Watch(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (Health_WatchClient, error)
}
type healthClient struct {
cc grpc.ClientConnInterface
}
func NewHealthClient(cc grpc.ClientConnInterface) HealthClient {
return &healthClient{cc}
}
func (c *healthClient) Check(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) {
out := new(HealthCheckResponse)
err := c.cc.Invoke(ctx, "/grpc.health.v1.Health/Check", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *healthClient) Watch(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (Health_WatchClient, error) {
stream, err := c.cc.NewStream(ctx, &Health_ServiceDesc.Streams[0], "/grpc.health.v1.Health/Watch", opts...)
if err != nil {
return nil, err
}
x := &healthWatchClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Health_WatchClient interface {
Recv() (*HealthCheckResponse, error)
grpc.ClientStream
}
type healthWatchClient struct {
grpc.ClientStream
}
func (x *healthWatchClient) Recv() (*HealthCheckResponse, error) {
m := new(HealthCheckResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// HealthServer is the server API for Health service.
// All implementations should embed UnimplementedHealthServer
// for forward compatibility
type HealthServer interface {
// If the requested service is unknown, the call will fail with status
// NOT_FOUND.
Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error)
// Performs a watch for the serving status of the requested service.
// The server will immediately send back a message indicating the current
// serving status. It will then subsequently send a new message whenever
// the service's serving status changes.
//
// If the requested service is unknown when the call is received, the
// server will send a message setting the serving status to
// SERVICE_UNKNOWN but will *not* terminate the call. If at some
// future point, the serving status of the service becomes known, the
// server will send a new message with the service's serving status.
//
// If the call terminates with status UNIMPLEMENTED, then clients
// should assume this method is not supported and should not retry the
// call. If the call terminates with any other status (including OK),
// clients should retry the call with appropriate exponential backoff.
Watch(*HealthCheckRequest, Health_WatchServer) error
}
// UnimplementedHealthServer should be embedded to have forward compatible implementations.
type UnimplementedHealthServer struct {
}
func (UnimplementedHealthServer) Check(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Check not implemented")
}
func (UnimplementedHealthServer) Watch(*HealthCheckRequest, Health_WatchServer) error {
return status.Errorf(codes.Unimplemented, "method Watch not implemented")
}
// UnsafeHealthServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to HealthServer will
// result in compilation errors.
type UnsafeHealthServer interface {
mustEmbedUnimplementedHealthServer()
}
func RegisterHealthServer(s grpc.ServiceRegistrar, srv HealthServer) {
s.RegisterService(&Health_ServiceDesc, srv)
}
func _Health_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HealthCheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(HealthServer).Check(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/grpc.health.v1.Health/Check",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(HealthServer).Check(ctx, req.(*HealthCheckRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Health_Watch_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(HealthCheckRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(HealthServer).Watch(m, &healthWatchServer{stream})
}
type Health_WatchServer interface {
Send(*HealthCheckResponse) error
grpc.ServerStream
}
type healthWatchServer struct {
grpc.ServerStream
}
func (x *healthWatchServer) Send(m *HealthCheckResponse) error {
return x.ServerStream.SendMsg(m)
}
// Health_ServiceDesc is the grpc.ServiceDesc for Health service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Health_ServiceDesc = grpc.ServiceDesc{
ServiceName: "grpc.health.v1.Health",
HandlerType: (*HealthServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Check",
Handler: _Health_Check_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Watch",
Handler: _Health_Watch_Handler,
ServerStreams: true,
},
},
Metadata: "grpc/health/v1/health.proto",
}

View File

@ -1,6 +1,6 @@
/* /*
* *
* Copyright 2017 gRPC authors. * Copyright 2020 gRPC authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,29 +16,8 @@
* *
*/ */
// This file contains wrappers for grpclog functions. package health
// The transport package only logs to verbose level 2 by default.
package transport
import "google.golang.org/grpc/grpclog" import "google.golang.org/grpc/grpclog"
const logLevel = 2 var logger = grpclog.Component("health_service")
func infof(format string, args ...interface{}) {
if grpclog.V(logLevel) {
grpclog.Infof(format, args...)
}
}
func warningf(format string, args ...interface{}) {
if grpclog.V(logLevel) {
grpclog.Warningf(format, args...)
}
}
func errorf(format string, args ...interface{}) {
if grpclog.V(logLevel) {
grpclog.Errorf(format, args...)
}
}

View File

@ -16,8 +16,6 @@
* *
*/ */
//go:generate ./regenerate.sh
// Package health provides a service that exposes server's health and it must be // Package health provides a service that exposes server's health and it must be
// imported to enable support for client-side health checks. // imported to enable support for client-side health checks.
package health package health
@ -27,7 +25,6 @@ import (
"sync" "sync"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
healthgrpc "google.golang.org/grpc/health/grpc_health_v1" healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
healthpb "google.golang.org/grpc/health/grpc_health_v1" healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
@ -35,6 +32,7 @@ import (
// Server implements `service Health`. // Server implements `service Health`.
type Server struct { type Server struct {
healthgrpc.UnimplementedHealthServer
mu sync.RWMutex mu sync.RWMutex
// If shutdown is true, it's expected all serving status is NOT_SERVING, and // If shutdown is true, it's expected all serving status is NOT_SERVING, and
// will stay in NOT_SERVING. // will stay in NOT_SERVING.
@ -115,7 +113,7 @@ func (s *Server) SetServingStatus(service string, servingStatus healthpb.HealthC
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.shutdown { if s.shutdown {
grpclog.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus) logger.Infof("health: status changing for %s to %v is ignored because health service is shutdown", service, servingStatus)
return return
} }

View File

@ -25,17 +25,41 @@ import (
// UnaryInvoker is called by UnaryClientInterceptor to complete RPCs. // UnaryInvoker is called by UnaryClientInterceptor to complete RPCs.
type UnaryInvoker func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error type UnaryInvoker func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, opts ...CallOption) error
// UnaryClientInterceptor intercepts the execution of a unary RPC on the client. invoker is the handler to complete the RPC // UnaryClientInterceptor intercepts the execution of a unary RPC on the client.
// and it is the responsibility of the interceptor to call it. // Unary interceptors can be specified as a DialOption, using
// This is an EXPERIMENTAL API. // WithUnaryInterceptor() or WithChainUnaryInterceptor(), when creating a
// ClientConn. When a unary interceptor(s) is set on a ClientConn, gRPC
// delegates all unary RPC invocations to the interceptor, and it is the
// responsibility of the interceptor to call invoker to complete the processing
// of the RPC.
//
// method is the RPC name. req and reply are the corresponding request and
// response messages. cc is the ClientConn on which the RPC was invoked. invoker
// is the handler to complete the RPC and it is the responsibility of the
// interceptor to call it. opts contain all applicable call options, including
// defaults from the ClientConn as well as per-call options.
//
// The returned error must be compatible with the status package.
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
// Streamer is called by StreamClientInterceptor to create a ClientStream. // Streamer is called by StreamClientInterceptor to create a ClientStream.
type Streamer func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) type Streamer func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error)
// StreamClientInterceptor intercepts the creation of ClientStream. It may return a custom ClientStream to intercept all I/O // StreamClientInterceptor intercepts the creation of a ClientStream. Stream
// operations. streamer is the handler to create a ClientStream and it is the responsibility of the interceptor to call it. // interceptors can be specified as a DialOption, using WithStreamInterceptor()
// This is an EXPERIMENTAL API. // or WithChainStreamInterceptor(), when creating a ClientConn. When a stream
// interceptor(s) is set on the ClientConn, gRPC delegates all stream creations
// to the interceptor, and it is the responsibility of the interceptor to call
// streamer.
//
// desc contains a description of the stream. cc is the ClientConn on which the
// RPC was invoked. streamer is the handler to create a ClientStream and it is
// the responsibility of the interceptor to call it. opts contain all applicable
// call options, including defaults from the ClientConn as well as per-call
// options.
//
// StreamClientInterceptor may return a custom ClientStream to intercept all I/O
// operations. The returned error must be compatible with the status package.
type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error) type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
// UnaryServerInfo consists of various information about a unary RPC on // UnaryServerInfo consists of various information about a unary RPC on

View File

@ -25,6 +25,7 @@ import (
"os" "os"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/grpcutil"
) )
// Logger is the global binary logger. It can be used to get binary logger for // Logger is the global binary logger. It can be used to get binary logger for
@ -39,6 +40,8 @@ type Logger interface {
// It is used to get a methodLogger for each individual method. // It is used to get a methodLogger for each individual method.
var binLogger Logger var binLogger Logger
var grpclogLogger = grpclog.Component("binarylog")
// SetLogger sets the binarg logger. // SetLogger sets the binarg logger.
// //
// Only call this at init time. // Only call this at init time.
@ -146,9 +149,9 @@ func (l *logger) setBlacklist(method string) error {
// Each methodLogger returned by this method is a new instance. This is to // Each methodLogger returned by this method is a new instance. This is to
// generate sequence id within the call. // generate sequence id within the call.
func (l *logger) getMethodLogger(methodName string) *MethodLogger { func (l *logger) getMethodLogger(methodName string) *MethodLogger {
s, m, err := parseMethodName(methodName) s, m, err := grpcutil.ParseMethod(methodName)
if err != nil { if err != nil {
grpclog.Infof("binarylogging: failed to parse %q: %v", methodName, err) grpclogLogger.Infof("binarylogging: failed to parse %q: %v", methodName, err)
return nil return nil
} }
if ml, ok := l.methods[s+"/"+m]; ok { if ml, ok := l.methods[s+"/"+m]; ok {

View File

@ -24,8 +24,6 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"google.golang.org/grpc/grpclog"
) )
// NewLoggerFromConfigString reads the string and build a logger. It can be used // NewLoggerFromConfigString reads the string and build a logger. It can be used
@ -52,7 +50,7 @@ func NewLoggerFromConfigString(s string) Logger {
methods := strings.Split(s, ",") methods := strings.Split(s, ",")
for _, method := range methods { for _, method := range methods {
if err := l.fillMethodLoggerWithConfigString(method); err != nil { if err := l.fillMethodLoggerWithConfigString(method); err != nil {
grpclog.Warningf("failed to parse binary log config: %v", err) grpclogLogger.Warningf("failed to parse binary log config: %v", err)
return nil return nil
} }
} }

View File

@ -27,7 +27,6 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1" pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -66,7 +65,7 @@ func newMethodLogger(h, m uint64) *MethodLogger {
callID: idGen.next(), callID: idGen.next(),
idWithinCallGen: &callIDGenerator{}, idWithinCallGen: &callIDGenerator{},
sink: defaultSink, // TODO(blog): make it plugable. sink: DefaultSink, // TODO(blog): make it plugable.
} }
} }
@ -219,12 +218,12 @@ func (c *ClientMessage) toProto() *pb.GrpcLogEntry {
if m, ok := c.Message.(proto.Message); ok { if m, ok := c.Message.(proto.Message); ok {
data, err = proto.Marshal(m) data, err = proto.Marshal(m)
if err != nil { if err != nil {
grpclog.Infof("binarylogging: failed to marshal proto message: %v", err) grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
} }
} else if b, ok := c.Message.([]byte); ok { } else if b, ok := c.Message.([]byte); ok {
data = b data = b
} else { } else {
grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte") grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
} }
ret := &pb.GrpcLogEntry{ ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE, Type: pb.GrpcLogEntry_EVENT_TYPE_CLIENT_MESSAGE,
@ -259,12 +258,12 @@ func (c *ServerMessage) toProto() *pb.GrpcLogEntry {
if m, ok := c.Message.(proto.Message); ok { if m, ok := c.Message.(proto.Message); ok {
data, err = proto.Marshal(m) data, err = proto.Marshal(m)
if err != nil { if err != nil {
grpclog.Infof("binarylogging: failed to marshal proto message: %v", err) grpclogLogger.Infof("binarylogging: failed to marshal proto message: %v", err)
} }
} else if b, ok := c.Message.([]byte); ok { } else if b, ok := c.Message.([]byte); ok {
data = b data = b
} else { } else {
grpclog.Infof("binarylogging: message to log is neither proto.message nor []byte") grpclogLogger.Infof("binarylogging: message to log is neither proto.message nor []byte")
} }
ret := &pb.GrpcLogEntry{ ret := &pb.GrpcLogEntry{
Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE, Type: pb.GrpcLogEntry_EVENT_TYPE_SERVER_MESSAGE,
@ -315,7 +314,7 @@ type ServerTrailer struct {
func (c *ServerTrailer) toProto() *pb.GrpcLogEntry { func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
st, ok := status.FromError(c.Err) st, ok := status.FromError(c.Err)
if !ok { if !ok {
grpclog.Info("binarylogging: error in trailer is not a status error") grpclogLogger.Info("binarylogging: error in trailer is not a status error")
} }
var ( var (
detailsBytes []byte detailsBytes []byte
@ -325,7 +324,7 @@ func (c *ServerTrailer) toProto() *pb.GrpcLogEntry {
if stProto != nil && len(stProto.Details) != 0 { if stProto != nil && len(stProto.Details) != 0 {
detailsBytes, err = proto.Marshal(stProto) detailsBytes, err = proto.Marshal(stProto)
if err != nil { if err != nil {
grpclog.Infof("binarylogging: failed to marshal status proto: %v", err) grpclogLogger.Infof("binarylogging: failed to marshal status proto: %v", err)
} }
} }
ret := &pb.GrpcLogEntry{ ret := &pb.GrpcLogEntry{

View File

@ -21,32 +21,23 @@ package binarylog
import ( import (
"bufio" "bufio"
"encoding/binary" "encoding/binary"
"fmt"
"io" "io"
"io/ioutil"
"sync" "sync"
"time" "time"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1" pb "google.golang.org/grpc/binarylog/grpc_binarylog_v1"
"google.golang.org/grpc/grpclog"
) )
var ( var (
defaultSink Sink = &noopSink{} // TODO(blog): change this default (file in /tmp). // DefaultSink is the sink where the logs will be written to. It's exported
// for the binarylog package to update.
DefaultSink Sink = &noopSink{} // TODO(blog): change this default (file in /tmp).
) )
// SetDefaultSink sets the sink where binary logs will be written to.
//
// Not thread safe. Only set during initialization.
func SetDefaultSink(s Sink) {
if defaultSink != nil {
defaultSink.Close()
}
defaultSink = s
}
// Sink writes log entry into the binary log sink. // Sink writes log entry into the binary log sink.
//
// sink is a copy of the exported binarylog.Sink, to avoid circular dependency.
type Sink interface { type Sink interface {
// Write will be called to write the log entry into the sink. // Write will be called to write the log entry into the sink.
// //
@ -67,7 +58,7 @@ func (ns *noopSink) Close() error { return nil }
// message is prefixed with a 4 byte big endian unsigned integer as the length. // message is prefixed with a 4 byte big endian unsigned integer as the length.
// //
// No buffer is done, Close() doesn't try to close the writer. // No buffer is done, Close() doesn't try to close the writer.
func newWriterSink(w io.Writer) *writerSink { func newWriterSink(w io.Writer) Sink {
return &writerSink{out: w} return &writerSink{out: w}
} }
@ -78,7 +69,7 @@ type writerSink struct {
func (ws *writerSink) Write(e *pb.GrpcLogEntry) error { func (ws *writerSink) Write(e *pb.GrpcLogEntry) error {
b, err := proto.Marshal(e) b, err := proto.Marshal(e)
if err != nil { if err != nil {
grpclog.Infof("binary logging: failed to marshal proto message: %v", err) grpclogLogger.Infof("binary logging: failed to marshal proto message: %v", err)
} }
hdr := make([]byte, 4) hdr := make([]byte, 4)
binary.BigEndian.PutUint32(hdr, uint32(len(b))) binary.BigEndian.PutUint32(hdr, uint32(len(b)))
@ -93,17 +84,17 @@ func (ws *writerSink) Write(e *pb.GrpcLogEntry) error {
func (ws *writerSink) Close() error { return nil } func (ws *writerSink) Close() error { return nil }
type bufWriteCloserSink struct { type bufferedSink struct {
mu sync.Mutex mu sync.Mutex
closer io.Closer closer io.Closer
out *writerSink // out is built on buf. out Sink // out is built on buf.
buf *bufio.Writer // buf is kept for flush. buf *bufio.Writer // buf is kept for flush.
writeStartOnce sync.Once writeStartOnce sync.Once
writeTicker *time.Ticker writeTicker *time.Ticker
} }
func (fs *bufWriteCloserSink) Write(e *pb.GrpcLogEntry) error { func (fs *bufferedSink) Write(e *pb.GrpcLogEntry) error {
// Start the write loop when Write is called. // Start the write loop when Write is called.
fs.writeStartOnce.Do(fs.startFlushGoroutine) fs.writeStartOnce.Do(fs.startFlushGoroutine)
fs.mu.Lock() fs.mu.Lock()
@ -119,44 +110,50 @@ const (
bufFlushDuration = 60 * time.Second bufFlushDuration = 60 * time.Second
) )
func (fs *bufWriteCloserSink) startFlushGoroutine() { func (fs *bufferedSink) startFlushGoroutine() {
fs.writeTicker = time.NewTicker(bufFlushDuration) fs.writeTicker = time.NewTicker(bufFlushDuration)
go func() { go func() {
for range fs.writeTicker.C { for range fs.writeTicker.C {
fs.mu.Lock() fs.mu.Lock()
fs.buf.Flush() if err := fs.buf.Flush(); err != nil {
grpclogLogger.Warningf("failed to flush to Sink: %v", err)
}
fs.mu.Unlock() fs.mu.Unlock()
} }
}() }()
} }
func (fs *bufWriteCloserSink) Close() error { func (fs *bufferedSink) Close() error {
if fs.writeTicker != nil { if fs.writeTicker != nil {
fs.writeTicker.Stop() fs.writeTicker.Stop()
} }
fs.mu.Lock() fs.mu.Lock()
fs.buf.Flush() if err := fs.buf.Flush(); err != nil {
fs.closer.Close() grpclogLogger.Warningf("failed to flush to Sink: %v", err)
fs.out.Close() }
if err := fs.closer.Close(); err != nil {
grpclogLogger.Warningf("failed to close the underlying WriterCloser: %v", err)
}
if err := fs.out.Close(); err != nil {
grpclogLogger.Warningf("failed to close the Sink: %v", err)
}
fs.mu.Unlock() fs.mu.Unlock()
return nil return nil
} }
func newBufWriteCloserSink(o io.WriteCloser) Sink { // NewBufferedSink creates a binary log sink with the given WriteCloser.
//
// Write() marshals the proto message and writes it to the given writer. Each
// message is prefixed with a 4 byte big endian unsigned integer as the length.
//
// Content is kept in a buffer, and is flushed every 60 seconds.
//
// Close closes the WriteCloser.
func NewBufferedSink(o io.WriteCloser) Sink {
bufW := bufio.NewWriter(o) bufW := bufio.NewWriter(o)
return &bufWriteCloserSink{ return &bufferedSink{
closer: o, closer: o,
out: newWriterSink(bufW), out: newWriterSink(bufW),
buf: bufW, buf: bufW,
} }
} }
// NewTempFileSink creates a temp file and returns a Sink that writes to this
// file.
func NewTempFileSink() (Sink, error) {
tempFile, err := ioutil.TempFile("/tmp", "grpcgo_binarylog_*.txt")
if err != nil {
return nil, fmt.Errorf("failed to create temp file: %v", err)
}
return newBufWriteCloserSink(tempFile), nil
}

View File

@ -1,41 +0,0 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package binarylog
import (
"errors"
"strings"
)
// parseMethodName splits service and method from the input. It expects format
// "/service/method".
//
// TODO: move to internal/grpcutil.
func parseMethodName(methodName string) (service, method string, _ error) {
if !strings.HasPrefix(methodName, "/") {
return "", "", errors.New("invalid method name: should start with /")
}
methodName = methodName[1:]
pos := strings.LastIndex(methodName, "/")
if pos < 0 {
return "", "", errors.New("invalid method name: suffix /method is missing")
}
return methodName[:pos], methodName[pos+1:], nil
}

View File

@ -216,7 +216,7 @@ func RegisterChannel(c Channel, pid int64, ref string) int64 {
// by pid). It returns the unique channelz tracking id assigned to this subchannel. // by pid). It returns the unique channelz tracking id assigned to this subchannel.
func RegisterSubChannel(c Channel, pid int64, ref string) int64 { func RegisterSubChannel(c Channel, pid int64, ref string) int64 {
if pid == 0 { if pid == 0 {
grpclog.Error("a SubChannel's parent id cannot be 0") logger.Error("a SubChannel's parent id cannot be 0")
return 0 return 0
} }
id := idGen.genID() id := idGen.genID()
@ -253,7 +253,7 @@ func RegisterServer(s Server, ref string) int64 {
// this listen socket. // this listen socket.
func RegisterListenSocket(s Socket, pid int64, ref string) int64 { func RegisterListenSocket(s Socket, pid int64, ref string) int64 {
if pid == 0 { if pid == 0 {
grpclog.Error("a ListenSocket's parent id cannot be 0") logger.Error("a ListenSocket's parent id cannot be 0")
return 0 return 0
} }
id := idGen.genID() id := idGen.genID()
@ -268,7 +268,7 @@ func RegisterListenSocket(s Socket, pid int64, ref string) int64 {
// this normal socket. // this normal socket.
func RegisterNormalSocket(s Socket, pid int64, ref string) int64 { func RegisterNormalSocket(s Socket, pid int64, ref string) int64 {
if pid == 0 { if pid == 0 {
grpclog.Error("a NormalSocket's parent id cannot be 0") logger.Error("a NormalSocket's parent id cannot be 0")
return 0 return 0
} }
id := idGen.genID() id := idGen.genID()
@ -294,7 +294,17 @@ type TraceEventDesc struct {
} }
// AddTraceEvent adds trace related to the entity with specified id, using the provided TraceEventDesc. // AddTraceEvent adds trace related to the entity with specified id, using the provided TraceEventDesc.
func AddTraceEvent(id int64, desc *TraceEventDesc) { func AddTraceEvent(l grpclog.DepthLoggerV2, id int64, depth int, desc *TraceEventDesc) {
for d := desc; d != nil; d = d.Parent {
switch d.Severity {
case CtUnknown, CtInfo:
l.InfoDepth(depth+1, d.Desc)
case CtWarning:
l.WarningDepth(depth+1, d.Desc)
case CtError:
l.ErrorDepth(depth+1, d.Desc)
}
}
if getMaxTraceEntry() == 0 { if getMaxTraceEntry() == 0 {
return return
} }

View File

@ -0,0 +1,102 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package channelz
import (
"fmt"
"google.golang.org/grpc/grpclog"
)
var logger = grpclog.Component("channelz")
// Info logs and adds a trace event if channelz is on.
func Info(l grpclog.DepthLoggerV2, id int64, args ...interface{}) {
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: fmt.Sprint(args...),
Severity: CtInfo,
})
} else {
l.InfoDepth(1, args...)
}
}
// Infof logs and adds a trace event if channelz is on.
func Infof(l grpclog.DepthLoggerV2, id int64, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: msg,
Severity: CtInfo,
})
} else {
l.InfoDepth(1, msg)
}
}
// Warning logs and adds a trace event if channelz is on.
func Warning(l grpclog.DepthLoggerV2, id int64, args ...interface{}) {
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: fmt.Sprint(args...),
Severity: CtWarning,
})
} else {
l.WarningDepth(1, args...)
}
}
// Warningf logs and adds a trace event if channelz is on.
func Warningf(l grpclog.DepthLoggerV2, id int64, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: msg,
Severity: CtWarning,
})
} else {
l.WarningDepth(1, msg)
}
}
// Error logs and adds a trace event if channelz is on.
func Error(l grpclog.DepthLoggerV2, id int64, args ...interface{}) {
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: fmt.Sprint(args...),
Severity: CtError,
})
} else {
l.ErrorDepth(1, args...)
}
}
// Errorf logs and adds a trace event if channelz is on.
func Errorf(l grpclog.DepthLoggerV2, id int64, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if IsOn() {
AddTraceEvent(l, id, 1, &TraceEventDesc{
Desc: msg,
Severity: CtError,
})
} else {
l.ErrorDepth(1, msg)
}
}

View File

@ -26,7 +26,6 @@ import (
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
) )
// entry represents a node in the channelz database. // entry represents a node in the channelz database.
@ -60,17 +59,17 @@ func (d *dummyEntry) addChild(id int64, e entry) {
// the addrConn will create a new transport. And when registering the new transport in // the addrConn will create a new transport. And when registering the new transport in
// channelz, its parent addrConn could have already been torn down and deleted // channelz, its parent addrConn could have already been torn down and deleted
// from channelz tracking, and thus reach the code here. // from channelz tracking, and thus reach the code here.
grpclog.Infof("attempt to add child of type %T with id %d to a parent (id=%d) that doesn't currently exist", e, id, d.idNotFound) logger.Infof("attempt to add child of type %T with id %d to a parent (id=%d) that doesn't currently exist", e, id, d.idNotFound)
} }
func (d *dummyEntry) deleteChild(id int64) { func (d *dummyEntry) deleteChild(id int64) {
// It is possible for a normal program to reach here under race condition. // It is possible for a normal program to reach here under race condition.
// Refer to the example described in addChild(). // Refer to the example described in addChild().
grpclog.Infof("attempt to delete child with id %d from a parent (id=%d) that doesn't currently exist", id, d.idNotFound) logger.Infof("attempt to delete child with id %d from a parent (id=%d) that doesn't currently exist", id, d.idNotFound)
} }
func (d *dummyEntry) triggerDelete() { func (d *dummyEntry) triggerDelete() {
grpclog.Warningf("attempt to delete an entry (id=%d) that doesn't currently exist", d.idNotFound) logger.Warningf("attempt to delete an entry (id=%d) that doesn't currently exist", d.idNotFound)
} }
func (*dummyEntry) deleteSelfIfReady() { func (*dummyEntry) deleteSelfIfReady() {
@ -215,7 +214,7 @@ func (c *channel) addChild(id int64, e entry) {
case *channel: case *channel:
c.nestedChans[id] = v.refName c.nestedChans[id] = v.refName
default: default:
grpclog.Errorf("cannot add a child (id = %d) of type %T to a channel", id, e) logger.Errorf("cannot add a child (id = %d) of type %T to a channel", id, e)
} }
} }
@ -326,7 +325,7 @@ func (sc *subChannel) addChild(id int64, e entry) {
if v, ok := e.(*normalSocket); ok { if v, ok := e.(*normalSocket); ok {
sc.sockets[id] = v.refName sc.sockets[id] = v.refName
} else { } else {
grpclog.Errorf("cannot add a child (id = %d) of type %T to a subChannel", id, e) logger.Errorf("cannot add a child (id = %d) of type %T to a subChannel", id, e)
} }
} }
@ -493,11 +492,11 @@ type listenSocket struct {
} }
func (ls *listenSocket) addChild(id int64, e entry) { func (ls *listenSocket) addChild(id int64, e entry) {
grpclog.Errorf("cannot add a child (id = %d) of type %T to a listen socket", id, e) logger.Errorf("cannot add a child (id = %d) of type %T to a listen socket", id, e)
} }
func (ls *listenSocket) deleteChild(id int64) { func (ls *listenSocket) deleteChild(id int64) {
grpclog.Errorf("cannot delete a child (id = %d) from a listen socket", id) logger.Errorf("cannot delete a child (id = %d) from a listen socket", id)
} }
func (ls *listenSocket) triggerDelete() { func (ls *listenSocket) triggerDelete() {
@ -506,7 +505,7 @@ func (ls *listenSocket) triggerDelete() {
} }
func (ls *listenSocket) deleteSelfIfReady() { func (ls *listenSocket) deleteSelfIfReady() {
grpclog.Errorf("cannot call deleteSelfIfReady on a listen socket") logger.Errorf("cannot call deleteSelfIfReady on a listen socket")
} }
func (ls *listenSocket) getParentID() int64 { func (ls *listenSocket) getParentID() int64 {
@ -522,11 +521,11 @@ type normalSocket struct {
} }
func (ns *normalSocket) addChild(id int64, e entry) { func (ns *normalSocket) addChild(id int64, e entry) {
grpclog.Errorf("cannot add a child (id = %d) of type %T to a normal socket", id, e) logger.Errorf("cannot add a child (id = %d) of type %T to a normal socket", id, e)
} }
func (ns *normalSocket) deleteChild(id int64) { func (ns *normalSocket) deleteChild(id int64) {
grpclog.Errorf("cannot delete a child (id = %d) from a normal socket", id) logger.Errorf("cannot delete a child (id = %d) from a normal socket", id)
} }
func (ns *normalSocket) triggerDelete() { func (ns *normalSocket) triggerDelete() {
@ -535,7 +534,7 @@ func (ns *normalSocket) triggerDelete() {
} }
func (ns *normalSocket) deleteSelfIfReady() { func (ns *normalSocket) deleteSelfIfReady() {
grpclog.Errorf("cannot call deleteSelfIfReady on a normal socket") logger.Errorf("cannot call deleteSelfIfReady on a normal socket")
} }
func (ns *normalSocket) getParentID() int64 { func (ns *normalSocket) getParentID() int64 {
@ -594,7 +593,7 @@ func (s *server) addChild(id int64, e entry) {
case *listenSocket: case *listenSocket:
s.listenSockets[id] = v.refName s.listenSockets[id] = v.refName
default: default:
grpclog.Errorf("cannot add a child (id = %d) of type %T to a server", id, e) logger.Errorf("cannot add a child (id = %d) of type %T to a server", id, e)
} }
} }
@ -673,10 +672,10 @@ func (c *channelTrace) clear() {
type Severity int type Severity int
const ( const (
// CtUNKNOWN indicates unknown severity of a trace event. // CtUnknown indicates unknown severity of a trace event.
CtUNKNOWN Severity = iota CtUnknown Severity = iota
// CtINFO indicates info level severity of a trace event. // CtInfo indicates info level severity of a trace event.
CtINFO CtInfo
// CtWarning indicates warning level severity of a trace event. // CtWarning indicates warning level severity of a trace event.
CtWarning CtWarning
// CtError indicates error level severity of a trace event. // CtError indicates error level severity of a trace event.

View File

@ -22,8 +22,6 @@ package channelz
import ( import (
"sync" "sync"
"google.golang.org/grpc/grpclog"
) )
var once sync.Once var once sync.Once
@ -39,6 +37,6 @@ type SocketOptionData struct {
// Windows OS doesn't support Socket Option // Windows OS doesn't support Socket Option
func (s *SocketOptionData) Getsockopt(fd uintptr) { func (s *SocketOptionData) Getsockopt(fd uintptr) {
once.Do(func() { once.Do(func() {
grpclog.Warningln("Channelz: socket options are not supported on non-linux os and appengine.") logger.Warning("Channelz: socket options are not supported on non-linux os and appengine.")
}) })
} }

View File

@ -0,0 +1,49 @@
/*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package credentials
import (
"context"
)
// requestInfoKey is a struct to be used as the key to store RequestInfo in a
// context.
type requestInfoKey struct{}
// NewRequestInfoContext creates a context with ri.
func NewRequestInfoContext(ctx context.Context, ri interface{}) context.Context {
return context.WithValue(ctx, requestInfoKey{}, ri)
}
// RequestInfoFromContext extracts the RequestInfo from ctx.
func RequestInfoFromContext(ctx context.Context) interface{} {
return ctx.Value(requestInfoKey{})
}
// clientHandshakeInfoKey is a struct used as the key to store
// ClientHandshakeInfo in a context.
type clientHandshakeInfoKey struct{}
// ClientHandshakeInfoFromContext extracts the ClientHandshakeInfo from ctx.
func ClientHandshakeInfoFromContext(ctx context.Context) interface{} {
return ctx.Value(clientHandshakeInfoKey{})
}
// NewClientHandshakeInfoContext creates a context with chi.
func NewClientHandshakeInfoContext(ctx context.Context, chi interface{}) context.Context {
return context.WithValue(ctx, clientHandshakeInfoKey{}, chi)
}

View File

@ -0,0 +1,77 @@
// +build !appengine
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package credentials defines APIs for parsing SPIFFE ID.
//
// All APIs in this package are experimental.
package credentials
import (
"crypto/tls"
"crypto/x509"
"net/url"
"google.golang.org/grpc/grpclog"
)
var logger = grpclog.Component("credentials")
// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format
// is invalid, return nil with warning.
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
if len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 {
return nil
}
return SPIFFEIDFromCert(state.PeerCertificates[0])
}
// SPIFFEIDFromCert parses the SPIFFE ID from x509.Certificate. If the SPIFFE
// ID format is invalid, return nil with warning.
func SPIFFEIDFromCert(cert *x509.Certificate) *url.URL {
if cert == nil || cert.URIs == nil {
return nil
}
var spiffeID *url.URL
for _, uri := range cert.URIs {
if uri == nil || uri.Scheme != "spiffe" || uri.Opaque != "" || (uri.User != nil && uri.User.Username() != "") {
continue
}
// From this point, we assume the uri is intended for a SPIFFE ID.
if len(uri.String()) > 2048 {
logger.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes")
return nil
}
if len(uri.Host) == 0 || len(uri.Path) == 0 {
logger.Warning("invalid SPIFFE ID: domain or workload ID is empty")
return nil
}
if len(uri.Host) > 255 {
logger.Warning("invalid SPIFFE ID: domain length larger than 255 characters")
return nil
}
// A valid SPIFFE certificate can only have exactly one URI SAN field.
if len(cert.URIs) > 1 {
logger.Warning("invalid SPIFFE ID: multiple URI SANs")
return nil
}
spiffeID = uri
}
return spiffeID
}

View File

@ -0,0 +1,31 @@
// +build appengine
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package credentials
import (
"crypto/tls"
"net/url"
)
// SPIFFEIDFromState is a no-op for appengine builds.
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
return nil
}

View File

@ -18,8 +18,7 @@
* *
*/ */
// Package internal contains credentials-internal code. package credentials
package internal
import ( import (
"net" "net"

View File

@ -18,7 +18,7 @@
* *
*/ */
package internal package credentials
import ( import (
"net" "net"

View File

@ -0,0 +1,50 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package credentials
import "crypto/tls"
const alpnProtoStrH2 = "h2"
// AppendH2ToNextProtos appends h2 to next protos.
func AppendH2ToNextProtos(ps []string) []string {
for _, p := range ps {
if p == alpnProtoStrH2 {
return ps
}
}
ret := make([]string, 0, len(ps)+1)
ret = append(ret, ps...)
return append(ret, alpnProtoStrH2)
}
// CloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// If cfg is nil, a new zero tls.Config is returned.
//
// TODO: inline this function if possible.
func CloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@ -34,5 +34,5 @@ var (
// Retry is set if retry is explicitly enabled via "GRPC_GO_RETRY=on". // Retry is set if retry is explicitly enabled via "GRPC_GO_RETRY=on".
Retry = strings.EqualFold(os.Getenv(retryStr), "on") Retry = strings.EqualFold(os.Getenv(retryStr), "on")
// TXTErrIgnore is set if TXT errors should be ignored ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false"). // TXTErrIgnore is set if TXT errors should be ignored ("GRPC_GO_IGNORE_TXT_ERRORS" is not "false").
TXTErrIgnore = !strings.EqualFold(os.Getenv(retryStr), "false") TXTErrIgnore = !strings.EqualFold(os.Getenv(txtErrIgnoreStr), "false")
) )

View File

@ -0,0 +1,126 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package grpclog (internal) defines depth logging for grpc.
package grpclog
import (
"os"
)
// Logger is the logger used for the non-depth log functions.
var Logger LoggerV2
// DepthLogger is the logger used for the depth log functions.
var DepthLogger DepthLoggerV2
// InfoDepth logs to the INFO log at the specified depth.
func InfoDepth(depth int, args ...interface{}) {
if DepthLogger != nil {
DepthLogger.InfoDepth(depth, args...)
} else {
Logger.Infoln(args...)
}
}
// WarningDepth logs to the WARNING log at the specified depth.
func WarningDepth(depth int, args ...interface{}) {
if DepthLogger != nil {
DepthLogger.WarningDepth(depth, args...)
} else {
Logger.Warningln(args...)
}
}
// ErrorDepth logs to the ERROR log at the specified depth.
func ErrorDepth(depth int, args ...interface{}) {
if DepthLogger != nil {
DepthLogger.ErrorDepth(depth, args...)
} else {
Logger.Errorln(args...)
}
}
// FatalDepth logs to the FATAL log at the specified depth.
func FatalDepth(depth int, args ...interface{}) {
if DepthLogger != nil {
DepthLogger.FatalDepth(depth, args...)
} else {
Logger.Fatalln(args...)
}
os.Exit(1)
}
// LoggerV2 does underlying logging work for grpclog.
// This is a copy of the LoggerV2 defined in the external grpclog package. It
// is defined here to avoid a circular dependency.
type LoggerV2 interface {
// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
Info(args ...interface{})
// Infoln logs to INFO log. Arguments are handled in the manner of fmt.Println.
Infoln(args ...interface{})
// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
Infof(format string, args ...interface{})
// Warning logs to WARNING log. Arguments are handled in the manner of fmt.Print.
Warning(args ...interface{})
// Warningln logs to WARNING log. Arguments are handled in the manner of fmt.Println.
Warningln(args ...interface{})
// Warningf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
Warningf(format string, args ...interface{})
// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
Error(args ...interface{})
// Errorln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
Errorln(args ...interface{})
// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
Errorf(format string, args ...interface{})
// Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatal(args ...interface{})
// Fatalln logs to ERROR log. Arguments are handled in the manner of fmt.Println.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatalln(args ...interface{})
// Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
// gRPC ensures that all Fatal logs will exit with os.Exit(1).
// Implementations may also call os.Exit() with a non-zero exit code.
Fatalf(format string, args ...interface{})
// V reports whether verbosity level l is at least the requested verbose level.
V(l int) bool
}
// DepthLoggerV2 logs at a specified call frame. If a LoggerV2 also implements
// DepthLoggerV2, the below functions will be called with the appropriate stack
// depth set for trivial functions the logger may ignore.
// This is a copy of the DepthLoggerV2 defined in the external grpclog package.
// It is defined here to avoid a circular dependency.
//
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type DepthLoggerV2 interface {
// InfoDepth logs to INFO log at the specified depth. Arguments are handled in the manner of fmt.Print.
InfoDepth(depth int, args ...interface{})
// WarningDepth logs to WARNING log at the specified depth. Arguments are handled in the manner of fmt.Print.
WarningDepth(depth int, args ...interface{})
// ErrorDetph logs to ERROR log at the specified depth. Arguments are handled in the manner of fmt.Print.
ErrorDepth(depth int, args ...interface{})
// FatalDepth logs to FATAL log at the specified depth. Arguments are handled in the manner of fmt.Print.
FatalDepth(depth int, args ...interface{})
}

View File

@ -0,0 +1,81 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpclog
import (
"fmt"
)
// PrefixLogger does logging with a prefix.
//
// Logging method on a nil logs without any prefix.
type PrefixLogger struct {
logger DepthLoggerV2
prefix string
}
// Infof does info logging.
func (pl *PrefixLogger) Infof(format string, args ...interface{}) {
if pl != nil {
// Handle nil, so the tests can pass in a nil logger.
format = pl.prefix + format
pl.logger.InfoDepth(1, fmt.Sprintf(format, args...))
return
}
InfoDepth(1, fmt.Sprintf(format, args...))
}
// Warningf does warning logging.
func (pl *PrefixLogger) Warningf(format string, args ...interface{}) {
if pl != nil {
format = pl.prefix + format
pl.logger.WarningDepth(1, fmt.Sprintf(format, args...))
return
}
WarningDepth(1, fmt.Sprintf(format, args...))
}
// Errorf does error logging.
func (pl *PrefixLogger) Errorf(format string, args ...interface{}) {
if pl != nil {
format = pl.prefix + format
pl.logger.ErrorDepth(1, fmt.Sprintf(format, args...))
return
}
ErrorDepth(1, fmt.Sprintf(format, args...))
}
// Debugf does info logging at verbose level 2.
func (pl *PrefixLogger) Debugf(format string, args ...interface{}) {
if !Logger.V(2) {
return
}
if pl != nil {
// Handle nil, so the tests can pass in a nil logger.
format = pl.prefix + format
pl.logger.InfoDepth(1, fmt.Sprintf(format, args...))
return
}
InfoDepth(1, fmt.Sprintf(format, args...))
}
// NewPrefixLogger creates a prefix logger with the given prefix.
func NewPrefixLogger(logger DepthLoggerV2, prefix string) *PrefixLogger {
return &PrefixLogger{logger: logger, prefix: prefix}
}

View File

@ -0,0 +1,63 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpcutil
import (
"strconv"
"time"
)
const maxTimeoutValue int64 = 100000000 - 1
// div does integer division and round-up the result. Note that this is
// equivalent to (d+r-1)/r but has less chance to overflow.
func div(d, r time.Duration) int64 {
if d%r > 0 {
return int64(d/r + 1)
}
return int64(d / r)
}
// EncodeDuration encodes the duration to the format grpc-timeout header
// accepts.
//
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
func EncodeDuration(t time.Duration) string {
// TODO: This is simplistic and not bandwidth efficient. Improve it.
if t <= 0 {
return "0n"
}
if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "n"
}
if d := div(t, time.Microsecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "u"
}
if d := div(t, time.Millisecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "m"
}
if d := div(t, time.Second); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "S"
}
if d := div(t, time.Minute); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "M"
}
// Note that maxTimeoutValue * time.Hour > MaxInt64.
return strconv.FormatInt(div(t, time.Hour), 10) + "H"
}

View File

@ -0,0 +1,40 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpcutil
import (
"context"
"google.golang.org/grpc/metadata"
)
type mdExtraKey struct{}
// WithExtraMetadata creates a new context with incoming md attached.
func WithExtraMetadata(ctx context.Context, md metadata.MD) context.Context {
return context.WithValue(ctx, mdExtraKey{}, md)
}
// ExtraMetadata returns the incoming metadata in ctx if it exists. The
// returned MD should not be modified. Writing to it may cause races.
// Modification should be made to copies of the returned MD.
func ExtraMetadata(ctx context.Context) (md metadata.MD, ok bool) {
md, ok = ctx.Value(mdExtraKey{}).(metadata.MD)
return
}

View File

@ -0,0 +1,84 @@
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpcutil
import (
"errors"
"strings"
)
// ParseMethod splits service and method from the input. It expects format
// "/service/method".
//
func ParseMethod(methodName string) (service, method string, _ error) {
if !strings.HasPrefix(methodName, "/") {
return "", "", errors.New("invalid method name: should start with /")
}
methodName = methodName[1:]
pos := strings.LastIndex(methodName, "/")
if pos < 0 {
return "", "", errors.New("invalid method name: suffix /method is missing")
}
return methodName[:pos], methodName[pos+1:], nil
}
const baseContentType = "application/grpc"
// ContentSubtype returns the content-subtype for the given content-type. The
// given content-type must be a valid content-type that starts with
// "application/grpc". A content-subtype will follow "application/grpc" after a
// "+" or ";". See
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details.
//
// If contentType is not a valid content-type for gRPC, the boolean
// will be false, otherwise true. If content-type == "application/grpc",
// "application/grpc+", or "application/grpc;", the boolean will be true,
// but no content-subtype will be returned.
//
// contentType is assumed to be lowercase already.
func ContentSubtype(contentType string) (string, bool) {
if contentType == baseContentType {
return "", true
}
if !strings.HasPrefix(contentType, baseContentType) {
return "", false
}
// guaranteed since != baseContentType and has baseContentType prefix
switch contentType[len(baseContentType)] {
case '+', ';':
// this will return true for "application/grpc+" or "application/grpc;"
// which the previous validContentType function tested to be valid, so we
// just say that no content-subtype is specified in this case
return contentType[len(baseContentType)+1:], true
default:
return "", false
}
}
// ContentType builds full content type with the given sub-type.
//
// contentSubtype is assumed to be lowercase
func ContentType(contentSubtype string) string {
if contentSubtype == "" {
return baseContentType
}
return baseContentType + "+" + contentSubtype
}

View File

@ -0,0 +1,89 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package grpcutil provides a bunch of utility functions to be used across the
// gRPC codebase.
package grpcutil
import (
"strings"
"google.golang.org/grpc/resolver"
)
// split2 returns the values from strings.SplitN(s, sep, 2).
// If sep is not found, it returns ("", "", false) instead.
func split2(s, sep string) (string, string, bool) {
spl := strings.SplitN(s, sep, 2)
if len(spl) < 2 {
return "", "", false
}
return spl[0], spl[1], true
}
// ParseTarget splits target into a resolver.Target struct containing scheme,
// authority and endpoint. skipUnixColonParsing indicates that the parse should
// not parse "unix:[path]" cases. This should be true in cases where a custom
// dialer is present, to prevent a behavior change.
//
// If target is not a valid scheme://authority/endpoint as specified in
// https://github.com/grpc/grpc/blob/master/doc/naming.md,
// it returns {Endpoint: target}.
func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) {
var ok bool
if strings.HasPrefix(target, "unix-abstract:") {
if strings.HasPrefix(target, "unix-abstract://") {
// Maybe, with Authority specified, try to parse it
var remain string
ret.Scheme, remain, _ = split2(target, "://")
ret.Authority, ret.Endpoint, ok = split2(remain, "/")
if !ok {
// No Authority, add the "//" back
ret.Endpoint = "//" + remain
} else {
// Found Authority, add the "/" back
ret.Endpoint = "/" + ret.Endpoint
}
} else {
// Without Authority specified, split target on ":"
ret.Scheme, ret.Endpoint, _ = split2(target, ":")
}
return ret
}
ret.Scheme, ret.Endpoint, ok = split2(target, "://")
if !ok {
if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing {
// Handle the "unix:[local/path]" and "unix:[/absolute/path]" cases,
// because splitting on :// only handles the
// "unix://[/absolute/path]" case. Only handle if the dialer is nil,
// to avoid a behavior change with custom dialers.
return resolver.Target{Scheme: "unix", Endpoint: target[len("unix:"):]}
}
return resolver.Target{Endpoint: target}
}
ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/")
if !ok {
return resolver.Target{Endpoint: target}
}
if ret.Scheme == "unix" {
// Add the "/" back in the unix case, so the unix resolver receives the
// actual endpoint in the "unix://[/absolute/path]" case.
ret.Endpoint = "/" + ret.Endpoint
}
return ret
}

View File

@ -25,6 +25,7 @@ import (
"time" "time"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/serviceconfig"
) )
var ( var (
@ -37,17 +38,32 @@ var (
// KeepaliveMinPingTime is the minimum ping interval. This must be 10s by // KeepaliveMinPingTime is the minimum ping interval. This must be 10s by
// default, but tests may wish to set it lower for convenience. // default, but tests may wish to set it lower for convenience.
KeepaliveMinPingTime = 10 * time.Second KeepaliveMinPingTime = 10 * time.Second
// StatusRawProto is exported by status/status.go. This func returns a
// pointer to the wrapped Status proto for a given status.Status without a
// call to proto.Clone(). The returned Status proto should not be mutated by
// the caller.
StatusRawProto interface{} // func (*status.Status) *spb.Status
// NewRequestInfoContext creates a new context based on the argument context attaching
// the passed in RequestInfo to the new context.
NewRequestInfoContext interface{} // func(context.Context, credentials.RequestInfo) context.Context
// ParseServiceConfigForTesting is for creating a fake // ParseServiceConfigForTesting is for creating a fake
// ClientConn for resolver testing only // ClientConn for resolver testing only
ParseServiceConfigForTesting interface{} // func(string) *serviceconfig.ParseResult ParseServiceConfigForTesting interface{} // func(string) *serviceconfig.ParseResult
// EqualServiceConfigForTesting is for testing service config generation and
// parsing. Both a and b should be returned by ParseServiceConfigForTesting.
// This function compares the config without rawJSON stripped, in case the
// there's difference in white space.
EqualServiceConfigForTesting func(a, b serviceconfig.Config) bool
// GetCertificateProviderBuilder returns the registered builder for the
// given name. This is set by package certprovider for use from xDS
// bootstrap code while parsing certificate provider configs in the
// bootstrap file.
GetCertificateProviderBuilder interface{} // func(string) certprovider.Builder
// GetXDSHandshakeInfoForTesting returns a pointer to the xds.HandshakeInfo
// stored in the passed in attributes. This is set by
// credentials/xds/xds.go.
GetXDSHandshakeInfoForTesting interface{} // func (*attributes.Attributes) *xds.HandshakeInfo
// GetServerCredentials returns the transport credentials configured on a
// gRPC server. An xDS-enabled server needs to know what type of credentials
// is configured on the underlying gRPC server. This is set by server.go.
GetServerCredentials interface{} // func (*grpc.Server) credentials.TransportCredentials
// DrainServerTransports initiates a graceful close of existing connections
// on a gRPC server accepted on the provided listener address. An
// xDS-enabled server invokes this method on a grpc.Server when a particular
// listener moves to "not-serving" mode.
DrainServerTransports interface{} // func(*grpc.Server, string)
) )
// HealthChecker defines the signature of the client-side LB channel health checking function. // HealthChecker defines the signature of the client-side LB channel health checking function.

View File

@ -0,0 +1,50 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package metadata contains functions to set and get metadata from addresses.
//
// This package is experimental.
package metadata
import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
)
type mdKeyType string
const mdKey = mdKeyType("grpc.internal.address.metadata")
// Get returns the metadata of addr.
func Get(addr resolver.Address) metadata.MD {
attrs := addr.Attributes
if attrs == nil {
return nil
}
md, _ := attrs.Value(mdKey).(metadata.MD)
return md
}
// Set sets (overrides) the metadata in addr.
//
// When a SubConn is created with this address, the RPCs sent on it will all
// have this metadata.
func Set(addr resolver.Address, md metadata.MD) resolver.Address {
addr.Attributes = addr.Attributes.WithValues(mdKey, md)
return addr
}

View File

@ -0,0 +1,164 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package resolver provides internal resolver-related functionality.
package resolver
import (
"context"
"sync"
"google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
)
// ConfigSelector controls what configuration to use for every RPC.
type ConfigSelector interface {
// Selects the configuration for the RPC, or terminates it using the error.
// This error will be converted by the gRPC library to a status error with
// code UNKNOWN if it is not returned as a status error.
SelectConfig(RPCInfo) (*RPCConfig, error)
}
// RPCInfo contains RPC information needed by a ConfigSelector.
type RPCInfo struct {
// Context is the user's context for the RPC and contains headers and
// application timeout. It is passed for interception purposes and for
// efficiency reasons. SelectConfig should not be blocking.
Context context.Context
Method string // i.e. "/Service/Method"
}
// RPCConfig describes the configuration to use for each RPC.
type RPCConfig struct {
// The context to use for the remainder of the RPC; can pass info to LB
// policy or affect timeout or metadata.
Context context.Context
MethodConfig serviceconfig.MethodConfig // configuration to use for this RPC
OnCommitted func() // Called when the RPC has been committed (retries no longer possible)
Interceptor ClientInterceptor
}
// ClientStream is the same as grpc.ClientStream, but defined here for circular
// dependency reasons.
type ClientStream interface {
// Header returns the header metadata received from the server if there
// is any. It blocks if the metadata is not ready to read.
Header() (metadata.MD, error)
// Trailer returns the trailer metadata from the server, if there is any.
// It must only be called after stream.CloseAndRecv has returned, or
// stream.Recv has returned a non-nil error (including io.EOF).
Trailer() metadata.MD
// CloseSend closes the send direction of the stream. It closes the stream
// when non-nil error is met. It is also not safe to call CloseSend
// concurrently with SendMsg.
CloseSend() error
// Context returns the context for this stream.
//
// It should not be called until after Header or RecvMsg has returned. Once
// called, subsequent client-side retries are disabled.
Context() context.Context
// SendMsg is generally called by generated code. On error, SendMsg aborts
// the stream. If the error was generated by the client, the status is
// returned directly; otherwise, io.EOF is returned and the status of
// the stream may be discovered using RecvMsg.
//
// SendMsg blocks until:
// - There is sufficient flow control to schedule m with the transport, or
// - The stream is done, or
// - The stream breaks.
//
// SendMsg does not wait until the message is received by the server. An
// untimely stream closure may result in lost messages. To ensure delivery,
// users should ensure the RPC completed successfully using RecvMsg.
//
// It is safe to have a goroutine calling SendMsg and another goroutine
// calling RecvMsg on the same stream at the same time, but it is not safe
// to call SendMsg on the same stream in different goroutines. It is also
// not safe to call CloseSend concurrently with SendMsg.
SendMsg(m interface{}) error
// RecvMsg blocks until it receives a message into m or the stream is
// done. It returns io.EOF when the stream completes successfully. On
// any other error, the stream is aborted and the error contains the RPC
// status.
//
// It is safe to have a goroutine calling SendMsg and another goroutine
// calling RecvMsg on the same stream at the same time, but it is not
// safe to call RecvMsg on the same stream in different goroutines.
RecvMsg(m interface{}) error
}
// ClientInterceptor is an interceptor for gRPC client streams.
type ClientInterceptor interface {
// NewStream produces a ClientStream for an RPC which may optionally use
// the provided function to produce a stream for delegation. Note:
// RPCInfo.Context should not be used (will be nil).
//
// done is invoked when the RPC is finished using its connection, or could
// not be assigned a connection. RPC operations may still occur on
// ClientStream after done is called, since the interceptor is invoked by
// application-layer operations. done must never be nil when called.
NewStream(ctx context.Context, ri RPCInfo, done func(), newStream func(ctx context.Context, done func()) (ClientStream, error)) (ClientStream, error)
}
// ServerInterceptor is unimplementable; do not use.
type ServerInterceptor interface {
notDefined()
}
type csKeyType string
const csKey = csKeyType("grpc.internal.resolver.configSelector")
// SetConfigSelector sets the config selector in state and returns the new
// state.
func SetConfigSelector(state resolver.State, cs ConfigSelector) resolver.State {
state.Attributes = state.Attributes.WithValues(csKey, cs)
return state
}
// GetConfigSelector retrieves the config selector from state, if present, and
// returns it or nil if absent.
func GetConfigSelector(state resolver.State) ConfigSelector {
cs, _ := state.Attributes.Value(csKey).(ConfigSelector)
return cs
}
// SafeConfigSelector allows for safe switching of ConfigSelector
// implementations such that previous values are guaranteed to not be in use
// when UpdateConfigSelector returns.
type SafeConfigSelector struct {
mu sync.RWMutex
cs ConfigSelector
}
// UpdateConfigSelector swaps to the provided ConfigSelector and blocks until
// all uses of the previous ConfigSelector have completed.
func (scs *SafeConfigSelector) UpdateConfigSelector(cs ConfigSelector) {
scs.mu.Lock()
defer scs.mu.Unlock()
scs.cs = cs
}
// SelectConfig defers to the current ConfigSelector in scs.
func (scs *SafeConfigSelector) SelectConfig(r RPCInfo) (*RPCConfig, error) {
scs.mu.RLock()
defer scs.mu.RUnlock()
return scs.cs.SelectConfig(r)
}

View File

@ -32,7 +32,9 @@ import (
"sync" "sync"
"time" "time"
grpclbstate "google.golang.org/grpc/balancer/grpclb/state"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/backoff"
"google.golang.org/grpc/internal/envconfig" "google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
@ -43,6 +45,15 @@ import (
// addresses from SRV records. Must not be changed after init time. // addresses from SRV records. Must not be changed after init time.
var EnableSRVLookups = false var EnableSRVLookups = false
var logger = grpclog.Component("dns")
// Globals to stub out in tests. TODO: Perhaps these two can be combined into a
// single variable for testing the resolver?
var (
newTimer = time.NewTimer
newTimerDNSResRate = time.NewTimer
)
func init() { func init() {
resolver.Register(NewBuilder()) resolver.Register(NewBuilder())
} }
@ -140,7 +151,6 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts
d.wg.Add(1) d.wg.Add(1)
go d.watcher() go d.watcher()
d.ResolveNow(resolver.ResolveNowOptions{})
return d, nil return d, nil
} }
@ -198,28 +208,38 @@ func (d *dnsResolver) Close() {
func (d *dnsResolver) watcher() { func (d *dnsResolver) watcher() {
defer d.wg.Done() defer d.wg.Done()
backoffIndex := 1
for { for {
state, err := d.lookup()
if err != nil {
// Report error to the underlying grpc.ClientConn.
d.cc.ReportError(err)
} else {
err = d.cc.UpdateState(*state)
}
var timer *time.Timer
if err == nil {
// Success resolving, wait for the next ResolveNow. However, also wait 30 seconds at the very least
// to prevent constantly re-resolving.
backoffIndex = 1
timer = newTimerDNSResRate(minDNSResRate)
select { select {
case <-d.ctx.Done(): case <-d.ctx.Done():
timer.Stop()
return return
case <-d.rn: case <-d.rn:
} }
state, err := d.lookup()
if err != nil {
d.cc.ReportError(err)
} else { } else {
d.cc.UpdateState(*state) // Poll on an error found in DNS Resolver or an error received from ClientConn.
timer = newTimer(backoff.DefaultExponential.Backoff(backoffIndex))
backoffIndex++
} }
// Sleep to prevent excessive re-resolutions. Incoming resolution requests
// will be queued in d.rn.
t := time.NewTimer(minDNSResRate)
select { select {
case <-t.C:
case <-d.ctx.Done(): case <-d.ctx.Done():
t.Stop() timer.Stop()
return return
case <-timer.C:
} }
} }
} }
@ -251,7 +271,7 @@ func (d *dnsResolver) lookupSRV() ([]resolver.Address, error) {
return nil, fmt.Errorf("dns: error parsing A record IP address %v", a) return nil, fmt.Errorf("dns: error parsing A record IP address %v", a)
} }
addr := ip + ":" + strconv.Itoa(int(s.Port)) addr := ip + ":" + strconv.Itoa(int(s.Port))
newAddrs = append(newAddrs, resolver.Address{Addr: addr, Type: resolver.GRPCLB, ServerName: s.Target}) newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target})
} }
} }
return newAddrs, nil return newAddrs, nil
@ -271,7 +291,7 @@ func handleDNSError(err error, lookupType string) error {
err = filterError(err) err = filterError(err)
if err != nil { if err != nil {
err = fmt.Errorf("dns: %v record lookup error: %v", lookupType, err) err = fmt.Errorf("dns: %v record lookup error: %v", lookupType, err)
grpclog.Infoln(err) logger.Info(err)
} }
return err return err
} }
@ -294,7 +314,7 @@ func (d *dnsResolver) lookupTXT() *serviceconfig.ParseResult {
// TXT record must have "grpc_config=" attribute in order to be used as service config. // TXT record must have "grpc_config=" attribute in order to be used as service config.
if !strings.HasPrefix(res, txtAttribute) { if !strings.HasPrefix(res, txtAttribute) {
grpclog.Warningf("dns: TXT record %v missing %v attribute", res, txtAttribute) logger.Warningf("dns: TXT record %v missing %v attribute", res, txtAttribute)
// This is not an error; it is the equivalent of not having a service config. // This is not an error; it is the equivalent of not having a service config.
return nil return nil
} }
@ -326,13 +346,15 @@ func (d *dnsResolver) lookup() (*resolver.State, error) {
if hostErr != nil && (srvErr != nil || len(srv) == 0) { if hostErr != nil && (srvErr != nil || len(srv) == 0) {
return nil, hostErr return nil, hostErr
} }
state := &resolver.State{
Addresses: append(addrs, srv...), state := resolver.State{Addresses: addrs}
if len(srv) > 0 {
state = grpclbstate.Set(state, &grpclbstate.State{BalancerAddresses: srv})
} }
if !d.disableServiceConfig { if !d.disableServiceConfig {
state.ServiceConfig = d.lookupTXT() state.ServiceConfig = d.lookupTXT()
} }
return state, nil return &state, nil
} }
// formatIP returns ok = false if addr is not a valid textual representation of an IP address. // formatIP returns ok = false if addr is not a valid textual representation of an IP address.
@ -418,12 +440,12 @@ func canaryingSC(js string) string {
var rcs []rawChoice var rcs []rawChoice
err := json.Unmarshal([]byte(js), &rcs) err := json.Unmarshal([]byte(js), &rcs)
if err != nil { if err != nil {
grpclog.Warningf("dns: error parsing service config json: %v", err) logger.Warningf("dns: error parsing service config json: %v", err)
return "" return ""
} }
cliHostname, err := os.Hostname() cliHostname, err := os.Hostname()
if err != nil { if err != nil {
grpclog.Warningf("dns: error getting client hostname: %v", err) logger.Warningf("dns: error getting client hostname: %v", err)
return "" return ""
} }
var sc string var sc string

View File

@ -0,0 +1,63 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package unix implements a resolver for unix targets.
package unix
import (
"fmt"
"google.golang.org/grpc/internal/transport/networktype"
"google.golang.org/grpc/resolver"
)
const unixScheme = "unix"
const unixAbstractScheme = "unix-abstract"
type builder struct {
scheme string
}
func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
if target.Authority != "" {
return nil, fmt.Errorf("invalid (non-empty) authority: %v", target.Authority)
}
addr := resolver.Address{Addr: target.Endpoint}
if b.scheme == unixAbstractScheme {
// prepend "\x00" to address for unix-abstract
addr.Addr = "\x00" + addr.Addr
}
cc.UpdateState(resolver.State{Addresses: []resolver.Address{networktype.Set(addr, "unix")}})
return &nopResolver{}, nil
}
func (b *builder) Scheme() string {
return b.scheme
}
type nopResolver struct {
}
func (*nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
func (*nopResolver) Close() {}
func init() {
resolver.Register(&builder{scheme: unixScheme})
resolver.Register(&builder{scheme: unixAbstractScheme})
}

View File

@ -0,0 +1,178 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package serviceconfig contains utility functions to parse service config.
package serviceconfig
import (
"encoding/json"
"fmt"
"time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
externalserviceconfig "google.golang.org/grpc/serviceconfig"
)
var logger = grpclog.Component("core")
// BalancerConfig wraps the name and config associated with one load balancing
// policy. It corresponds to a single entry of the loadBalancingConfig field
// from ServiceConfig.
//
// It implements the json.Unmarshaler interface.
//
// https://github.com/grpc/grpc-proto/blob/54713b1e8bc6ed2d4f25fb4dff527842150b91b2/grpc/service_config/service_config.proto#L247
type BalancerConfig struct {
Name string
Config externalserviceconfig.LoadBalancingConfig
}
type intermediateBalancerConfig []map[string]json.RawMessage
// MarshalJSON implements the json.Marshaler interface.
//
// It marshals the balancer and config into a length-1 slice
// ([]map[string]config).
func (bc *BalancerConfig) MarshalJSON() ([]byte, error) {
if bc.Config == nil {
// If config is nil, return empty config `{}`.
return []byte(fmt.Sprintf(`[{%q: %v}]`, bc.Name, "{}")), nil
}
c, err := json.Marshal(bc.Config)
if err != nil {
return nil, err
}
return []byte(fmt.Sprintf(`[{%q: %s}]`, bc.Name, c)), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
//
// ServiceConfig contains a list of loadBalancingConfigs, each with a name and
// config. This method iterates through that list in order, and stops at the
// first policy that is supported.
// - If the config for the first supported policy is invalid, the whole service
// config is invalid.
// - If the list doesn't contain any supported policy, the whole service config
// is invalid.
func (bc *BalancerConfig) UnmarshalJSON(b []byte) error {
var ir intermediateBalancerConfig
err := json.Unmarshal(b, &ir)
if err != nil {
return err
}
for i, lbcfg := range ir {
if len(lbcfg) != 1 {
return fmt.Errorf("invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q", i, lbcfg)
}
var (
name string
jsonCfg json.RawMessage
)
// Get the key:value pair from the map. We have already made sure that
// the map contains a single entry.
for name, jsonCfg = range lbcfg {
}
builder := balancer.Get(name)
if builder == nil {
// If the balancer is not registered, move on to the next config.
// This is not an error.
continue
}
bc.Name = name
parser, ok := builder.(balancer.ConfigParser)
if !ok {
if string(jsonCfg) != "{}" {
logger.Warningf("non-empty balancer configuration %q, but balancer does not implement ParseConfig", string(jsonCfg))
}
// Stop at this, though the builder doesn't support parsing config.
return nil
}
cfg, err := parser.ParseConfig(jsonCfg)
if err != nil {
return fmt.Errorf("error parsing loadBalancingConfig for policy %q: %v", name, err)
}
bc.Config = cfg
return nil
}
// This is reached when the for loop iterates over all entries, but didn't
// return. This means we had a loadBalancingConfig slice but did not
// encounter a registered policy. The config is considered invalid in this
// case.
return fmt.Errorf("invalid loadBalancingConfig: no supported policies found")
}
// MethodConfig defines the configuration recommended by the service providers for a
// particular method.
type MethodConfig struct {
// WaitForReady indicates whether RPCs sent to this method should wait until
// the connection is ready by default (!failfast). The value specified via the
// gRPC client API will override the value set here.
WaitForReady *bool
// Timeout is the default timeout for RPCs sent to this method. The actual
// deadline used will be the minimum of the value specified here and the value
// set by the application via the gRPC client API. If either one is not set,
// then the other will be used. If neither is set, then the RPC has no deadline.
Timeout *time.Duration
// MaxReqSize is the maximum allowed payload size for an individual request in a
// stream (client->server) in bytes. The size which is measured is the serialized
// payload after per-message compression (but before stream compression) in bytes.
// The actual value used is the minimum of the value specified here and the value set
// by the application via the gRPC client API. If either one is not set, then the other
// will be used. If neither is set, then the built-in default is used.
MaxReqSize *int
// MaxRespSize is the maximum allowed payload size for an individual response in a
// stream (server->client) in bytes.
MaxRespSize *int
// RetryPolicy configures retry options for the method.
RetryPolicy *RetryPolicy
}
// RetryPolicy defines the go-native version of the retry policy defined by the
// service config here:
// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config
type RetryPolicy struct {
// MaxAttempts is the maximum number of attempts, including the original RPC.
//
// This field is required and must be two or greater.
MaxAttempts int
// Exponential backoff parameters. The initial retry attempt will occur at
// random(0, initialBackoff). In general, the nth attempt will occur at
// random(0,
// min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)).
//
// These fields are required and must be greater than zero.
InitialBackoff time.Duration
MaxBackoff time.Duration
BackoffMultiplier float64
// The set of status codes which may be retried.
//
// Status codes are specified as strings, e.g., "UNAVAILABLE".
//
// This field is required and must be non-empty.
// Note: a set is used to store this for easy lookup.
RetryableStatusCodes map[codes.Code]bool
}

162
vendor/google.golang.org/grpc/internal/status/status.go generated vendored Normal file
View File

@ -0,0 +1,162 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package status implements errors returned by gRPC. These errors are
// serialized and transmitted on the wire between server and client, and allow
// for additional data to be transmitted via the Details field in the status
// proto. gRPC service handlers should return an error created by this
// package, and gRPC clients should expect a corresponding error to be
// returned from the RPC call.
//
// This package upholds the invariants that a non-nil error may not
// contain an OK code, and an OK code must result in a nil error.
package status
import (
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
)
// Status represents an RPC status code, message, and details. It is immutable
// and should be created with New, Newf, or FromProto.
type Status struct {
s *spb.Status
}
// New returns a Status representing c and msg.
func New(c codes.Code, msg string) *Status {
return &Status{s: &spb.Status{Code: int32(c), Message: msg}}
}
// Newf returns New(c, fmt.Sprintf(format, a...)).
func Newf(c codes.Code, format string, a ...interface{}) *Status {
return New(c, fmt.Sprintf(format, a...))
}
// FromProto returns a Status representing s.
func FromProto(s *spb.Status) *Status {
return &Status{s: proto.Clone(s).(*spb.Status)}
}
// Err returns an error representing c and msg. If c is OK, returns nil.
func Err(c codes.Code, msg string) error {
return New(c, msg).Err()
}
// Errorf returns Error(c, fmt.Sprintf(format, a...)).
func Errorf(c codes.Code, format string, a ...interface{}) error {
return Err(c, fmt.Sprintf(format, a...))
}
// Code returns the status code contained in s.
func (s *Status) Code() codes.Code {
if s == nil || s.s == nil {
return codes.OK
}
return codes.Code(s.s.Code)
}
// Message returns the message contained in s.
func (s *Status) Message() string {
if s == nil || s.s == nil {
return ""
}
return s.s.Message
}
// Proto returns s's status as an spb.Status proto message.
func (s *Status) Proto() *spb.Status {
if s == nil {
return nil
}
return proto.Clone(s.s).(*spb.Status)
}
// Err returns an immutable error representing s; returns nil if s.Code() is OK.
func (s *Status) Err() error {
if s.Code() == codes.OK {
return nil
}
return &Error{e: s.Proto()}
}
// WithDetails returns a new status with the provided details messages appended to the status.
// If any errors are encountered, it returns nil and the first error encountered.
func (s *Status) WithDetails(details ...proto.Message) (*Status, error) {
if s.Code() == codes.OK {
return nil, errors.New("no error details for status with code OK")
}
// s.Code() != OK implies that s.Proto() != nil.
p := s.Proto()
for _, detail := range details {
any, err := ptypes.MarshalAny(detail)
if err != nil {
return nil, err
}
p.Details = append(p.Details, any)
}
return &Status{s: p}, nil
}
// Details returns a slice of details messages attached to the status.
// If a detail cannot be decoded, the error is returned in place of the detail.
func (s *Status) Details() []interface{} {
if s == nil || s.s == nil {
return nil
}
details := make([]interface{}, 0, len(s.s.Details))
for _, any := range s.s.Details {
detail := &ptypes.DynamicAny{}
if err := ptypes.UnmarshalAny(any, detail); err != nil {
details = append(details, err)
continue
}
details = append(details, detail.Message)
}
return details
}
// Error wraps a pointer of a status proto. It implements error and Status,
// and a nil *Error should never be returned by this package.
type Error struct {
e *spb.Status
}
func (e *Error) Error() string {
return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(e.e.GetCode()), e.e.GetMessage())
}
// GRPCStatus returns the Status represented by se.
func (e *Error) GRPCStatus() *Status {
return FromProto(e.e)
}
// Is implements future error.Is functionality.
// A Error is equivalent if the code and message are identical.
func (e *Error) Is(target error) bool {
tse, ok := target.(*Error)
if !ok {
return false
}
return proto.Equal(e.e, tse.e)
}

View File

@ -32,35 +32,35 @@ import (
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )
var logger = grpclog.Component("core")
// GetCPUTime returns the how much CPU time has passed since the start of this process. // GetCPUTime returns the how much CPU time has passed since the start of this process.
func GetCPUTime() int64 { func GetCPUTime() int64 {
var ts unix.Timespec var ts unix.Timespec
if err := unix.ClockGettime(unix.CLOCK_PROCESS_CPUTIME_ID, &ts); err != nil { if err := unix.ClockGettime(unix.CLOCK_PROCESS_CPUTIME_ID, &ts); err != nil {
grpclog.Fatal(err) logger.Fatal(err)
} }
return ts.Nano() return ts.Nano()
} }
// Rusage is an alias for syscall.Rusage under linux non-appengine environment. // Rusage is an alias for syscall.Rusage under linux environment.
type Rusage syscall.Rusage type Rusage = syscall.Rusage
// GetRusage returns the resource usage of current process. // GetRusage returns the resource usage of current process.
func GetRusage() (rusage *Rusage) { func GetRusage() *Rusage {
rusage = new(Rusage) rusage := new(Rusage)
syscall.Getrusage(syscall.RUSAGE_SELF, (*syscall.Rusage)(rusage)) syscall.Getrusage(syscall.RUSAGE_SELF, rusage)
return return rusage
} }
// CPUTimeDiff returns the differences of user CPU time and system CPU time used // CPUTimeDiff returns the differences of user CPU time and system CPU time used
// between two Rusage structs. // between two Rusage structs.
func CPUTimeDiff(first *Rusage, latest *Rusage) (float64, float64) { func CPUTimeDiff(first *Rusage, latest *Rusage) (float64, float64) {
f := (*syscall.Rusage)(first)
l := (*syscall.Rusage)(latest)
var ( var (
utimeDiffs = l.Utime.Sec - f.Utime.Sec utimeDiffs = latest.Utime.Sec - first.Utime.Sec
utimeDiffus = l.Utime.Usec - f.Utime.Usec utimeDiffus = latest.Utime.Usec - first.Utime.Usec
stimeDiffs = l.Stime.Sec - f.Stime.Sec stimeDiffs = latest.Stime.Sec - first.Stime.Sec
stimeDiffus = l.Stime.Usec - f.Stime.Usec stimeDiffus = latest.Stime.Usec - first.Stime.Usec
) )
uTimeElapsed := float64(utimeDiffs) + float64(utimeDiffus)*1.0e-6 uTimeElapsed := float64(utimeDiffs) + float64(utimeDiffus)*1.0e-6

View File

@ -18,6 +18,8 @@
* *
*/ */
// Package syscall provides functionalities that grpc uses to get low-level
// operating system stats/info.
package syscall package syscall
import ( import (
@ -29,10 +31,11 @@ import (
) )
var once sync.Once var once sync.Once
var logger = grpclog.Component("core")
func log() { func log() {
once.Do(func() { once.Do(func() {
grpclog.Info("CPU time info is unavailable on non-linux or appengine environment.") logger.Info("CPU time info is unavailable on non-linux or appengine environment.")
}) })
} }
@ -47,7 +50,7 @@ func GetCPUTime() int64 {
type Rusage struct{} type Rusage struct{}
// GetRusage is a no-op function under non-linux or appengine environment. // GetRusage is a no-op function under non-linux or appengine environment.
func GetRusage() (rusage *Rusage) { func GetRusage() *Rusage {
log() log()
return nil return nil
} }

View File

@ -20,13 +20,17 @@ package transport
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"runtime" "runtime"
"strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/status"
) )
var updateHeaderTblSize = func(e *hpack.Encoder, v uint32) { var updateHeaderTblSize = func(e *hpack.Encoder, v uint32) {
@ -128,6 +132,14 @@ type cleanupStream struct {
func (c *cleanupStream) isTransportResponseFrame() bool { return c.rst } // Results in a RST_STREAM func (c *cleanupStream) isTransportResponseFrame() bool { return c.rst } // Results in a RST_STREAM
type earlyAbortStream struct {
streamID uint32
contentSubtype string
status *status.Status
}
func (*earlyAbortStream) isTransportResponseFrame() bool { return false }
type dataFrame struct { type dataFrame struct {
streamID uint32 streamID uint32
endStream bool endStream bool
@ -505,7 +517,9 @@ func (l *loopyWriter) run() (err error) {
// 1. When the connection is closed by some other known issue. // 1. When the connection is closed by some other known issue.
// 2. User closed the connection. // 2. User closed the connection.
// 3. A graceful close of connection. // 3. A graceful close of connection.
infof("transport: loopyWriter.run returning. %v", err) if logger.V(logLevel) {
logger.Infof("transport: loopyWriter.run returning. %v", err)
}
err = nil err = nil
} }
}() }()
@ -605,7 +619,9 @@ func (l *loopyWriter) headerHandler(h *headerFrame) error {
if l.side == serverSide { if l.side == serverSide {
str, ok := l.estdStreams[h.streamID] str, ok := l.estdStreams[h.streamID]
if !ok { if !ok {
warningf("transport: loopy doesn't recognize the stream: %d", h.streamID) if logger.V(logLevel) {
logger.Warningf("transport: loopy doesn't recognize the stream: %d", h.streamID)
}
return nil return nil
} }
// Case 1.A: Server is responding back with headers. // Case 1.A: Server is responding back with headers.
@ -658,7 +674,9 @@ func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.He
l.hBuf.Reset() l.hBuf.Reset()
for _, f := range hf { for _, f := range hf {
if err := l.hEnc.WriteField(f); err != nil { if err := l.hEnc.WriteField(f); err != nil {
warningf("transport: loopyWriter.writeHeader encountered error while encoding headers:", err) if logger.V(logLevel) {
logger.Warningf("transport: loopyWriter.writeHeader encountered error while encoding headers: %v", err)
}
} }
} }
var ( var (
@ -743,6 +761,24 @@ func (l *loopyWriter) cleanupStreamHandler(c *cleanupStream) error {
return nil return nil
} }
func (l *loopyWriter) earlyAbortStreamHandler(eas *earlyAbortStream) error {
if l.side == clientSide {
return errors.New("earlyAbortStream not handled on client")
}
headerFields := []hpack.HeaderField{
{Name: ":status", Value: "200"},
{Name: "content-type", Value: grpcutil.ContentType(eas.contentSubtype)},
{Name: "grpc-status", Value: strconv.Itoa(int(eas.status.Code()))},
{Name: "grpc-message", Value: encodeGrpcMessage(eas.status.Message())},
}
if err := l.writeHeader(eas.streamID, true, headerFields, nil); err != nil {
return err
}
return nil
}
func (l *loopyWriter) incomingGoAwayHandler(*incomingGoAway) error { func (l *loopyWriter) incomingGoAwayHandler(*incomingGoAway) error {
if l.side == clientSide { if l.side == clientSide {
l.draining = true l.draining = true
@ -781,6 +817,8 @@ func (l *loopyWriter) handle(i interface{}) error {
return l.registerStreamHandler(i) return l.registerStreamHandler(i)
case *cleanupStream: case *cleanupStream:
return l.cleanupStreamHandler(i) return l.cleanupStreamHandler(i)
case *earlyAbortStream:
return l.earlyAbortStreamHandler(i)
case *incomingGoAway: case *incomingGoAway:
return l.incomingGoAwayHandler(i) return l.incomingGoAwayHandler(i)
case *dataFrame: case *dataFrame:
@ -857,53 +895,56 @@ func (l *loopyWriter) processData() (bool, error) {
return false, nil return false, nil
} }
var ( var (
idx int
buf []byte buf []byte
) )
if len(dataItem.h) != 0 { // data header has not been written out yet. // Figure out the maximum size we can send
buf = dataItem.h maxSize := http2MaxFrameLen
} else {
idx = 1
buf = dataItem.d
}
size := http2MaxFrameLen
if len(buf) < size {
size = len(buf)
}
if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { // stream-level flow control. if strQuota := int(l.oiws) - str.bytesOutStanding; strQuota <= 0 { // stream-level flow control.
str.state = waitingOnStreamQuota str.state = waitingOnStreamQuota
return false, nil return false, nil
} else if strQuota < size { } else if maxSize > strQuota {
size = strQuota maxSize = strQuota
}
if maxSize > int(l.sendQuota) { // connection-level flow control.
maxSize = int(l.sendQuota)
}
// Compute how much of the header and data we can send within quota and max frame length
hSize := min(maxSize, len(dataItem.h))
dSize := min(maxSize-hSize, len(dataItem.d))
if hSize != 0 {
if dSize == 0 {
buf = dataItem.h
} else {
// We can add some data to grpc message header to distribute bytes more equally across frames.
// Copy on the stack to avoid generating garbage
var localBuf [http2MaxFrameLen]byte
copy(localBuf[:hSize], dataItem.h)
copy(localBuf[hSize:], dataItem.d[:dSize])
buf = localBuf[:hSize+dSize]
}
} else {
buf = dataItem.d
} }
if l.sendQuota < uint32(size) { // connection-level flow control. size := hSize + dSize
size = int(l.sendQuota)
}
// Now that outgoing flow controls are checked we can replenish str's write quota // Now that outgoing flow controls are checked we can replenish str's write quota
str.wq.replenish(size) str.wq.replenish(size)
var endStream bool var endStream bool
// If this is the last data message on this stream and all of it can be written in this iteration. // If this is the last data message on this stream and all of it can be written in this iteration.
if dataItem.endStream && size == len(buf) { if dataItem.endStream && len(dataItem.h)+len(dataItem.d) <= size {
// buf contains either data or it contains header but data is empty.
if idx == 1 || len(dataItem.d) == 0 {
endStream = true endStream = true
} }
}
if dataItem.onEachWrite != nil { if dataItem.onEachWrite != nil {
dataItem.onEachWrite() dataItem.onEachWrite()
} }
if err := l.framer.fr.WriteData(dataItem.streamID, endStream, buf[:size]); err != nil { if err := l.framer.fr.WriteData(dataItem.streamID, endStream, buf[:size]); err != nil {
return false, err return false, err
} }
buf = buf[size:]
str.bytesOutStanding += size str.bytesOutStanding += size
l.sendQuota -= uint32(size) l.sendQuota -= uint32(size)
if idx == 0 { dataItem.h = dataItem.h[hSize:]
dataItem.h = buf dataItem.d = dataItem.d[dSize:]
} else {
dataItem.d = buf
}
if len(dataItem.h) == 0 && len(dataItem.d) == 0 { // All the data from that message was written out. if len(dataItem.h) == 0 && len(dataItem.d) == 0 { // All the data from that message was written out.
str.itl.dequeue() str.itl.dequeue()
@ -924,3 +965,10 @@ func (l *loopyWriter) processData() (bool, error) {
} }
return false, nil return false, nil
} }
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -39,6 +39,7 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer" "google.golang.org/grpc/peer"
"google.golang.org/grpc/stats" "google.golang.org/grpc/stats"
@ -57,7 +58,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request, stats sta
} }
contentType := r.Header.Get("Content-Type") contentType := r.Header.Get("Content-Type")
// TODO: do we assume contentType is lowercase? we did before // TODO: do we assume contentType is lowercase? we did before
contentSubtype, validContentType := contentSubtype(contentType) contentSubtype, validContentType := grpcutil.ContentSubtype(contentType)
if !validContentType { if !validContentType {
return nil, errors.New("invalid gRPC request content-type") return nil, errors.New("invalid gRPC request content-type")
} }
@ -116,7 +117,6 @@ type serverHandlerTransport struct {
req *http.Request req *http.Request
timeoutSet bool timeoutSet bool
timeout time.Duration timeout time.Duration
didCommonHeaders bool
headerMD metadata.MD headerMD metadata.MD
@ -186,8 +186,11 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
ht.writeStatusMu.Lock() ht.writeStatusMu.Lock()
defer ht.writeStatusMu.Unlock() defer ht.writeStatusMu.Unlock()
headersWritten := s.updateHeaderSent()
err := ht.do(func() { err := ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
ht.writePendingHeaders(s)
}
// And flush, in case no header or body has been sent yet. // And flush, in case no header or body has been sent yet.
// This forces a separation of headers and trailers if this is the // This forces a separation of headers and trailers if this is the
@ -227,6 +230,8 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
if err == nil { // transport has not been closed if err == nil { // transport has not been closed
if ht.stats != nil { if ht.stats != nil {
// Note: The trailer fields are compressed with hpack after this call returns.
// No WireLength field is set here.
ht.stats.HandleRPC(s.Context(), &stats.OutTrailer{ ht.stats.HandleRPC(s.Context(), &stats.OutTrailer{
Trailer: s.trailer.Copy(), Trailer: s.trailer.Copy(),
}) })
@ -236,14 +241,16 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
return err return err
} }
// writePendingHeaders sets common and custom headers on the first
// write call (Write, WriteHeader, or WriteStatus)
func (ht *serverHandlerTransport) writePendingHeaders(s *Stream) {
ht.writeCommonHeaders(s)
ht.writeCustomHeaders(s)
}
// writeCommonHeaders sets common headers on the first write // writeCommonHeaders sets common headers on the first write
// call (Write, WriteHeader, or WriteStatus). // call (Write, WriteHeader, or WriteStatus).
func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) { func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
if ht.didCommonHeaders {
return
}
ht.didCommonHeaders = true
h := ht.rw.Header() h := ht.rw.Header()
h["Date"] = nil // suppress Date to make tests happy; TODO: restore h["Date"] = nil // suppress Date to make tests happy; TODO: restore
h.Set("Content-Type", ht.contentType) h.Set("Content-Type", ht.contentType)
@ -262,9 +269,30 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) {
} }
} }
// writeCustomHeaders sets custom headers set on the stream via SetHeader
// on the first write call (Write, WriteHeader, or WriteStatus).
func (ht *serverHandlerTransport) writeCustomHeaders(s *Stream) {
h := ht.rw.Header()
s.hdrMu.Lock()
for k, vv := range s.header {
if isReservedHeader(k) {
continue
}
for _, v := range vv {
h.Add(k, encodeMetadataHeader(k, v))
}
}
s.hdrMu.Unlock()
}
func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts *Options) error { func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts *Options) error {
headersWritten := s.updateHeaderSent()
return ht.do(func() { return ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
ht.writePendingHeaders(s)
}
ht.rw.Write(hdr) ht.rw.Write(hdr)
ht.rw.Write(data) ht.rw.Write(data)
ht.rw.(http.Flusher).Flush() ht.rw.(http.Flusher).Flush()
@ -272,27 +300,27 @@ func (ht *serverHandlerTransport) Write(s *Stream, hdr []byte, data []byte, opts
} }
func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error { func (ht *serverHandlerTransport) WriteHeader(s *Stream, md metadata.MD) error {
if err := s.SetHeader(md); err != nil {
return err
}
headersWritten := s.updateHeaderSent()
err := ht.do(func() { err := ht.do(func() {
ht.writeCommonHeaders(s) if !headersWritten {
h := ht.rw.Header() ht.writePendingHeaders(s)
for k, vv := range md {
// Clients don't tolerate reading restricted headers after some non restricted ones were sent.
if isReservedHeader(k) {
continue
}
for _, v := range vv {
v = encodeMetadataHeader(k, v)
h.Add(k, v)
}
} }
ht.rw.WriteHeader(200) ht.rw.WriteHeader(200)
ht.rw.(http.Flusher).Flush() ht.rw.(http.Flusher).Flush()
}) })
if err == nil { if err == nil {
if ht.stats != nil { if ht.stats != nil {
// Note: The header fields are compressed with hpack after this call returns.
// No WireLength field is set here.
ht.stats.HandleRPC(s.Context(), &stats.OutHeader{ ht.stats.HandleRPC(s.Context(), &stats.OutHeader{
Header: md.Copy(), Header: md.Copy(),
Compression: s.sendCompress,
}) })
} }
} }
@ -338,7 +366,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace
Addr: ht.RemoteAddr(), Addr: ht.RemoteAddr(),
} }
if req.TLS != nil { if req.TLS != nil {
pr.AuthInfo = credentials.TLSInfo{State: *req.TLS, CommonAuthInfo: credentials.CommonAuthInfo{credentials.PrivacyAndIntegrity}} pr.AuthInfo = credentials.TLSInfo{State: *req.TLS, CommonAuthInfo: credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}
} }
ctx = metadata.NewIncomingContext(ctx, ht.headerMD) ctx = metadata.NewIncomingContext(ctx, ht.headerMD)
s.ctx = peer.NewContext(ctx, pr) s.ctx = peer.NewContext(ctx, pr)

View File

@ -32,15 +32,18 @@ import (
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
icredentials "google.golang.org/grpc/internal/credentials"
"google.golang.org/grpc/internal/grpcutil"
imetadata "google.golang.org/grpc/internal/metadata"
"google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/internal/syscall"
"google.golang.org/grpc/internal/transport/networktype"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer" "google.golang.org/grpc/peer"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/stats" "google.golang.org/grpc/stats"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -57,7 +60,7 @@ type http2Client struct {
cancel context.CancelFunc cancel context.CancelFunc
ctxDone <-chan struct{} // Cache the ctx.Done() chan. ctxDone <-chan struct{} // Cache the ctx.Done() chan.
userAgent string userAgent string
md interface{} md metadata.MD
conn net.Conn // underlying communication channel conn net.Conn // underlying communication channel
loopy *loopyWriter loopy *loopyWriter
remoteAddr net.Addr remoteAddr net.Addr
@ -112,6 +115,9 @@ type http2Client struct {
// goAwayReason records the http2.ErrCode and debug data received with the // goAwayReason records the http2.ErrCode and debug data received with the
// GoAway frame. // GoAway frame.
goAwayReason GoAwayReason goAwayReason GoAwayReason
// goAwayDebugMessage contains a detailed human readable string about a
// GoAway frame, useful for error messages.
goAwayDebugMessage string
// A condition variable used to signal when the keepalive goroutine should // A condition variable used to signal when the keepalive goroutine should
// go dormant. The condition for dormancy is based on the number of active // go dormant. The condition for dormancy is based on the number of active
// streams and the `PermitWithoutStream` keepalive client parameter. And // streams and the `PermitWithoutStream` keepalive client parameter. And
@ -135,11 +141,27 @@ type http2Client struct {
connectionID uint64 connectionID uint64
} }
func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr string) (net.Conn, error) { func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr resolver.Address, useProxy bool, grpcUA string) (net.Conn, error) {
address := addr.Addr
networkType, ok := networktype.Get(addr)
if fn != nil { if fn != nil {
return fn(ctx, addr) if networkType == "unix" && !strings.HasPrefix(address, "\x00") {
// For backward compatibility, if the user dialed "unix:///path",
// the passthrough resolver would be used and the user's custom
// dialer would see "unix:///path". Since the unix resolver is used
// and the address is now "/path", prepend "unix://" so the user's
// custom dialer sees the same address.
return fn(ctx, "unix://"+address)
} }
return (&net.Dialer{}).DialContext(ctx, "tcp", addr) return fn(ctx, address)
}
if !ok {
networkType, address = parseDialTarget(address)
}
if networkType == "tcp" && useProxy {
return proxyDial(ctx, address, grpcUA)
}
return (&net.Dialer{}).DialContext(ctx, networkType, address)
} }
func isTemporary(err error) bool { func isTemporary(err error) bool {
@ -161,7 +183,7 @@ func isTemporary(err error) bool {
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
// and starts to receive messages on it. Non-nil error returns if construction // and starts to receive messages on it. Non-nil error returns if construction
// fails. // fails.
func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts ConnectOptions, onPrefaceReceipt func(), onGoAway func(GoAwayReason), onClose func()) (_ *http2Client, err error) { func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onPrefaceReceipt func(), onGoAway func(GoAwayReason), onClose func()) (_ *http2Client, err error) {
scheme := "http" scheme := "http"
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer func() { defer func() {
@ -170,7 +192,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
} }
}() }()
conn, err := dial(connectCtx, opts.Dialer, addr.Addr) conn, err := dial(connectCtx, opts.Dialer, addr, opts.UseProxy, opts.UserAgent)
if err != nil { if err != nil {
if opts.FailOnNonTempDialError { if opts.FailOnNonTempDialError {
return nil, connectionErrorf(isTemporary(err), err, "transport: error while dialing: %v", err) return nil, connectionErrorf(isTemporary(err), err, "transport: error while dialing: %v", err)
@ -214,12 +236,31 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
} }
} }
if transportCreds != nil { if transportCreds != nil {
scheme = "https" // gRPC, resolver, balancer etc. can specify arbitrary data in the
conn, authInfo, err = transportCreds.ClientHandshake(connectCtx, addr.Authority, conn) // Attributes field of resolver.Address, which is shoved into connectCtx
// and passed to the credential handshaker. This makes it possible for
// address specific arbitrary data to reach the credential handshaker.
connectCtx = icredentials.NewClientHandshakeInfoContext(connectCtx, credentials.ClientHandshakeInfo{Attributes: addr.Attributes})
conn, authInfo, err = transportCreds.ClientHandshake(connectCtx, addr.ServerName, conn)
if err != nil { if err != nil {
return nil, connectionErrorf(isTemporary(err), err, "transport: authentication handshake failed: %v", err) return nil, connectionErrorf(isTemporary(err), err, "transport: authentication handshake failed: %v", err)
} }
for _, cd := range perRPCCreds {
if cd.RequireTransportSecurity() {
if ci, ok := authInfo.(interface {
GetCommonAuthInfo() credentials.CommonAuthInfo
}); ok {
secLevel := ci.GetCommonAuthInfo().SecurityLevel
if secLevel != credentials.InvalidSecurityLevel && secLevel < credentials.PrivacyAndIntegrity {
return nil, connectionErrorf(true, nil, "transport: cannot send secure credentials on an insecure connection")
}
}
}
}
isSecure = true isSecure = true
if transportCreds.Info().SecurityProtocol == "tls" {
scheme = "https"
}
} }
dynamicWindow := true dynamicWindow := true
icwz := int32(initialWindowSize) icwz := int32(initialWindowSize)
@ -238,7 +279,6 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
ctxDone: ctx.Done(), // Cache Done chan. ctxDone: ctx.Done(), // Cache Done chan.
cancel: cancel, cancel: cancel,
userAgent: opts.UserAgent, userAgent: opts.UserAgent,
md: addr.Metadata,
conn: conn, conn: conn,
remoteAddr: conn.RemoteAddr(), remoteAddr: conn.RemoteAddr(),
localAddr: conn.LocalAddr(), localAddr: conn.LocalAddr(),
@ -266,6 +306,12 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
keepaliveEnabled: keepaliveEnabled, keepaliveEnabled: keepaliveEnabled,
bufferPool: newBufferPool(), bufferPool: newBufferPool(),
} }
if md, ok := addr.Metadata.(*metadata.MD); ok {
t.md = *md
} else if md := imetadata.Get(addr); md != nil {
t.md = md
}
t.controlBuf = newControlBuffer(t.ctxDone) t.controlBuf = newControlBuffer(t.ctxDone)
if opts.InitialWindowSize >= defaultWindowSize { if opts.InitialWindowSize >= defaultWindowSize {
t.initialWindowSize = opts.InitialWindowSize t.initialWindowSize = opts.InitialWindowSize
@ -302,12 +348,14 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
// Send connection preface to server. // Send connection preface to server.
n, err := t.conn.Write(clientPreface) n, err := t.conn.Write(clientPreface)
if err != nil { if err != nil {
t.Close() err = connectionErrorf(true, err, "transport: failed to write client preface: %v", err)
return nil, connectionErrorf(true, err, "transport: failed to write client preface: %v", err) t.Close(err)
return nil, err
} }
if n != len(clientPreface) { if n != len(clientPreface) {
t.Close() err = connectionErrorf(true, nil, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface))
return nil, connectionErrorf(true, err, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) t.Close(err)
return nil, err
} }
var ss []http2.Setting var ss []http2.Setting
@ -325,14 +373,16 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
} }
err = t.framer.fr.WriteSettings(ss...) err = t.framer.fr.WriteSettings(ss...)
if err != nil { if err != nil {
t.Close() err = connectionErrorf(true, err, "transport: failed to write initial settings frame: %v", err)
return nil, connectionErrorf(true, err, "transport: failed to write initial settings frame: %v", err) t.Close(err)
return nil, err
} }
// Adjust the connection flow control window if needed. // Adjust the connection flow control window if needed.
if delta := uint32(icwz - defaultWindowSize); delta > 0 { if delta := uint32(icwz - defaultWindowSize); delta > 0 {
if err := t.framer.fr.WriteWindowUpdate(0, delta); err != nil { if err := t.framer.fr.WriteWindowUpdate(0, delta); err != nil {
t.Close() err = connectionErrorf(true, err, "transport: failed to write window update: %v", err)
return nil, connectionErrorf(true, err, "transport: failed to write window update: %v", err) t.Close(err)
return nil, err
} }
} }
@ -345,7 +395,9 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr TargetInfo, opts Conne
t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst) t.loopy = newLoopyWriter(clientSide, t.framer, t.controlBuf, t.bdpEst)
err := t.loopy.run() err := t.loopy.run()
if err != nil { if err != nil {
errorf("transport: loopyWriter.run returning. Err: %v", err) if logger.V(logLevel) {
logger.Errorf("transport: loopyWriter.run returning. Err: %v", err)
}
} }
// If it's a connection error, let reader goroutine handle it // If it's a connection error, let reader goroutine handle it
// since there might be data in the buffers. // since there might be data in the buffers.
@ -367,6 +419,7 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
buf: newRecvBuffer(), buf: newRecvBuffer(),
headerChan: make(chan struct{}), headerChan: make(chan struct{}),
contentSubtype: callHdr.ContentSubtype, contentSubtype: callHdr.ContentSubtype,
doneFunc: callHdr.DoneFunc,
} }
s.wq = newWriteQuota(defaultWriteQuota, s.done) s.wq = newWriteQuota(defaultWriteQuota, s.done)
s.requestRead = func(n int) { s.requestRead = func(n int) {
@ -406,7 +459,7 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
Method: callHdr.Method, Method: callHdr.Method,
AuthInfo: t.authInfo, AuthInfo: t.authInfo,
} }
ctxWithRequestInfo := internal.NewRequestInfoContext.(func(context.Context, credentials.RequestInfo) context.Context)(ctx, ri) ctxWithRequestInfo := icredentials.NewRequestInfoContext(ctx, ri)
authData, err := t.getTrAuthData(ctxWithRequestInfo, aud) authData, err := t.getTrAuthData(ctxWithRequestInfo, aud)
if err != nil { if err != nil {
return nil, err return nil, err
@ -425,7 +478,7 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
headerFields = append(headerFields, hpack.HeaderField{Name: ":scheme", Value: t.scheme}) headerFields = append(headerFields, hpack.HeaderField{Name: ":scheme", Value: t.scheme})
headerFields = append(headerFields, hpack.HeaderField{Name: ":path", Value: callHdr.Method}) headerFields = append(headerFields, hpack.HeaderField{Name: ":path", Value: callHdr.Method})
headerFields = append(headerFields, hpack.HeaderField{Name: ":authority", Value: callHdr.Host}) headerFields = append(headerFields, hpack.HeaderField{Name: ":authority", Value: callHdr.Host})
headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(callHdr.ContentSubtype)}) headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: grpcutil.ContentType(callHdr.ContentSubtype)})
headerFields = append(headerFields, hpack.HeaderField{Name: "user-agent", Value: t.userAgent}) headerFields = append(headerFields, hpack.HeaderField{Name: "user-agent", Value: t.userAgent})
headerFields = append(headerFields, hpack.HeaderField{Name: "te", Value: "trailers"}) headerFields = append(headerFields, hpack.HeaderField{Name: "te", Value: "trailers"})
if callHdr.PreviousAttempts > 0 { if callHdr.PreviousAttempts > 0 {
@ -440,7 +493,7 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
// Send out timeout regardless its value. The server can detect timeout context by itself. // Send out timeout regardless its value. The server can detect timeout context by itself.
// TODO(mmukhi): Perhaps this field should be updated when actually writing out to the wire. // TODO(mmukhi): Perhaps this field should be updated when actually writing out to the wire.
timeout := time.Until(dl) timeout := time.Until(dl)
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-timeout", Value: encodeTimeout(timeout)}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-timeout", Value: grpcutil.EncodeDuration(timeout)})
} }
for k, v := range authData { for k, v := range authData {
headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
@ -469,19 +522,18 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
for _, vv := range added { for _, vv := range added {
for i, v := range vv { for i, v := range vv {
if i%2 == 0 { if i%2 == 0 {
k = v k = strings.ToLower(v)
continue continue
} }
// HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set. // HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set.
if isReservedHeader(k) { if isReservedHeader(k) {
continue continue
} }
headerFields = append(headerFields, hpack.HeaderField{Name: strings.ToLower(k), Value: encodeMetadataHeader(k, v)}) headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
} }
} }
} }
if md, ok := t.md.(*metadata.MD); ok { for k, vv := range t.md {
for k, vv := range *md {
if isReservedHeader(k) { if isReservedHeader(k) {
continue continue
} }
@ -489,7 +541,6 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)})
} }
} }
}
return headerFields, nil return headerFields, nil
} }
@ -537,9 +588,12 @@ func (t *http2Client) getCallAuthData(ctx context.Context, audience string, call
// Note: if these credentials are provided both via dial options and call // Note: if these credentials are provided both via dial options and call
// options, then both sets of credentials will be applied. // options, then both sets of credentials will be applied.
if callCreds := callHdr.Creds; callCreds != nil { if callCreds := callHdr.Creds; callCreds != nil {
if !t.isSecure && callCreds.RequireTransportSecurity() { if callCreds.RequireTransportSecurity() {
ri, _ := credentials.RequestInfoFromContext(ctx)
if !t.isSecure || credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity) != nil {
return nil, status.Error(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection") return nil, status.Error(codes.Unauthenticated, "transport: cannot send secure credentials on an insecure connection")
} }
}
data, err := callCreds.GetRequestMetadata(ctx, audience) data, err := callCreds.GetRequestMetadata(ctx, audience)
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, "transport: %v", err) return nil, status.Errorf(codes.Internal, "transport: %v", err)
@ -554,13 +608,26 @@ func (t *http2Client) getCallAuthData(ctx context.Context, audience string, call
return callAuthData, nil return callAuthData, nil
} }
// PerformedIOError wraps an error to indicate IO may have been performed
// before the error occurred.
type PerformedIOError struct {
Err error
}
// Error implements error.
func (p PerformedIOError) Error() string {
return p.Err.Error()
}
// NewStream creates a stream and registers it into the transport as "active" // NewStream creates a stream and registers it into the transport as "active"
// streams. // streams.
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) { func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) {
ctx = peer.NewContext(ctx, t.getPeer()) ctx = peer.NewContext(ctx, t.getPeer())
headerFields, err := t.createHeaderFields(ctx, callHdr) headerFields, err := t.createHeaderFields(ctx, callHdr)
if err != nil { if err != nil {
return nil, err // We may have performed I/O in the per-RPC creds callback, so do not
// allow transparent retry.
return nil, PerformedIOError{err}
} }
s := t.newStream(ctx, callHdr) s := t.newStream(ctx, callHdr)
cleanup := func(err error) { cleanup := func(err error) {
@ -680,14 +747,21 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
} }
} }
if t.statsHandler != nil { if t.statsHandler != nil {
header, _, _ := metadata.FromOutgoingContextRaw(ctx) header, ok := metadata.FromOutgoingContext(ctx)
if ok {
header.Set("user-agent", t.userAgent)
} else {
header = metadata.Pairs("user-agent", t.userAgent)
}
// Note: The header fields are compressed with hpack after this call returns.
// No WireLength field is set here.
outHeader := &stats.OutHeader{ outHeader := &stats.OutHeader{
Client: true, Client: true,
FullMethod: callHdr.Method, FullMethod: callHdr.Method,
RemoteAddr: t.remoteAddr, RemoteAddr: t.remoteAddr,
LocalAddr: t.localAddr, LocalAddr: t.localAddr,
Compression: callHdr.SendCompress, Compression: callHdr.SendCompress,
Header: header.Copy(), Header: header,
} }
t.statsHandler.HandleRPC(s.ctx, outHeader) t.statsHandler.HandleRPC(s.ctx, outHeader)
} }
@ -764,6 +838,9 @@ func (t *http2Client) closeStream(s *Stream, err error, rst bool, rstCode http2.
t.controlBuf.executeAndPut(addBackStreamQuota, cleanup) t.controlBuf.executeAndPut(addBackStreamQuota, cleanup)
// This will unblock write. // This will unblock write.
close(s.done) close(s.done)
if s.doneFunc != nil {
s.doneFunc()
}
} }
// Close kicks off the shutdown process of the transport. This should be called // Close kicks off the shutdown process of the transport. This should be called
@ -773,12 +850,12 @@ func (t *http2Client) closeStream(s *Stream, err error, rst bool, rstCode http2.
// This method blocks until the addrConn that initiated this transport is // This method blocks until the addrConn that initiated this transport is
// re-connected. This happens because t.onClose() begins reconnect logic at the // re-connected. This happens because t.onClose() begins reconnect logic at the
// addrConn level and blocks until the addrConn is successfully connected. // addrConn level and blocks until the addrConn is successfully connected.
func (t *http2Client) Close() error { func (t *http2Client) Close(err error) {
t.mu.Lock() t.mu.Lock()
// Make sure we only Close once. // Make sure we only Close once.
if t.state == closing { if t.state == closing {
t.mu.Unlock() t.mu.Unlock()
return nil return
} }
// Call t.onClose before setting the state to closing to prevent the client // Call t.onClose before setting the state to closing to prevent the client
// from attempting to create new streams ASAP. // from attempting to create new streams ASAP.
@ -794,13 +871,19 @@ func (t *http2Client) Close() error {
t.mu.Unlock() t.mu.Unlock()
t.controlBuf.finish() t.controlBuf.finish()
t.cancel() t.cancel()
err := t.conn.Close() t.conn.Close()
if channelz.IsOn() { if channelz.IsOn() {
channelz.RemoveEntry(t.channelzID) channelz.RemoveEntry(t.channelzID)
} }
// Append info about previous goaways if there were any, since this may be important
// for understanding the root cause for this connection to be closed.
_, goAwayDebugMessage := t.GetGoAwayReason()
if len(goAwayDebugMessage) > 0 {
err = fmt.Errorf("closing transport due to: %v, received prior goaway: %v", err, goAwayDebugMessage)
}
// Notify all active streams. // Notify all active streams.
for _, s := range streams { for _, s := range streams {
t.closeStream(s, ErrConnClosing, false, http2.ErrCodeNo, status.New(codes.Unavailable, ErrConnClosing.Desc), nil, false) t.closeStream(s, err, false, http2.ErrCodeNo, status.New(codes.Unavailable, err.Error()), nil, false)
} }
if t.statsHandler != nil { if t.statsHandler != nil {
connEnd := &stats.ConnEnd{ connEnd := &stats.ConnEnd{
@ -808,7 +891,6 @@ func (t *http2Client) Close() error {
} }
t.statsHandler.HandleConn(t.ctx, connEnd) t.statsHandler.HandleConn(t.ctx, connEnd)
} }
return err
} }
// GracefulClose sets the state to draining, which prevents new streams from // GracefulClose sets the state to draining, which prevents new streams from
@ -827,7 +909,7 @@ func (t *http2Client) GracefulClose() {
active := len(t.activeStreams) active := len(t.activeStreams)
t.mu.Unlock() t.mu.Unlock()
if active == 0 { if active == 0 {
t.Close() t.Close(ErrConnClosing)
return return
} }
t.controlBuf.put(&incomingGoAway{}) t.controlBuf.put(&incomingGoAway{})
@ -847,18 +929,10 @@ func (t *http2Client) Write(s *Stream, hdr []byte, data []byte, opts *Options) e
df := &dataFrame{ df := &dataFrame{
streamID: s.id, streamID: s.id,
endStream: opts.Last, endStream: opts.Last,
h: hdr,
d: data,
} }
if hdr != nil || data != nil { // If it's not an empty data frame. if hdr != nil || data != nil { // If it's not an empty data frame, check quota.
// Add some data to grpc message header so that we can equally
// distribute bytes across frames.
emptyLen := http2MaxFrameLen - len(hdr)
if emptyLen > len(data) {
emptyLen = len(data)
}
hdr = append(hdr, data[:emptyLen]...)
data = data[emptyLen:]
df.h, df.d = hdr, data
// TODO(mmukhi): The above logic in this if can be moved to loopyWriter's data handler.
if err := s.wq.get(int32(len(hdr) + len(data))); err != nil { if err := s.wq.get(int32(len(hdr) + len(data))); err != nil {
return err return err
} }
@ -992,7 +1066,9 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) {
} }
statusCode, ok := http2ErrConvTab[f.ErrCode] statusCode, ok := http2ErrConvTab[f.ErrCode]
if !ok { if !ok {
warningf("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error %v", f.ErrCode) if logger.V(logLevel) {
logger.Warningf("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error %v", f.ErrCode)
}
statusCode = codes.Unknown statusCode = codes.Unknown
} }
if statusCode == codes.Canceled { if statusCode == codes.Canceled {
@ -1074,12 +1150,14 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
return return
} }
if f.ErrCode == http2.ErrCodeEnhanceYourCalm { if f.ErrCode == http2.ErrCodeEnhanceYourCalm {
infof("Client received GoAway with http2.ErrCodeEnhanceYourCalm.") if logger.V(logLevel) {
logger.Infof("Client received GoAway with http2.ErrCodeEnhanceYourCalm.")
}
} }
id := f.LastStreamID id := f.LastStreamID
if id > 0 && id%2 != 1 { if id > 0 && id%2 == 0 {
t.mu.Unlock() t.mu.Unlock()
t.Close() t.Close(connectionErrorf(true, nil, "received goaway with non-zero even-numbered numbered stream id: %v", id))
return return
} }
// A client can receive multiple GoAways from the server (see // A client can receive multiple GoAways from the server (see
@ -1097,7 +1175,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
// If there are multiple GoAways the first one should always have an ID greater than the following ones. // If there are multiple GoAways the first one should always have an ID greater than the following ones.
if id > t.prevGoAwayID { if id > t.prevGoAwayID {
t.mu.Unlock() t.mu.Unlock()
t.Close() t.Close(connectionErrorf(true, nil, "received goaway with stream id: %v, which exceeds stream id of previous goaway: %v", id, t.prevGoAwayID))
return return
} }
default: default:
@ -1127,7 +1205,7 @@ func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
active := len(t.activeStreams) active := len(t.activeStreams)
t.mu.Unlock() t.mu.Unlock()
if active == 0 { if active == 0 {
t.Close() t.Close(connectionErrorf(true, nil, "received goaway and there are no active streams"))
} }
} }
@ -1143,12 +1221,13 @@ func (t *http2Client) setGoAwayReason(f *http2.GoAwayFrame) {
t.goAwayReason = GoAwayTooManyPings t.goAwayReason = GoAwayTooManyPings
} }
} }
t.goAwayDebugMessage = fmt.Sprintf("code: %s, debug data: %v", f.ErrCode, string(f.DebugData()))
} }
func (t *http2Client) GetGoAwayReason() GoAwayReason { func (t *http2Client) GetGoAwayReason() (GoAwayReason, string) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock() defer t.mu.Unlock()
return t.goAwayReason return t.goAwayReason, t.goAwayDebugMessage
} }
func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) { func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) {
@ -1178,8 +1257,8 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
state := &decodeState{} state := &decodeState{}
// Initialize isGRPC value to be !initialHeader, since if a gRPC Response-Headers has already been received, then it means that the peer is speaking gRPC and we are in gRPC mode. // Initialize isGRPC value to be !initialHeader, since if a gRPC Response-Headers has already been received, then it means that the peer is speaking gRPC and we are in gRPC mode.
state.data.isGRPC = !initialHeader state.data.isGRPC = !initialHeader
if err := state.decodeHeader(frame); err != nil { if h2code, err := state.decodeHeader(frame); err != nil {
t.closeStream(s, err, true, http2.ErrCodeProtocol, status.Convert(err), nil, endStream) t.closeStream(s, err, true, h2code, status.Convert(err), nil, endStream)
return return
} }
@ -1191,6 +1270,7 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
Client: true, Client: true,
WireLength: int(frame.Header().Length), WireLength: int(frame.Header().Length),
Header: s.header.Copy(), Header: s.header.Copy(),
Compression: s.recvCompress,
} }
t.statsHandler.HandleRPC(s.ctx, inHeader) t.statsHandler.HandleRPC(s.ctx, inHeader)
} else { } else {
@ -1244,7 +1324,8 @@ func (t *http2Client) reader() {
// Check the validity of server preface. // Check the validity of server preface.
frame, err := t.framer.fr.ReadFrame() frame, err := t.framer.fr.ReadFrame()
if err != nil { if err != nil {
t.Close() // this kicks off resetTransport, so must be last before return err = connectionErrorf(true, err, "error reading server preface: %v", err)
t.Close(err) // this kicks off resetTransport, so must be last before return
return return
} }
t.conn.SetReadDeadline(time.Time{}) // reset deadline once we get the settings frame (we didn't time out, yay!) t.conn.SetReadDeadline(time.Time{}) // reset deadline once we get the settings frame (we didn't time out, yay!)
@ -1253,7 +1334,8 @@ func (t *http2Client) reader() {
} }
sf, ok := frame.(*http2.SettingsFrame) sf, ok := frame.(*http2.SettingsFrame)
if !ok { if !ok {
t.Close() // this kicks off resetTransport, so must be last before return // this kicks off resetTransport, so must be last before return
t.Close(connectionErrorf(true, nil, "initial http2 frame from server is not a settings frame: %T", frame))
return return
} }
t.onPrefaceReceipt() t.onPrefaceReceipt()
@ -1277,13 +1359,19 @@ func (t *http2Client) reader() {
if s != nil { if s != nil {
// use error detail to provide better err message // use error detail to provide better err message
code := http2ErrConvTab[se.Code] code := http2ErrConvTab[se.Code]
msg := t.framer.fr.ErrorDetail().Error() errorDetail := t.framer.fr.ErrorDetail()
var msg string
if errorDetail != nil {
msg = errorDetail.Error()
} else {
msg = "received invalid frame"
}
t.closeStream(s, status.Error(code, msg), true, http2.ErrCodeProtocol, status.New(code, msg), nil, false) t.closeStream(s, status.Error(code, msg), true, http2.ErrCodeProtocol, status.New(code, msg), nil, false)
} }
continue continue
} else { } else {
// Transport error. // Transport error.
t.Close() t.Close(connectionErrorf(true, err, "error reading from server: %v", err))
return return
} }
} }
@ -1303,7 +1391,9 @@ func (t *http2Client) reader() {
case *http2.WindowUpdateFrame: case *http2.WindowUpdateFrame:
t.handleWindowUpdate(frame) t.handleWindowUpdate(frame)
default: default:
errorf("transport: http2Client.reader got unhandled frame type %v.", frame) if logger.V(logLevel) {
logger.Errorf("transport: http2Client.reader got unhandled frame type %v.", frame)
}
} }
} }
} }
@ -1340,7 +1430,7 @@ func (t *http2Client) keepalive() {
continue continue
} }
if outstandingPing && timeoutLeft <= 0 { if outstandingPing && timeoutLeft <= 0 {
t.Close() t.Close(connectionErrorf(true, nil, "keepalive ping failed to receive ACK within timeout"))
return return
} }
t.mu.Lock() t.mu.Lock()

View File

@ -26,6 +26,7 @@ import (
"io" "io"
"math" "math"
"net" "net"
"net/http"
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -34,12 +35,10 @@ import (
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"golang.org/x/net/http2" "golang.org/x/net/http2"
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
"google.golang.org/grpc/internal/grpcutil"
spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
@ -57,9 +56,6 @@ var (
// ErrHeaderListSizeLimitViolation indicates that the header list size is larger // ErrHeaderListSizeLimitViolation indicates that the header list size is larger
// than the limit set by peer. // than the limit set by peer.
ErrHeaderListSizeLimitViolation = errors.New("transport: trying to send header list size larger than the limit set by peer") ErrHeaderListSizeLimitViolation = errors.New("transport: trying to send header list size larger than the limit set by peer")
// statusRawProto is a function to get to the raw status proto wrapped in a
// status.Status without a proto.Clone().
statusRawProto = internal.StatusRawProto.(func(*status.Status) *spb.Status)
) )
// serverConnectionCounter counts the number of connections a server has seen // serverConnectionCounter counts the number of connections a server has seen
@ -294,7 +290,9 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err
t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst) t.loopy = newLoopyWriter(serverSide, t.framer, t.controlBuf, t.bdpEst)
t.loopy.ssGoAwayHandler = t.outgoingGoAwayHandler t.loopy.ssGoAwayHandler = t.outgoingGoAwayHandler
if err := t.loopy.run(); err != nil { if err := t.loopy.run(); err != nil {
errorf("transport: loopyWriter.run returning. Err: %v", err) if logger.V(logLevel) {
logger.Errorf("transport: loopyWriter.run returning. Err: %v", err)
}
} }
t.conn.Close() t.conn.Close()
close(t.writerDone) close(t.writerDone)
@ -309,12 +307,12 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
state := &decodeState{ state := &decodeState{
serverSide: true, serverSide: true,
} }
if err := state.decodeHeader(frame); err != nil { if h2code, err := state.decodeHeader(frame); err != nil {
if se, ok := status.FromError(err); ok { if _, ok := status.FromError(err); ok {
t.controlBuf.put(&cleanupStream{ t.controlBuf.put(&cleanupStream{
streamID: streamID, streamID: streamID,
rst: true, rst: true,
rstCode: statusCodeConvTab[se.Code()], rstCode: h2code,
onWrite: func() {}, onWrite: func() {},
}) })
} }
@ -358,24 +356,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
if state.data.statsTrace != nil { if state.data.statsTrace != nil {
s.ctx = stats.SetIncomingTrace(s.ctx, state.data.statsTrace) s.ctx = stats.SetIncomingTrace(s.ctx, state.data.statsTrace)
} }
if t.inTapHandle != nil {
var err error
info := &tap.Info{
FullMethodName: state.data.method,
}
s.ctx, err = t.inTapHandle(s.ctx, info)
if err != nil {
warningf("transport: http2Server.operateHeaders got an error from InTapHandle: %v", err)
t.controlBuf.put(&cleanupStream{
streamID: s.id,
rst: true,
rstCode: http2.ErrCodeRefusedStream,
onWrite: func() {},
})
s.cancel()
return false
}
}
t.mu.Lock() t.mu.Lock()
if t.state != reachable { if t.state != reachable {
t.mu.Unlock() t.mu.Unlock()
@ -396,11 +376,46 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
if streamID%2 != 1 || streamID <= t.maxStreamID { if streamID%2 != 1 || streamID <= t.maxStreamID {
t.mu.Unlock() t.mu.Unlock()
// illegal gRPC stream id. // illegal gRPC stream id.
errorf("transport: http2Server.HandleStreams received an illegal stream id: %v", streamID) if logger.V(logLevel) {
logger.Errorf("transport: http2Server.HandleStreams received an illegal stream id: %v", streamID)
}
s.cancel() s.cancel()
return true return true
} }
t.maxStreamID = streamID t.maxStreamID = streamID
if state.data.httpMethod != http.MethodPost {
t.mu.Unlock()
if logger.V(logLevel) {
logger.Warningf("transport: http2Server.operateHeaders parsed a :method field: %v which should be POST", state.data.httpMethod)
}
t.controlBuf.put(&cleanupStream{
streamID: streamID,
rst: true,
rstCode: http2.ErrCodeProtocol,
onWrite: func() {},
})
s.cancel()
return false
}
if t.inTapHandle != nil {
var err error
if s.ctx, err = t.inTapHandle(s.ctx, &tap.Info{FullMethodName: state.data.method}); err != nil {
t.mu.Unlock()
if logger.V(logLevel) {
logger.Infof("transport: http2Server.operateHeaders got an error from InTapHandle: %v", err)
}
stat, ok := status.FromError(err)
if !ok {
stat = status.New(codes.PermissionDenied, err.Error())
}
t.controlBuf.put(&earlyAbortStream{
streamID: s.id,
contentSubtype: s.contentSubtype,
status: stat,
})
return false
}
}
t.activeStreams[streamID] = s t.activeStreams[streamID] = s
if len(t.activeStreams) == 1 { if len(t.activeStreams) == 1 {
t.idle = time.Time{} t.idle = time.Time{}
@ -459,7 +474,9 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.
atomic.StoreInt64(&t.lastRead, time.Now().UnixNano()) atomic.StoreInt64(&t.lastRead, time.Now().UnixNano())
if err != nil { if err != nil {
if se, ok := err.(http2.StreamError); ok { if se, ok := err.(http2.StreamError); ok {
warningf("transport: http2Server.HandleStreams encountered http2.StreamError: %v", se) if logger.V(logLevel) {
logger.Warningf("transport: http2Server.HandleStreams encountered http2.StreamError: %v", se)
}
t.mu.Lock() t.mu.Lock()
s := t.activeStreams[se.StreamID] s := t.activeStreams[se.StreamID]
t.mu.Unlock() t.mu.Unlock()
@ -479,7 +496,9 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.
t.Close() t.Close()
return return
} }
warningf("transport: http2Server.HandleStreams failed to read frame: %v", err) if logger.V(logLevel) {
logger.Warningf("transport: http2Server.HandleStreams failed to read frame: %v", err)
}
t.Close() t.Close()
return return
} }
@ -502,7 +521,9 @@ func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.
case *http2.GoAwayFrame: case *http2.GoAwayFrame:
// TODO: Handle GoAway from the client appropriately. // TODO: Handle GoAway from the client appropriately.
default: default:
errorf("transport: http2Server.HandleStreams found unhandled frame type %v.", frame) if logger.V(logLevel) {
logger.Errorf("transport: http2Server.HandleStreams found unhandled frame type %v.", frame)
}
} }
} }
} }
@ -604,6 +625,10 @@ func (t *http2Server) handleData(f *http2.DataFrame) {
if !ok { if !ok {
return return
} }
if s.getState() == streamReadDone {
t.closeStream(s, true, http2.ErrCodeStreamClosed, false)
return
}
if size > 0 { if size > 0 {
if err := s.fc.onData(size); err != nil { if err := s.fc.onData(size); err != nil {
t.closeStream(s, true, http2.ErrCodeFlowControl, false) t.closeStream(s, true, http2.ErrCodeFlowControl, false)
@ -724,7 +749,9 @@ func (t *http2Server) handlePing(f *http2.PingFrame) {
if t.pingStrikes > maxPingStrikes { if t.pingStrikes > maxPingStrikes {
// Send goaway and close the connection. // Send goaway and close the connection.
errorf("transport: Got too many pings from the client, closing the connection.") if logger.V(logLevel) {
logger.Errorf("transport: Got too many pings from the client, closing the connection.")
}
t.controlBuf.put(&goAway{code: http2.ErrCodeEnhanceYourCalm, debugData: []byte("too_many_pings"), closeConn: true}) t.controlBuf.put(&goAway{code: http2.ErrCodeEnhanceYourCalm, debugData: []byte("too_many_pings"), closeConn: true})
} }
} }
@ -757,7 +784,9 @@ func (t *http2Server) checkForHeaderListSize(it interface{}) bool {
var sz int64 var sz int64
for _, f := range hdrFrame.hf { for _, f := range hdrFrame.hf {
if sz += int64(f.Size()); sz > int64(*t.maxSendHeaderListSize) { if sz += int64(f.Size()); sz > int64(*t.maxSendHeaderListSize) {
errorf("header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize) if logger.V(logLevel) {
logger.Errorf("header list size to send violates the maximum size (%d bytes) set by client", *t.maxSendHeaderListSize)
}
return false return false
} }
} }
@ -794,7 +823,7 @@ func (t *http2Server) writeHeaderLocked(s *Stream) error {
// first and create a slice of that exact size. // first and create a slice of that exact size.
headerFields := make([]hpack.HeaderField, 0, 2) // at least :status, content-type will be there if none else. headerFields := make([]hpack.HeaderField, 0, 2) // at least :status, content-type will be there if none else.
headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"})
headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: grpcutil.ContentType(s.contentSubtype)})
if s.sendCompress != "" { if s.sendCompress != "" {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-encoding", Value: s.sendCompress})
} }
@ -813,10 +842,11 @@ func (t *http2Server) writeHeaderLocked(s *Stream) error {
return ErrHeaderListSizeLimitViolation return ErrHeaderListSizeLimitViolation
} }
if t.stats != nil { if t.stats != nil {
// Note: WireLength is not set in outHeader. // Note: Headers are compressed with hpack after this call returns.
// TODO(mmukhi): Revisit this later, if needed. // No WireLength field is set here.
outHeader := &stats.OutHeader{ outHeader := &stats.OutHeader{
Header: s.header.Copy(), Header: s.header.Copy(),
Compression: s.sendCompress,
} }
t.stats.HandleRPC(s.Context(), outHeader) t.stats.HandleRPC(s.Context(), outHeader)
} }
@ -843,17 +873,17 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
} }
} else { // Send a trailer only response. } else { // Send a trailer only response.
headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"}) headerFields = append(headerFields, hpack.HeaderField{Name: ":status", Value: "200"})
headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: contentType(s.contentSubtype)}) headerFields = append(headerFields, hpack.HeaderField{Name: "content-type", Value: grpcutil.ContentType(s.contentSubtype)})
} }
} }
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))})
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())})
if p := statusRawProto(st); p != nil && len(p.Details) > 0 { if p := st.Proto(); p != nil && len(p.Details) > 0 {
stBytes, err := proto.Marshal(p) stBytes, err := proto.Marshal(p)
if err != nil { if err != nil {
// TODO: return error instead, when callers are able to handle it. // TODO: return error instead, when callers are able to handle it.
grpclog.Errorf("transport: failed to marshal rpc status: %v, error: %v", p, err) logger.Errorf("transport: failed to marshal rpc status: %v, error: %v", p, err)
} else { } else {
headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status-details-bin", Value: encodeBinHeader(stBytes)}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status-details-bin", Value: encodeBinHeader(stBytes)})
} }
@ -880,6 +910,8 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
rst := s.getState() == streamActive rst := s.getState() == streamActive
t.finishStream(s, rst, http2.ErrCodeNo, trailingHeader, true) t.finishStream(s, rst, http2.ErrCodeNo, trailingHeader, true)
if t.stats != nil { if t.stats != nil {
// Note: The trailer fields are compressed with hpack after this call returns.
// No WireLength field is set here.
t.stats.HandleRPC(s.Context(), &stats.OutTrailer{ t.stats.HandleRPC(s.Context(), &stats.OutTrailer{
Trailer: s.trailer.Copy(), Trailer: s.trailer.Copy(),
}) })
@ -911,13 +943,6 @@ func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) e
return ContextErr(s.ctx.Err()) return ContextErr(s.ctx.Err())
} }
} }
// Add some data to header frame so that we can equally distribute bytes across frames.
emptyLen := http2MaxFrameLen - len(hdr)
if emptyLen > len(data) {
emptyLen = len(data)
}
hdr = append(hdr, data[:emptyLen]...)
data = data[emptyLen:]
df := &dataFrame{ df := &dataFrame{
streamID: s.id, streamID: s.id,
h: hdr, h: hdr,
@ -989,7 +1014,9 @@ func (t *http2Server) keepalive() {
select { select {
case <-ageTimer.C: case <-ageTimer.C:
// Close the connection after grace period. // Close the connection after grace period.
infof("transport: closing server transport due to maximum connection age.") if logger.V(logLevel) {
logger.Infof("transport: closing server transport due to maximum connection age.")
}
t.Close() t.Close()
case <-t.done: case <-t.done:
} }
@ -1006,7 +1033,9 @@ func (t *http2Server) keepalive() {
continue continue
} }
if outstandingPing && kpTimeoutLeft <= 0 { if outstandingPing && kpTimeoutLeft <= 0 {
infof("transport: closing server transport due to idleness.") if logger.V(logLevel) {
logger.Infof("transport: closing server transport due to idleness.")
}
t.Close() t.Close()
return return
} }

View File

@ -27,6 +27,7 @@ import (
"math" "math"
"net" "net"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -37,6 +38,8 @@ import (
"golang.org/x/net/http2/hpack" "golang.org/x/net/http2/hpack"
spb "google.golang.org/genproto/googleapis/rpc/status" spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@ -50,7 +53,7 @@ const (
// "proto" as a suffix after "+" or ";". See // "proto" as a suffix after "+" or ";". See
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
// for more details. // for more details.
baseContentType = "application/grpc"
) )
var ( var (
@ -71,13 +74,6 @@ var (
http2.ErrCodeInadequateSecurity: codes.PermissionDenied, http2.ErrCodeInadequateSecurity: codes.PermissionDenied,
http2.ErrCodeHTTP11Required: codes.Internal, http2.ErrCodeHTTP11Required: codes.Internal,
} }
statusCodeConvTab = map[codes.Code]http2.ErrCode{
codes.Internal: http2.ErrCodeInternal,
codes.Canceled: http2.ErrCodeCancel,
codes.Unavailable: http2.ErrCodeRefusedStream,
codes.ResourceExhausted: http2.ErrCodeEnhanceYourCalm,
codes.PermissionDenied: http2.ErrCodeInadequateSecurity,
}
// HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table. // HTTPStatusConvTab is the HTTP status code to gRPC error code conversion table.
HTTPStatusConvTab = map[int]codes.Code{ HTTPStatusConvTab = map[int]codes.Code{
// 400 Bad Request - INTERNAL. // 400 Bad Request - INTERNAL.
@ -97,6 +93,7 @@ var (
// 504 Gateway timeout - UNAVAILABLE. // 504 Gateway timeout - UNAVAILABLE.
http.StatusGatewayTimeout: codes.Unavailable, http.StatusGatewayTimeout: codes.Unavailable,
} }
logger = grpclog.Component("transport")
) )
type parsedHeaderData struct { type parsedHeaderData struct {
@ -114,6 +111,7 @@ type parsedHeaderData struct {
timeoutSet bool timeoutSet bool
timeout time.Duration timeout time.Duration
method string method string
httpMethod string
// key-value metadata map from the peer. // key-value metadata map from the peer.
mdata map[string][]string mdata map[string][]string
statsTags []byte statsTags []byte
@ -182,46 +180,6 @@ func isWhitelistedHeader(hdr string) bool {
} }
} }
// contentSubtype returns the content-subtype for the given content-type. The
// given content-type must be a valid content-type that starts with
// "application/grpc". A content-subtype will follow "application/grpc" after a
// "+" or ";". See
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details.
//
// If contentType is not a valid content-type for gRPC, the boolean
// will be false, otherwise true. If content-type == "application/grpc",
// "application/grpc+", or "application/grpc;", the boolean will be true,
// but no content-subtype will be returned.
//
// contentType is assumed to be lowercase already.
func contentSubtype(contentType string) (string, bool) {
if contentType == baseContentType {
return "", true
}
if !strings.HasPrefix(contentType, baseContentType) {
return "", false
}
// guaranteed since != baseContentType and has baseContentType prefix
switch contentType[len(baseContentType)] {
case '+', ';':
// this will return true for "application/grpc+" or "application/grpc;"
// which the previous validContentType function tested to be valid, so we
// just say that no content-subtype is specified in this case
return contentType[len(baseContentType)+1:], true
default:
return "", false
}
}
// contentSubtype is assumed to be lowercase
func contentType(contentSubtype string) string {
if contentSubtype == "" {
return baseContentType
}
return baseContentType + "+" + contentSubtype
}
func (d *decodeState) status() *status.Status { func (d *decodeState) status() *status.Status {
if d.data.statusGen == nil { if d.data.statusGen == nil {
// No status-details were provided; generate status using code/msg. // No status-details were provided; generate status using code/msg.
@ -259,11 +217,11 @@ func decodeMetadataHeader(k, v string) (string, error) {
return v, nil return v, nil
} }
func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error { func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) (http2.ErrCode, error) {
// frame.Truncated is set to true when framer detects that the current header // frame.Truncated is set to true when framer detects that the current header
// list size hits MaxHeaderListSize limit. // list size hits MaxHeaderListSize limit.
if frame.Truncated { if frame.Truncated {
return status.Error(codes.Internal, "peer header list size exceeded limit") return http2.ErrCodeFrameSize, status.Error(codes.Internal, "peer header list size exceeded limit")
} }
for _, hf := range frame.Fields { for _, hf := range frame.Fields {
@ -272,10 +230,10 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
if d.data.isGRPC { if d.data.isGRPC {
if d.data.grpcErr != nil { if d.data.grpcErr != nil {
return d.data.grpcErr return http2.ErrCodeProtocol, d.data.grpcErr
} }
if d.serverSide { if d.serverSide {
return nil return http2.ErrCodeNo, nil
} }
if d.data.rawStatusCode == nil && d.data.statusGen == nil { if d.data.rawStatusCode == nil && d.data.statusGen == nil {
// gRPC status doesn't exist. // gRPC status doesn't exist.
@ -287,12 +245,12 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
code := int(codes.Unknown) code := int(codes.Unknown)
d.data.rawStatusCode = &code d.data.rawStatusCode = &code
} }
return nil return http2.ErrCodeNo, nil
} }
// HTTP fallback mode // HTTP fallback mode
if d.data.httpErr != nil { if d.data.httpErr != nil {
return d.data.httpErr return http2.ErrCodeProtocol, d.data.httpErr
} }
var ( var (
@ -307,7 +265,7 @@ func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error {
} }
} }
return status.Error(code, d.constructHTTPErrMsg()) return http2.ErrCodeProtocol, status.Error(code, d.constructHTTPErrMsg())
} }
// constructErrMsg constructs error message to be returned in HTTP fallback mode. // constructErrMsg constructs error message to be returned in HTTP fallback mode.
@ -340,7 +298,7 @@ func (d *decodeState) addMetadata(k, v string) {
func (d *decodeState) processHeaderField(f hpack.HeaderField) { func (d *decodeState) processHeaderField(f hpack.HeaderField) {
switch f.Name { switch f.Name {
case "content-type": case "content-type":
contentSubtype, validContentType := contentSubtype(f.Value) contentSubtype, validContentType := grpcutil.ContentSubtype(f.Value)
if !validContentType { if !validContentType {
d.data.contentTypeErr = fmt.Sprintf("transport: received the unexpected content-type %q", f.Value) d.data.contentTypeErr = fmt.Sprintf("transport: received the unexpected content-type %q", f.Value)
return return
@ -406,13 +364,17 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) {
} }
d.data.statsTrace = v d.data.statsTrace = v
d.addMetadata(f.Name, string(v)) d.addMetadata(f.Name, string(v))
case ":method":
d.data.httpMethod = f.Value
default: default:
if isReservedHeader(f.Name) && !isWhitelistedHeader(f.Name) { if isReservedHeader(f.Name) && !isWhitelistedHeader(f.Name) {
break break
} }
v, err := decodeMetadataHeader(f.Name, f.Value) v, err := decodeMetadataHeader(f.Name, f.Value)
if err != nil { if err != nil {
errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err) if logger.V(logLevel) {
logger.Errorf("Failed to decode metadata header (%q, %q): %v", f.Name, f.Value, err)
}
return return
} }
d.addMetadata(f.Name, v) d.addMetadata(f.Name, v)
@ -449,41 +411,6 @@ func timeoutUnitToDuration(u timeoutUnit) (d time.Duration, ok bool) {
return return
} }
const maxTimeoutValue int64 = 100000000 - 1
// div does integer division and round-up the result. Note that this is
// equivalent to (d+r-1)/r but has less chance to overflow.
func div(d, r time.Duration) int64 {
if m := d % r; m > 0 {
return int64(d/r + 1)
}
return int64(d / r)
}
// TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it.
func encodeTimeout(t time.Duration) string {
if t <= 0 {
return "0n"
}
if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "n"
}
if d := div(t, time.Microsecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "u"
}
if d := div(t, time.Millisecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "m"
}
if d := div(t, time.Second); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "S"
}
if d := div(t, time.Minute); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "M"
}
// Note that maxTimeoutValue * time.Hour > MaxInt64.
return strconv.FormatInt(div(t, time.Hour), 10) + "H"
}
func decodeTimeout(s string) (time.Duration, error) { func decodeTimeout(s string) (time.Duration, error) {
size := len(s) size := len(s)
if size < 2 { if size < 2 {
@ -675,3 +602,31 @@ func newFramer(conn net.Conn, writeBufferSize, readBufferSize int, maxHeaderList
f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil) f.fr.ReadMetaHeaders = hpack.NewDecoder(http2InitHeaderTableSize, nil)
return f return f
} }
// parseDialTarget returns the network and address to pass to dialer.
func parseDialTarget(target string) (string, string) {
net := "tcp"
m1 := strings.Index(target, ":")
m2 := strings.Index(target, ":/")
// handle unix:addr which will fail with url.Parse
if m1 >= 0 && m2 < 0 {
if n := target[0:m1]; n == "unix" {
return n, target[m1+1:]
}
}
if m2 >= 0 {
t, err := url.Parse(target)
if err != nil {
return net, target
}
scheme := t.Scheme
addr := t.Path
if scheme == "unix" {
if addr == "" {
addr = t.Host
}
return scheme, addr
}
}
return net, target
}

View File

@ -0,0 +1,46 @@
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package networktype declares the network type to be used in the default
// dailer. Attribute of a resolver.Address.
package networktype
import (
"google.golang.org/grpc/resolver"
)
// keyType is the key to use for storing State in Attributes.
type keyType string
const key = keyType("grpc.internal.transport.networktype")
// Set returns a copy of the provided address with attributes containing networkType.
func Set(address resolver.Address, networkType string) resolver.Address {
address.Attributes = address.Attributes.WithValues(key, networkType)
return address
}
// Get returns the network type in the resolver.Address and true, or "", false
// if not present.
func Get(address resolver.Address) (string, bool) {
v := address.Attributes.Value(key)
if v == nil {
return "", false
}
return v.(string), true
}

View File

@ -16,13 +16,12 @@
* *
*/ */
package grpc package transport
import ( import (
"bufio" "bufio"
"context" "context"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -34,8 +33,6 @@ import (
const proxyAuthHeaderKey = "Proxy-Authorization" const proxyAuthHeaderKey = "Proxy-Authorization"
var ( var (
// errDisabled indicates that proxy is disabled for the address.
errDisabled = errors.New("proxy is disabled for the address")
// The following variable will be overwritten in the tests. // The following variable will be overwritten in the tests.
httpProxyFromEnvironment = http.ProxyFromEnvironment httpProxyFromEnvironment = http.ProxyFromEnvironment
) )
@ -51,9 +48,6 @@ func mapAddress(ctx context.Context, address string) (*url.URL, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if url == nil {
return nil, errDisabled
}
return url, nil return url, nil
} }
@ -76,7 +70,7 @@ func basicAuth(username, password string) string {
return base64.StdEncoding.EncodeToString([]byte(auth)) return base64.StdEncoding.EncodeToString([]byte(auth))
} }
func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr string, proxyURL *url.URL) (_ net.Conn, err error) { func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr string, proxyURL *url.URL, grpcUA string) (_ net.Conn, err error) {
defer func() { defer func() {
if err != nil { if err != nil {
conn.Close() conn.Close()
@ -115,32 +109,28 @@ func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr stri
return &bufConn{Conn: conn, r: r}, nil return &bufConn{Conn: conn, r: r}, nil
} }
// newProxyDialer returns a dialer that connects to proxy first if necessary. // proxyDial dials, connecting to a proxy first if necessary. Checks if a proxy
// The returned dialer checks if a proxy is necessary, dial to the proxy with the // is necessary, dials, does the HTTP CONNECT handshake, and returns the
// provided dialer, does HTTP CONNECT handshake and returns the connection. // connection.
func newProxyDialer(dialer func(context.Context, string) (net.Conn, error)) func(context.Context, string) (net.Conn, error) { func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn, err error) {
return func(ctx context.Context, addr string) (conn net.Conn, err error) { newAddr := addr
var newAddr string
proxyURL, err := mapAddress(ctx, addr) proxyURL, err := mapAddress(ctx, addr)
if err != nil { if err != nil {
if err != errDisabled {
return nil, err return nil, err
} }
newAddr = addr if proxyURL != nil {
} else {
newAddr = proxyURL.Host newAddr = proxyURL.Host
} }
conn, err = dialer(ctx, newAddr) conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", newAddr)
if err != nil { if err != nil {
return return
} }
if proxyURL != nil { if proxyURL != nil {
// proxy is disabled if proxyURL is nil. // proxy is disabled if proxyURL is nil.
conn, err = doHTTPConnectHandshake(ctx, conn, addr, proxyURL) conn, err = doHTTPConnectHandshake(ctx, conn, addr, proxyURL, grpcUA)
} }
return return
}
} }
func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error { func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error {

View File

@ -35,11 +35,14 @@ import (
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/stats" "google.golang.org/grpc/stats"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/grpc/tap" "google.golang.org/grpc/tap"
) )
const logLevel = 2
type bufferPool struct { type bufferPool struct {
pool sync.Pool pool sync.Pool
} }
@ -238,6 +241,7 @@ type Stream struct {
ctx context.Context // the associated context of the stream ctx context.Context // the associated context of the stream
cancel context.CancelFunc // always nil for client side Stream cancel context.CancelFunc // always nil for client side Stream
done chan struct{} // closed at the end of stream to unblock writers. On the client side. done chan struct{} // closed at the end of stream to unblock writers. On the client side.
doneFunc func() // invoked at the end of stream on client side.
ctxDone <-chan struct{} // same as done chan but for server side. Cache of ctx.Done() (for performance) ctxDone <-chan struct{} // same as done chan but for server side. Cache of ctx.Done() (for performance)
method string // the associated RPC method of the stream method string // the associated RPC method of the stream
recvCompress string recvCompress string
@ -566,19 +570,14 @@ type ConnectOptions struct {
ChannelzParentID int64 ChannelzParentID int64
// MaxHeaderListSize sets the max (uncompressed) size of header list that is prepared to be received. // MaxHeaderListSize sets the max (uncompressed) size of header list that is prepared to be received.
MaxHeaderListSize *uint32 MaxHeaderListSize *uint32
} // UseProxy specifies if a proxy should be used.
UseProxy bool
// TargetInfo contains the information of the target such as network address and metadata.
type TargetInfo struct {
Addr string
Metadata interface{}
Authority string
} }
// NewClientTransport establishes the transport with the required ConnectOptions // NewClientTransport establishes the transport with the required ConnectOptions
// and returns it to the caller. // and returns it to the caller.
func NewClientTransport(connectCtx, ctx context.Context, target TargetInfo, opts ConnectOptions, onPrefaceReceipt func(), onGoAway func(GoAwayReason), onClose func()) (ClientTransport, error) { func NewClientTransport(connectCtx, ctx context.Context, addr resolver.Address, opts ConnectOptions, onPrefaceReceipt func(), onGoAway func(GoAwayReason), onClose func()) (ClientTransport, error) {
return newHTTP2Client(connectCtx, ctx, target, opts, onPrefaceReceipt, onGoAway, onClose) return newHTTP2Client(connectCtx, ctx, addr, opts, onPrefaceReceipt, onGoAway, onClose)
} }
// Options provides additional hints and information for message // Options provides additional hints and information for message
@ -613,6 +612,8 @@ type CallHdr struct {
ContentSubtype string ContentSubtype string
PreviousAttempts int // value of grpc-previous-rpc-attempts header to set PreviousAttempts int // value of grpc-previous-rpc-attempts header to set
DoneFunc func() // called when the stream is finished
} }
// ClientTransport is the common interface for all gRPC client-side transport // ClientTransport is the common interface for all gRPC client-side transport
@ -621,7 +622,7 @@ type ClientTransport interface {
// Close tears down this transport. Once it returns, the transport // Close tears down this transport. Once it returns, the transport
// should not be accessed any more. The caller must make sure this // should not be accessed any more. The caller must make sure this
// is called only once. // is called only once.
Close() error Close(err error)
// GracefulClose starts to tear down the transport: the transport will stop // GracefulClose starts to tear down the transport: the transport will stop
// accepting new RPCs and NewStream will return error. Once all streams are // accepting new RPCs and NewStream will return error. Once all streams are
@ -655,8 +656,9 @@ type ClientTransport interface {
// HTTP/2). // HTTP/2).
GoAway() <-chan struct{} GoAway() <-chan struct{}
// GetGoAwayReason returns the reason why GoAway frame was received. // GetGoAwayReason returns the reason why GoAway frame was received, along
GetGoAwayReason() GoAwayReason // with a human readable string with debug info.
GetGoAwayReason() (GoAwayReason, string)
// RemoteAddr returns the remote network address. // RemoteAddr returns the remote network address.
RemoteAddr() net.Addr RemoteAddr() net.Addr

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package internal
import (
"google.golang.org/grpc/attributes"
"google.golang.org/grpc/resolver"
)
// handshakeClusterNameKey is the type used as the key to store cluster name in
// the Attributes field of resolver.Address.
type handshakeClusterNameKey struct{}
// SetXDSHandshakeClusterName returns a copy of addr in which the Attributes field
// is updated with the cluster name.
func SetXDSHandshakeClusterName(addr resolver.Address, clusterName string) resolver.Address {
addr.Attributes = addr.Attributes.WithValues(handshakeClusterNameKey{}, clusterName)
return addr
}
// GetXDSHandshakeClusterName returns cluster name stored in attr.
func GetXDSHandshakeClusterName(attr *attributes.Attributes) (string, bool) {
v := attr.Value(handshakeClusterNameKey{})
name, ok := v.(string)
return name, ok
}

View File

@ -75,13 +75,9 @@ func Pairs(kv ...string) MD {
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv))) panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
} }
md := MD{} md := MD{}
var key string for i := 0; i < len(kv); i += 2 {
for i, s := range kv { key := strings.ToLower(kv[i])
if i%2 == 0 { md[key] = append(md[key], kv[i+1])
key = strings.ToLower(s)
continue
}
md[key] = append(md[key], s)
} }
return md return md
} }
@ -195,12 +191,18 @@ func FromOutgoingContext(ctx context.Context) (MD, bool) {
return nil, false return nil, false
} }
mds := make([]MD, 0, len(raw.added)+1) out := raw.md.Copy()
mds = append(mds, raw.md) for _, added := range raw.added {
for _, vv := range raw.added { if len(added)%2 == 1 {
mds = append(mds, Pairs(vv...)) panic(fmt.Sprintf("metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d", len(added)))
} }
return Join(mds...), ok
for i := 0; i < len(added); i += 2 {
key := strings.ToLower(added[i])
out[key] = append(out[key], added[i+1])
}
}
return out, ok
} }
type rawMD struct { type rawMD struct {

View File

@ -1,293 +0,0 @@
/*
*
* Copyright 2017 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package naming
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"google.golang.org/grpc/grpclog"
)
const (
defaultPort = "443"
defaultFreq = time.Minute * 30
)
var (
errMissingAddr = errors.New("missing address")
errWatcherClose = errors.New("watcher has been closed")
lookupHost = net.DefaultResolver.LookupHost
lookupSRV = net.DefaultResolver.LookupSRV
)
// NewDNSResolverWithFreq creates a DNS Resolver that can resolve DNS names, and
// create watchers that poll the DNS server using the frequency set by freq.
func NewDNSResolverWithFreq(freq time.Duration) (Resolver, error) {
return &dnsResolver{freq: freq}, nil
}
// NewDNSResolver creates a DNS Resolver that can resolve DNS names, and create
// watchers that poll the DNS server using the default frequency defined by defaultFreq.
func NewDNSResolver() (Resolver, error) {
return NewDNSResolverWithFreq(defaultFreq)
}
// dnsResolver handles name resolution for names following the DNS scheme
type dnsResolver struct {
// frequency of polling the DNS server that the watchers created by this resolver will use.
freq time.Duration
}
// formatIP returns ok = false if addr is not a valid textual representation of an IP address.
// If addr is an IPv4 address, return the addr and ok = true.
// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true.
func formatIP(addr string) (addrIP string, ok bool) {
ip := net.ParseIP(addr)
if ip == nil {
return "", false
}
if ip.To4() != nil {
return addr, true
}
return "[" + addr + "]", true
}
// parseTarget takes the user input target string, returns formatted host and port info.
// If target doesn't specify a port, set the port to be the defaultPort.
// If target is in IPv6 format and host-name is enclosed in square brackets, brackets
// are stripped when setting the host.
// examples:
// target: "www.google.com" returns host: "www.google.com", port: "443"
// target: "ipv4-host:80" returns host: "ipv4-host", port: "80"
// target: "[ipv6-host]" returns host: "ipv6-host", port: "443"
// target: ":80" returns host: "localhost", port: "80"
// target: ":" returns host: "localhost", port: "443"
func parseTarget(target string) (host, port string, err error) {
if target == "" {
return "", "", errMissingAddr
}
if ip := net.ParseIP(target); ip != nil {
// target is an IPv4 or IPv6(without brackets) address
return target, defaultPort, nil
}
if host, port, err := net.SplitHostPort(target); err == nil {
// target has port, i.e ipv4-host:port, [ipv6-host]:port, host-name:port
if host == "" {
// Keep consistent with net.Dial(): If the host is empty, as in ":80", the local system is assumed.
host = "localhost"
}
if port == "" {
// If the port field is empty(target ends with colon), e.g. "[::1]:", defaultPort is used.
port = defaultPort
}
return host, port, nil
}
if host, port, err := net.SplitHostPort(target + ":" + defaultPort); err == nil {
// target doesn't have port
return host, port, nil
}
return "", "", fmt.Errorf("invalid target address %v", target)
}
// Resolve creates a watcher that watches the name resolution of the target.
func (r *dnsResolver) Resolve(target string) (Watcher, error) {
host, port, err := parseTarget(target)
if err != nil {
return nil, err
}
if net.ParseIP(host) != nil {
ipWatcher := &ipWatcher{
updateChan: make(chan *Update, 1),
}
host, _ = formatIP(host)
ipWatcher.updateChan <- &Update{Op: Add, Addr: host + ":" + port}
return ipWatcher, nil
}
ctx, cancel := context.WithCancel(context.Background())
return &dnsWatcher{
r: r,
host: host,
port: port,
ctx: ctx,
cancel: cancel,
t: time.NewTimer(0),
}, nil
}
// dnsWatcher watches for the name resolution update for a specific target
type dnsWatcher struct {
r *dnsResolver
host string
port string
// The latest resolved address set
curAddrs map[string]*Update
ctx context.Context
cancel context.CancelFunc
t *time.Timer
}
// ipWatcher watches for the name resolution update for an IP address.
type ipWatcher struct {
updateChan chan *Update
}
// Next returns the address resolution Update for the target. For IP address,
// the resolution is itself, thus polling name server is unnecessary. Therefore,
// Next() will return an Update the first time it is called, and will be blocked
// for all following calls as no Update exists until watcher is closed.
func (i *ipWatcher) Next() ([]*Update, error) {
u, ok := <-i.updateChan
if !ok {
return nil, errWatcherClose
}
return []*Update{u}, nil
}
// Close closes the ipWatcher.
func (i *ipWatcher) Close() {
close(i.updateChan)
}
// AddressType indicates the address type returned by name resolution.
type AddressType uint8
const (
// Backend indicates the server is a backend server.
Backend AddressType = iota
// GRPCLB indicates the server is a grpclb load balancer.
GRPCLB
)
// AddrMetadataGRPCLB contains the information the name resolver for grpclb should provide. The
// name resolver used by the grpclb balancer is required to provide this type of metadata in
// its address updates.
type AddrMetadataGRPCLB struct {
// AddrType is the type of server (grpc load balancer or backend).
AddrType AddressType
// ServerName is the name of the grpc load balancer. Used for authentication.
ServerName string
}
// compileUpdate compares the old resolved addresses and newly resolved addresses,
// and generates an update list
func (w *dnsWatcher) compileUpdate(newAddrs map[string]*Update) []*Update {
var res []*Update
for a, u := range w.curAddrs {
if _, ok := newAddrs[a]; !ok {
u.Op = Delete
res = append(res, u)
}
}
for a, u := range newAddrs {
if _, ok := w.curAddrs[a]; !ok {
res = append(res, u)
}
}
return res
}
func (w *dnsWatcher) lookupSRV() map[string]*Update {
newAddrs := make(map[string]*Update)
_, srvs, err := lookupSRV(w.ctx, "grpclb", "tcp", w.host)
if err != nil {
grpclog.Infof("grpc: failed dns SRV record lookup due to %v.\n", err)
return nil
}
for _, s := range srvs {
lbAddrs, err := lookupHost(w.ctx, s.Target)
if err != nil {
grpclog.Warningf("grpc: failed load balancer address dns lookup due to %v.\n", err)
continue
}
for _, a := range lbAddrs {
a, ok := formatIP(a)
if !ok {
grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
continue
}
addr := a + ":" + strconv.Itoa(int(s.Port))
newAddrs[addr] = &Update{Addr: addr,
Metadata: AddrMetadataGRPCLB{AddrType: GRPCLB, ServerName: s.Target}}
}
}
return newAddrs
}
func (w *dnsWatcher) lookupHost() map[string]*Update {
newAddrs := make(map[string]*Update)
addrs, err := lookupHost(w.ctx, w.host)
if err != nil {
grpclog.Warningf("grpc: failed dns A record lookup due to %v.\n", err)
return nil
}
for _, a := range addrs {
a, ok := formatIP(a)
if !ok {
grpclog.Errorf("grpc: failed IP parsing due to %v.\n", err)
continue
}
addr := a + ":" + w.port
newAddrs[addr] = &Update{Addr: addr}
}
return newAddrs
}
func (w *dnsWatcher) lookup() []*Update {
newAddrs := w.lookupSRV()
if newAddrs == nil {
// If failed to get any balancer address (either no corresponding SRV for the
// target, or caused by failure during resolution/parsing of the balancer target),
// return any A record info available.
newAddrs = w.lookupHost()
}
result := w.compileUpdate(newAddrs)
w.curAddrs = newAddrs
return result
}
// Next returns the resolved address update(delta) for the target. If there's no
// change, it will sleep for 30 mins and try to resolve again after that.
func (w *dnsWatcher) Next() ([]*Update, error) {
for {
select {
case <-w.ctx.Done():
return nil, errWatcherClose
case <-w.t.C:
}
result := w.lookup()
// Next lookup should happen after an interval defined by w.r.freq.
w.t.Reset(w.r.freq)
if len(result) > 0 {
return result, nil
}
}
}
func (w *dnsWatcher) Close() {
w.cancel()
}

View File

@ -1,68 +0,0 @@
/*
*
* Copyright 2014 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package naming defines the naming API and related data structures for gRPC.
//
// This package is deprecated: please use package resolver instead.
package naming
// Operation defines the corresponding operations for a name resolution change.
//
// Deprecated: please use package resolver.
type Operation uint8
const (
// Add indicates a new address is added.
Add Operation = iota
// Delete indicates an existing address is deleted.
Delete
)
// Update defines a name resolution update. Notice that it is not valid having both
// empty string Addr and nil Metadata in an Update.
//
// Deprecated: please use package resolver.
type Update struct {
// Op indicates the operation of the update.
Op Operation
// Addr is the updated address. It is empty string if there is no address update.
Addr string
// Metadata is the updated metadata. It is nil if there is no metadata update.
// Metadata is not required for a custom naming implementation.
Metadata interface{}
}
// Resolver creates a Watcher for a target to track its resolution changes.
//
// Deprecated: please use package resolver.
type Resolver interface {
// Resolve creates a Watcher for target.
Resolve(target string) (Watcher, error)
}
// Watcher watches for the updates on the specified target.
//
// Deprecated: please use package resolver.
type Watcher interface {
// Next blocks until an update or error happens. It may return one or more
// updates. The first call should get the full set of the results. It should
// return an error if and only if Watcher cannot recover.
Next() ([]*Update, error)
// Close closes the Watcher.
Close()
}

View File

@ -20,80 +20,31 @@ package grpc
import ( import (
"context" "context"
"fmt"
"io" "io"
"sync" "sync"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/transport" "google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
// v2PickerWrapper wraps a balancer.Picker while providing the
// balancer.V2Picker API. It requires a pickerWrapper to generate errors
// including the latest connectionError. To be deleted when balancer.Picker is
// updated to the balancer.V2Picker API.
type v2PickerWrapper struct {
picker balancer.Picker
connErr *connErr
}
func (v *v2PickerWrapper) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
sc, done, err := v.picker.Pick(info.Ctx, info)
if err != nil {
if err == balancer.ErrTransientFailure {
return balancer.PickResult{}, balancer.TransientFailureError(fmt.Errorf("%v, latest connection error: %v", err, v.connErr.connectionError()))
}
return balancer.PickResult{}, err
}
return balancer.PickResult{SubConn: sc, Done: done}, nil
}
// pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick // pickerWrapper is a wrapper of balancer.Picker. It blocks on certain pick
// actions and unblock when there's a picker update. // actions and unblock when there's a picker update.
type pickerWrapper struct { type pickerWrapper struct {
mu sync.Mutex mu sync.Mutex
done bool done bool
blockingCh chan struct{} blockingCh chan struct{}
picker balancer.V2Picker picker balancer.Picker
// The latest connection error. TODO: remove when V1 picker is deprecated;
// balancer should be responsible for providing the error.
*connErr
}
type connErr struct {
mu sync.Mutex
err error
}
func (c *connErr) updateConnectionError(err error) {
c.mu.Lock()
c.err = err
c.mu.Unlock()
}
func (c *connErr) connectionError() error {
c.mu.Lock()
err := c.err
c.mu.Unlock()
return err
} }
func newPickerWrapper() *pickerWrapper { func newPickerWrapper() *pickerWrapper {
return &pickerWrapper{blockingCh: make(chan struct{}), connErr: &connErr{}} return &pickerWrapper{blockingCh: make(chan struct{})}
} }
// updatePicker is called by UpdateBalancerState. It unblocks all blocked pick. // updatePicker is called by UpdateBalancerState. It unblocks all blocked pick.
func (pw *pickerWrapper) updatePicker(p balancer.Picker) { func (pw *pickerWrapper) updatePicker(p balancer.Picker) {
pw.updatePickerV2(&v2PickerWrapper{picker: p, connErr: pw.connErr})
}
// updatePicker is called by UpdateBalancerState. It unblocks all blocked pick.
func (pw *pickerWrapper) updatePickerV2(p balancer.V2Picker) {
pw.mu.Lock() pw.mu.Lock()
if pw.done { if pw.done {
pw.mu.Unlock() pw.mu.Unlock()
@ -154,8 +105,6 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
var errStr string var errStr string
if lastPickErr != nil { if lastPickErr != nil {
errStr = "latest balancer error: " + lastPickErr.Error() errStr = "latest balancer error: " + lastPickErr.Error()
} else if connectionErr := pw.connectionError(); connectionErr != nil {
errStr = "latest connection error: " + connectionErr.Error()
} else { } else {
errStr = ctx.Err().Error() errStr = ctx.Err().Error()
} }
@ -180,23 +129,22 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
if err == balancer.ErrNoSubConnAvailable { if err == balancer.ErrNoSubConnAvailable {
continue continue
} }
if tfe, ok := err.(interface{ IsTransientFailure() bool }); ok && tfe.IsTransientFailure() { if _, ok := status.FromError(err); ok {
// Status error: end the RPC unconditionally with this status.
return nil, nil, err
}
// For all other errors, wait for ready RPCs should block and other
// RPCs should fail with unavailable.
if !failfast { if !failfast {
lastPickErr = err lastPickErr = err
continue continue
} }
return nil, nil, status.Error(codes.Unavailable, err.Error()) return nil, nil, status.Error(codes.Unavailable, err.Error())
} }
if _, ok := status.FromError(err); ok {
return nil, nil, err
}
// err is some other error.
return nil, nil, status.Error(codes.Unknown, err.Error())
}
acw, ok := pickResult.SubConn.(*acBalancerWrapper) acw, ok := pickResult.SubConn.(*acBalancerWrapper)
if !ok { if !ok {
grpclog.Error("subconn returned from pick is not *acBalancerWrapper") logger.Error("subconn returned from pick is not *acBalancerWrapper")
continue continue
} }
if t, ok := acw.getAddrConn().getReadyTransport(); ok { if t, ok := acw.getAddrConn().getReadyTransport(); ok {
@ -210,7 +158,7 @@ func (pw *pickerWrapper) pick(ctx context.Context, failfast bool, info balancer.
// DoneInfo with default value works. // DoneInfo with default value works.
pickResult.Done(balancer.DoneInfo{}) pickResult.Done(balancer.DoneInfo{})
} }
grpclog.Infof("blockingPicker: the picked transport is not ready, loop back to repick") logger.Infof("blockingPicker: the picked transport is not ready, loop back to repick")
// If ok == false, ac.state is not READY. // If ok == false, ac.state is not READY.
// A valid picker always returns READY subConn. This means the state of ac // A valid picker always returns READY subConn. This means the state of ac
// just changed, and picker will be updated shortly. // just changed, and picker will be updated shortly.

View File

@ -20,13 +20,10 @@ package grpc
import ( import (
"errors" "errors"
"fmt"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity" "google.golang.org/grpc/connectivity"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
) )
// PickFirstBalancerName is the name of the pick_first balancer. // PickFirstBalancerName is the name of the pick_first balancer.
@ -52,30 +49,16 @@ type pickfirstBalancer struct {
sc balancer.SubConn sc balancer.SubConn
} }
var _ balancer.V2Balancer = &pickfirstBalancer{} // Assert we implement v2
func (b *pickfirstBalancer) HandleResolvedAddrs(addrs []resolver.Address, err error) {
if err != nil {
b.ResolverError(err)
return
}
b.UpdateClientConnState(balancer.ClientConnState{ResolverState: resolver.State{Addresses: addrs}}) // Ignore error
}
func (b *pickfirstBalancer) HandleSubConnStateChange(sc balancer.SubConn, s connectivity.State) {
b.UpdateSubConnState(sc, balancer.SubConnState{ConnectivityState: s})
}
func (b *pickfirstBalancer) ResolverError(err error) { func (b *pickfirstBalancer) ResolverError(err error) {
switch b.state { switch b.state {
case connectivity.TransientFailure, connectivity.Idle, connectivity.Connecting: case connectivity.TransientFailure, connectivity.Idle, connectivity.Connecting:
// Set a failing picker if we don't have a good picker. // Set a failing picker if we don't have a good picker.
b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: status.Errorf(codes.Unavailable, "name resolver error: %v", err)}}, Picker: &picker{err: fmt.Errorf("name resolver error: %v", err)},
) })
} }
if grpclog.V(2) { if logger.V(2) {
grpclog.Infof("pickfirstBalancer: ResolverError called with error %v", err) logger.Infof("pickfirstBalancer: ResolverError called with error %v", err)
} }
} }
@ -88,32 +71,32 @@ func (b *pickfirstBalancer) UpdateClientConnState(cs balancer.ClientConnState) e
var err error var err error
b.sc, err = b.cc.NewSubConn(cs.ResolverState.Addresses, balancer.NewSubConnOptions{}) b.sc, err = b.cc.NewSubConn(cs.ResolverState.Addresses, balancer.NewSubConnOptions{})
if err != nil { if err != nil {
if grpclog.V(2) { if logger.V(2) {
grpclog.Errorf("pickfirstBalancer: failed to NewSubConn: %v", err) logger.Errorf("pickfirstBalancer: failed to NewSubConn: %v", err)
} }
b.state = connectivity.TransientFailure b.state = connectivity.TransientFailure
b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure, b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.TransientFailure,
Picker: &picker{err: status.Errorf(codes.Unavailable, "error creating connection: %v", err)}}, Picker: &picker{err: fmt.Errorf("error creating connection: %v", err)},
) })
return balancer.ErrBadResolverState return balancer.ErrBadResolverState
} }
b.state = connectivity.Idle b.state = connectivity.Idle
b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Idle, Picker: &picker{result: balancer.PickResult{SubConn: b.sc}}}) b.cc.UpdateState(balancer.State{ConnectivityState: connectivity.Idle, Picker: &picker{result: balancer.PickResult{SubConn: b.sc}}})
b.sc.Connect() b.sc.Connect()
} else { } else {
b.sc.UpdateAddresses(cs.ResolverState.Addresses) b.cc.UpdateAddresses(b.sc, cs.ResolverState.Addresses)
b.sc.Connect() b.sc.Connect()
} }
return nil return nil
} }
func (b *pickfirstBalancer) UpdateSubConnState(sc balancer.SubConn, s balancer.SubConnState) { func (b *pickfirstBalancer) UpdateSubConnState(sc balancer.SubConn, s balancer.SubConnState) {
if grpclog.V(2) { if logger.V(2) {
grpclog.Infof("pickfirstBalancer: HandleSubConnStateChange: %p, %v", sc, s) logger.Infof("pickfirstBalancer: UpdateSubConnState: %p, %v", sc, s)
} }
if b.sc != sc { if b.sc != sc {
if grpclog.V(2) { if logger.V(2) {
grpclog.Infof("pickfirstBalancer: ignored state change because sc is not recognized") logger.Infof("pickfirstBalancer: ignored state change because sc is not recognized")
} }
return return
} }
@ -129,15 +112,9 @@ func (b *pickfirstBalancer) UpdateSubConnState(sc balancer.SubConn, s balancer.S
case connectivity.Connecting: case connectivity.Connecting:
b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState, Picker: &picker{err: balancer.ErrNoSubConnAvailable}}) b.cc.UpdateState(balancer.State{ConnectivityState: s.ConnectivityState, Picker: &picker{err: balancer.ErrNoSubConnAvailable}})
case connectivity.TransientFailure: case connectivity.TransientFailure:
err := balancer.ErrTransientFailure
// TODO: this can be unconditional after the V1 API is removed, as
// SubConnState will always contain a connection error.
if s.ConnectionError != nil {
err = balancer.TransientFailureError(s.ConnectionError)
}
b.cc.UpdateState(balancer.State{ b.cc.UpdateState(balancer.State{
ConnectivityState: s.ConnectivityState, ConnectivityState: s.ConnectivityState,
Picker: &picker{err: err}, Picker: &picker{err: s.ConnectionError},
}) })
} }
} }

View File

@ -25,7 +25,10 @@ import (
// PreparedMsg is responsible for creating a Marshalled and Compressed object. // PreparedMsg is responsible for creating a Marshalled and Compressed object.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type PreparedMsg struct { type PreparedMsg struct {
// Struct for preparing msg before sending them // Struct for preparing msg before sending them
encodedData []byte encodedData []byte

View File

@ -85,12 +85,19 @@ const (
Backend AddressType = iota Backend AddressType = iota
// GRPCLB indicates the address is for a grpclb load balancer. // GRPCLB indicates the address is for a grpclb load balancer.
// //
// Deprecated: use Attributes in Address instead. // Deprecated: to select the GRPCLB load balancing policy, use a service
// config with a corresponding loadBalancingConfig. To supply balancer
// addresses to the GRPCLB load balancing policy, set State.Attributes
// using balancer/grpclb/state.Set.
GRPCLB GRPCLB
) )
// Address represents a server the client connects to. // Address represents a server the client connects to.
// This is the EXPERIMENTAL API and may be changed or extended in the future. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type Address struct { type Address struct {
// Addr is the server address on which a connection will be established. // Addr is the server address on which a connection will be established.
Addr string Addr string
@ -174,7 +181,7 @@ type State struct {
// gRPC to add new methods to this interface. // gRPC to add new methods to this interface.
type ClientConn interface { type ClientConn interface {
// UpdateState updates the state of the ClientConn appropriately. // UpdateState updates the state of the ClientConn appropriately.
UpdateState(State) UpdateState(State) error
// ReportError notifies the ClientConn that the Resolver encountered an // ReportError notifies the ClientConn that the Resolver encountered an
// error. The ClientConn will notify the load balancer and begin calling // error. The ClientConn will notify the load balancer and begin calling
// ResolveNow on the Resolver with exponential backoff. // ResolveNow on the Resolver with exponential backoff.

View File

@ -22,11 +22,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"time"
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
@ -41,37 +39,6 @@ type ccResolverWrapper struct {
resolver resolver.Resolver resolver resolver.Resolver
done *grpcsync.Event done *grpcsync.Event
curState resolver.State curState resolver.State
pollingMu sync.Mutex
polling chan struct{}
}
// split2 returns the values from strings.SplitN(s, sep, 2).
// If sep is not found, it returns ("", "", false) instead.
func split2(s, sep string) (string, string, bool) {
spl := strings.SplitN(s, sep, 2)
if len(spl) < 2 {
return "", "", false
}
return spl[0], spl[1], true
}
// parseTarget splits target into a struct containing scheme, authority and
// endpoint.
//
// If target is not a valid scheme://authority/endpoint, it returns {Endpoint:
// target}.
func parseTarget(target string) (ret resolver.Target) {
var ok bool
ret.Scheme, ret.Endpoint, ok = split2(target, "://")
if !ok {
return resolver.Target{Endpoint: target}
}
ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/")
if !ok {
return resolver.Target{Endpoint: target}
}
return ret
} }
// newCCResolverWrapper uses the resolver.Builder to build a Resolver and // newCCResolverWrapper uses the resolver.Builder to build a Resolver and
@ -122,73 +89,27 @@ func (ccr *ccResolverWrapper) close() {
ccr.resolverMu.Unlock() ccr.resolverMu.Unlock()
} }
// poll begins or ends asynchronous polling of the resolver based on whether func (ccr *ccResolverWrapper) UpdateState(s resolver.State) error {
// err is ErrBadResolverState.
func (ccr *ccResolverWrapper) poll(err error) {
ccr.pollingMu.Lock()
defer ccr.pollingMu.Unlock()
if err != balancer.ErrBadResolverState {
// stop polling
if ccr.polling != nil {
close(ccr.polling)
ccr.polling = nil
}
return
}
if ccr.polling != nil {
// already polling
return
}
p := make(chan struct{})
ccr.polling = p
go func() {
for i := 0; ; i++ {
ccr.resolveNow(resolver.ResolveNowOptions{})
t := time.NewTimer(ccr.cc.dopts.resolveNowBackoff(i))
select {
case <-p:
t.Stop()
return
case <-ccr.done.Done():
// Resolver has been closed.
t.Stop()
return
case <-t.C:
select {
case <-p:
return
default:
}
// Timer expired; re-resolve.
}
}
}()
}
func (ccr *ccResolverWrapper) UpdateState(s resolver.State) {
if ccr.done.HasFired() { if ccr.done.HasFired() {
return return nil
} }
grpclog.Infof("ccResolverWrapper: sending update to cc: %v", s) channelz.Infof(logger, ccr.cc.channelzID, "ccResolverWrapper: sending update to cc: %v", s)
if channelz.IsOn() { if channelz.IsOn() {
ccr.addChannelzTraceEvent(s) ccr.addChannelzTraceEvent(s)
} }
ccr.curState = s ccr.curState = s
ccr.poll(ccr.cc.updateResolverState(ccr.curState, nil)) if err := ccr.cc.updateResolverState(ccr.curState, nil); err == balancer.ErrBadResolverState {
return balancer.ErrBadResolverState
}
return nil
} }
func (ccr *ccResolverWrapper) ReportError(err error) { func (ccr *ccResolverWrapper) ReportError(err error) {
if ccr.done.HasFired() { if ccr.done.HasFired() {
return return
} }
grpclog.Warningf("ccResolverWrapper: reporting error to cc: %v", err) channelz.Warningf(logger, ccr.cc.channelzID, "ccResolverWrapper: reporting error to cc: %v", err)
if channelz.IsOn() { ccr.cc.updateResolverState(resolver.State{}, err)
channelz.AddTraceEvent(ccr.cc.channelzID, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Resolver reported error: %v", err),
Severity: channelz.CtWarning,
})
}
ccr.poll(ccr.cc.updateResolverState(resolver.State{}, err))
} }
// NewAddress is called by the resolver implementation to send addresses to gRPC. // NewAddress is called by the resolver implementation to send addresses to gRPC.
@ -196,12 +117,12 @@ func (ccr *ccResolverWrapper) NewAddress(addrs []resolver.Address) {
if ccr.done.HasFired() { if ccr.done.HasFired() {
return return
} }
grpclog.Infof("ccResolverWrapper: sending new addresses to cc: %v", addrs) channelz.Infof(logger, ccr.cc.channelzID, "ccResolverWrapper: sending new addresses to cc: %v", addrs)
if channelz.IsOn() { if channelz.IsOn() {
ccr.addChannelzTraceEvent(resolver.State{Addresses: addrs, ServiceConfig: ccr.curState.ServiceConfig}) ccr.addChannelzTraceEvent(resolver.State{Addresses: addrs, ServiceConfig: ccr.curState.ServiceConfig})
} }
ccr.curState.Addresses = addrs ccr.curState.Addresses = addrs
ccr.poll(ccr.cc.updateResolverState(ccr.curState, nil)) ccr.cc.updateResolverState(ccr.curState, nil)
} }
// NewServiceConfig is called by the resolver implementation to send service // NewServiceConfig is called by the resolver implementation to send service
@ -210,28 +131,21 @@ func (ccr *ccResolverWrapper) NewServiceConfig(sc string) {
if ccr.done.HasFired() { if ccr.done.HasFired() {
return return
} }
grpclog.Infof("ccResolverWrapper: got new service config: %v", sc) channelz.Infof(logger, ccr.cc.channelzID, "ccResolverWrapper: got new service config: %v", sc)
if ccr.cc.dopts.disableServiceConfig { if ccr.cc.dopts.disableServiceConfig {
grpclog.Infof("Service config lookups disabled; ignoring config") channelz.Info(logger, ccr.cc.channelzID, "Service config lookups disabled; ignoring config")
return return
} }
scpr := parseServiceConfig(sc) scpr := parseServiceConfig(sc)
if scpr.Err != nil { if scpr.Err != nil {
grpclog.Warningf("ccResolverWrapper: error parsing service config: %v", scpr.Err) channelz.Warningf(logger, ccr.cc.channelzID, "ccResolverWrapper: error parsing service config: %v", scpr.Err)
if channelz.IsOn() {
channelz.AddTraceEvent(ccr.cc.channelzID, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Error parsing service config: %v", scpr.Err),
Severity: channelz.CtWarning,
})
}
ccr.poll(balancer.ErrBadResolverState)
return return
} }
if channelz.IsOn() { if channelz.IsOn() {
ccr.addChannelzTraceEvent(resolver.State{Addresses: ccr.curState.Addresses, ServiceConfig: scpr}) ccr.addChannelzTraceEvent(resolver.State{Addresses: ccr.curState.Addresses, ServiceConfig: scpr})
} }
ccr.curState.ServiceConfig = scpr ccr.curState.ServiceConfig = scpr
ccr.poll(ccr.cc.updateResolverState(ccr.curState, nil)) ccr.cc.updateResolverState(ccr.curState, nil)
} }
func (ccr *ccResolverWrapper) ParseServiceConfig(scJSON string) *serviceconfig.ParseResult { func (ccr *ccResolverWrapper) ParseServiceConfig(scJSON string) *serviceconfig.ParseResult {
@ -256,8 +170,8 @@ func (ccr *ccResolverWrapper) addChannelzTraceEvent(s resolver.State) {
} else if len(ccr.curState.Addresses) == 0 && len(s.Addresses) > 0 { } else if len(ccr.curState.Addresses) == 0 && len(s.Addresses) > 0 {
updates = append(updates, "resolver returned new addresses") updates = append(updates, "resolver returned new addresses")
} }
channelz.AddTraceEvent(ccr.cc.channelzID, &channelz.TraceEventDesc{ channelz.AddTraceEvent(logger, ccr.cc.channelzID, 0, &channelz.TraceEventDesc{
Desc: fmt.Sprintf("Resolver state updated: %+v (%v)", s, strings.Join(updates, "; ")), Desc: fmt.Sprintf("Resolver state updated: %+v (%v)", s, strings.Join(updates, "; ")),
Severity: channelz.CtINFO, Severity: channelz.CtInfo,
}) })
} }

View File

@ -27,7 +27,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
"net/url"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -155,7 +154,6 @@ func (d *gzipDecompressor) Type() string {
type callInfo struct { type callInfo struct {
compressorType string compressorType string
failFast bool failFast bool
stream ClientStream
maxReceiveMessageSize *int maxReceiveMessageSize *int
maxSendMessageSize *int maxSendMessageSize *int
creds credentials.PerRPCCredentials creds credentials.PerRPCCredentials
@ -180,7 +178,7 @@ type CallOption interface {
// after is called after the call has completed. after cannot return an // after is called after the call has completed. after cannot return an
// error, so any failures should be reported via output parameters. // error, so any failures should be reported via output parameters.
after(*callInfo) after(*callInfo, *csAttempt)
} }
// EmptyCallOption does not alter the Call configuration. // EmptyCallOption does not alter the Call configuration.
@ -189,7 +187,7 @@ type CallOption interface {
type EmptyCallOption struct{} type EmptyCallOption struct{}
func (EmptyCallOption) before(*callInfo) error { return nil } func (EmptyCallOption) before(*callInfo) error { return nil }
func (EmptyCallOption) after(*callInfo) {} func (EmptyCallOption) after(*callInfo, *csAttempt) {}
// Header returns a CallOptions that retrieves the header metadata // Header returns a CallOptions that retrieves the header metadata
// for a unary RPC. // for a unary RPC.
@ -199,16 +197,18 @@ func Header(md *metadata.MD) CallOption {
// HeaderCallOption is a CallOption for collecting response header metadata. // HeaderCallOption is a CallOption for collecting response header metadata.
// The metadata field will be populated *after* the RPC completes. // The metadata field will be populated *after* the RPC completes.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type HeaderCallOption struct { type HeaderCallOption struct {
HeaderAddr *metadata.MD HeaderAddr *metadata.MD
} }
func (o HeaderCallOption) before(c *callInfo) error { return nil } func (o HeaderCallOption) before(c *callInfo) error { return nil }
func (o HeaderCallOption) after(c *callInfo) { func (o HeaderCallOption) after(c *callInfo, attempt *csAttempt) {
if c.stream != nil { *o.HeaderAddr, _ = attempt.s.Header()
*o.HeaderAddr, _ = c.stream.Header()
}
} }
// Trailer returns a CallOptions that retrieves the trailer metadata // Trailer returns a CallOptions that retrieves the trailer metadata
@ -219,16 +219,18 @@ func Trailer(md *metadata.MD) CallOption {
// TrailerCallOption is a CallOption for collecting response trailer metadata. // TrailerCallOption is a CallOption for collecting response trailer metadata.
// The metadata field will be populated *after* the RPC completes. // The metadata field will be populated *after* the RPC completes.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type TrailerCallOption struct { type TrailerCallOption struct {
TrailerAddr *metadata.MD TrailerAddr *metadata.MD
} }
func (o TrailerCallOption) before(c *callInfo) error { return nil } func (o TrailerCallOption) before(c *callInfo) error { return nil }
func (o TrailerCallOption) after(c *callInfo) { func (o TrailerCallOption) after(c *callInfo, attempt *csAttempt) {
if c.stream != nil { *o.TrailerAddr = attempt.s.Trailer()
*o.TrailerAddr = c.stream.Trailer()
}
} }
// Peer returns a CallOption that retrieves peer information for a unary RPC. // Peer returns a CallOption that retrieves peer information for a unary RPC.
@ -239,18 +241,20 @@ func Peer(p *peer.Peer) CallOption {
// PeerCallOption is a CallOption for collecting the identity of the remote // PeerCallOption is a CallOption for collecting the identity of the remote
// peer. The peer field will be populated *after* the RPC completes. // peer. The peer field will be populated *after* the RPC completes.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type PeerCallOption struct { type PeerCallOption struct {
PeerAddr *peer.Peer PeerAddr *peer.Peer
} }
func (o PeerCallOption) before(c *callInfo) error { return nil } func (o PeerCallOption) before(c *callInfo) error { return nil }
func (o PeerCallOption) after(c *callInfo) { func (o PeerCallOption) after(c *callInfo, attempt *csAttempt) {
if c.stream != nil { if x, ok := peer.FromContext(attempt.s.Context()); ok {
if x, ok := peer.FromContext(c.stream.Context()); ok {
*o.PeerAddr = *x *o.PeerAddr = *x
} }
}
} }
// WaitForReady configures the action to take when an RPC is attempted on broken // WaitForReady configures the action to take when an RPC is attempted on broken
@ -276,7 +280,11 @@ func FailFast(failFast bool) CallOption {
// FailFastCallOption is a CallOption for indicating whether an RPC should fail // FailFastCallOption is a CallOption for indicating whether an RPC should fail
// fast or not. // fast or not.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type FailFastCallOption struct { type FailFastCallOption struct {
FailFast bool FailFast bool
} }
@ -285,16 +293,21 @@ func (o FailFastCallOption) before(c *callInfo) error {
c.failFast = o.FailFast c.failFast = o.FailFast
return nil return nil
} }
func (o FailFastCallOption) after(c *callInfo) {} func (o FailFastCallOption) after(c *callInfo, attempt *csAttempt) {}
// MaxCallRecvMsgSize returns a CallOption which sets the maximum message size the client can receive. // MaxCallRecvMsgSize returns a CallOption which sets the maximum message size
func MaxCallRecvMsgSize(s int) CallOption { // in bytes the client can receive.
return MaxRecvMsgSizeCallOption{MaxRecvMsgSize: s} func MaxCallRecvMsgSize(bytes int) CallOption {
return MaxRecvMsgSizeCallOption{MaxRecvMsgSize: bytes}
} }
// MaxRecvMsgSizeCallOption is a CallOption that indicates the maximum message // MaxRecvMsgSizeCallOption is a CallOption that indicates the maximum message
// size the client can receive. // size in bytes the client can receive.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type MaxRecvMsgSizeCallOption struct { type MaxRecvMsgSizeCallOption struct {
MaxRecvMsgSize int MaxRecvMsgSize int
} }
@ -303,16 +316,21 @@ func (o MaxRecvMsgSizeCallOption) before(c *callInfo) error {
c.maxReceiveMessageSize = &o.MaxRecvMsgSize c.maxReceiveMessageSize = &o.MaxRecvMsgSize
return nil return nil
} }
func (o MaxRecvMsgSizeCallOption) after(c *callInfo) {} func (o MaxRecvMsgSizeCallOption) after(c *callInfo, attempt *csAttempt) {}
// MaxCallSendMsgSize returns a CallOption which sets the maximum message size the client can send. // MaxCallSendMsgSize returns a CallOption which sets the maximum message size
func MaxCallSendMsgSize(s int) CallOption { // in bytes the client can send.
return MaxSendMsgSizeCallOption{MaxSendMsgSize: s} func MaxCallSendMsgSize(bytes int) CallOption {
return MaxSendMsgSizeCallOption{MaxSendMsgSize: bytes}
} }
// MaxSendMsgSizeCallOption is a CallOption that indicates the maximum message // MaxSendMsgSizeCallOption is a CallOption that indicates the maximum message
// size the client can send. // size in bytes the client can send.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type MaxSendMsgSizeCallOption struct { type MaxSendMsgSizeCallOption struct {
MaxSendMsgSize int MaxSendMsgSize int
} }
@ -321,7 +339,7 @@ func (o MaxSendMsgSizeCallOption) before(c *callInfo) error {
c.maxSendMessageSize = &o.MaxSendMsgSize c.maxSendMessageSize = &o.MaxSendMsgSize
return nil return nil
} }
func (o MaxSendMsgSizeCallOption) after(c *callInfo) {} func (o MaxSendMsgSizeCallOption) after(c *callInfo, attempt *csAttempt) {}
// PerRPCCredentials returns a CallOption that sets credentials.PerRPCCredentials // PerRPCCredentials returns a CallOption that sets credentials.PerRPCCredentials
// for a call. // for a call.
@ -331,7 +349,11 @@ func PerRPCCredentials(creds credentials.PerRPCCredentials) CallOption {
// PerRPCCredsCallOption is a CallOption that indicates the per-RPC // PerRPCCredsCallOption is a CallOption that indicates the per-RPC
// credentials to use for the call. // credentials to use for the call.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type PerRPCCredsCallOption struct { type PerRPCCredsCallOption struct {
Creds credentials.PerRPCCredentials Creds credentials.PerRPCCredentials
} }
@ -340,19 +362,26 @@ func (o PerRPCCredsCallOption) before(c *callInfo) error {
c.creds = o.Creds c.creds = o.Creds
return nil return nil
} }
func (o PerRPCCredsCallOption) after(c *callInfo) {} func (o PerRPCCredsCallOption) after(c *callInfo, attempt *csAttempt) {}
// UseCompressor returns a CallOption which sets the compressor used when // UseCompressor returns a CallOption which sets the compressor used when
// sending the request. If WithCompressor is also set, UseCompressor has // sending the request. If WithCompressor is also set, UseCompressor has
// higher priority. // higher priority.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func UseCompressor(name string) CallOption { func UseCompressor(name string) CallOption {
return CompressorCallOption{CompressorType: name} return CompressorCallOption{CompressorType: name}
} }
// CompressorCallOption is a CallOption that indicates the compressor to use. // CompressorCallOption is a CallOption that indicates the compressor to use.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type CompressorCallOption struct { type CompressorCallOption struct {
CompressorType string CompressorType string
} }
@ -361,7 +390,7 @@ func (o CompressorCallOption) before(c *callInfo) error {
c.compressorType = o.CompressorType c.compressorType = o.CompressorType
return nil return nil
} }
func (o CompressorCallOption) after(c *callInfo) {} func (o CompressorCallOption) after(c *callInfo, attempt *csAttempt) {}
// CallContentSubtype returns a CallOption that will set the content-subtype // CallContentSubtype returns a CallOption that will set the content-subtype
// for a call. For example, if content-subtype is "json", the Content-Type over // for a call. For example, if content-subtype is "json", the Content-Type over
@ -385,7 +414,11 @@ func CallContentSubtype(contentSubtype string) CallOption {
// ContentSubtypeCallOption is a CallOption that indicates the content-subtype // ContentSubtypeCallOption is a CallOption that indicates the content-subtype
// used for marshaling messages. // used for marshaling messages.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type ContentSubtypeCallOption struct { type ContentSubtypeCallOption struct {
ContentSubtype string ContentSubtype string
} }
@ -394,11 +427,12 @@ func (o ContentSubtypeCallOption) before(c *callInfo) error {
c.contentSubtype = o.ContentSubtype c.contentSubtype = o.ContentSubtype
return nil return nil
} }
func (o ContentSubtypeCallOption) after(c *callInfo) {} func (o ContentSubtypeCallOption) after(c *callInfo, attempt *csAttempt) {}
// ForceCodec returns a CallOption that will set the given Codec to be // ForceCodec returns a CallOption that will set codec to be used for all
// used for all request and response messages for a call. The result of calling // request and response messages for a call. The result of calling Name() will
// String() will be used as the content-subtype in a case-insensitive manner. // be used as the content-subtype after converting to lowercase, unless
// CallContentSubtype is also used.
// //
// See Content-Type on // See Content-Type on
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
@ -409,7 +443,10 @@ func (o ContentSubtypeCallOption) after(c *callInfo) {}
// This function is provided for advanced users; prefer to use only // This function is provided for advanced users; prefer to use only
// CallContentSubtype to select a registered codec instead. // CallContentSubtype to select a registered codec instead.
// //
// This is an EXPERIMENTAL API. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ForceCodec(codec encoding.Codec) CallOption { func ForceCodec(codec encoding.Codec) CallOption {
return ForceCodecCallOption{Codec: codec} return ForceCodecCallOption{Codec: codec}
} }
@ -417,7 +454,10 @@ func ForceCodec(codec encoding.Codec) CallOption {
// ForceCodecCallOption is a CallOption that indicates the codec used for // ForceCodecCallOption is a CallOption that indicates the codec used for
// marshaling messages. // marshaling messages.
// //
// This is an EXPERIMENTAL API. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type ForceCodecCallOption struct { type ForceCodecCallOption struct {
Codec encoding.Codec Codec encoding.Codec
} }
@ -426,7 +466,7 @@ func (o ForceCodecCallOption) before(c *callInfo) error {
c.codec = o.Codec c.codec = o.Codec
return nil return nil
} }
func (o ForceCodecCallOption) after(c *callInfo) {} func (o ForceCodecCallOption) after(c *callInfo, attempt *csAttempt) {}
// CallCustomCodec behaves like ForceCodec, but accepts a grpc.Codec instead of // CallCustomCodec behaves like ForceCodec, but accepts a grpc.Codec instead of
// an encoding.Codec. // an encoding.Codec.
@ -439,7 +479,10 @@ func CallCustomCodec(codec Codec) CallOption {
// CustomCodecCallOption is a CallOption that indicates the codec used for // CustomCodecCallOption is a CallOption that indicates the codec used for
// marshaling messages. // marshaling messages.
// //
// This is an EXPERIMENTAL API. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type CustomCodecCallOption struct { type CustomCodecCallOption struct {
Codec Codec Codec Codec
} }
@ -448,19 +491,26 @@ func (o CustomCodecCallOption) before(c *callInfo) error {
c.codec = o.Codec c.codec = o.Codec
return nil return nil
} }
func (o CustomCodecCallOption) after(c *callInfo) {} func (o CustomCodecCallOption) after(c *callInfo, attempt *csAttempt) {}
// MaxRetryRPCBufferSize returns a CallOption that limits the amount of memory // MaxRetryRPCBufferSize returns a CallOption that limits the amount of memory
// used for buffering this RPC's requests for retry purposes. // used for buffering this RPC's requests for retry purposes.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func MaxRetryRPCBufferSize(bytes int) CallOption { func MaxRetryRPCBufferSize(bytes int) CallOption {
return MaxRetryRPCBufferSizeCallOption{bytes} return MaxRetryRPCBufferSizeCallOption{bytes}
} }
// MaxRetryRPCBufferSizeCallOption is a CallOption indicating the amount of // MaxRetryRPCBufferSizeCallOption is a CallOption indicating the amount of
// memory to be used for caching this RPC for retry purposes. // memory to be used for caching this RPC for retry purposes.
// This is an EXPERIMENTAL API. //
// Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type MaxRetryRPCBufferSizeCallOption struct { type MaxRetryRPCBufferSizeCallOption struct {
MaxRetryRPCBufferSize int MaxRetryRPCBufferSize int
} }
@ -469,7 +519,7 @@ func (o MaxRetryRPCBufferSizeCallOption) before(c *callInfo) error {
c.maxRetryRPCBufferSize = o.MaxRetryRPCBufferSize c.maxRetryRPCBufferSize = o.MaxRetryRPCBufferSize
return nil return nil
} }
func (o MaxRetryRPCBufferSizeCallOption) after(c *callInfo) {} func (o MaxRetryRPCBufferSizeCallOption) after(c *callInfo, attempt *csAttempt) {}
// The format of the payload: compressed or not? // The format of the payload: compressed or not?
type payloadFormat uint8 type payloadFormat uint8
@ -804,7 +854,17 @@ func toRPCErr(err error) error {
// setCallInfoCodec should only be called after CallOptions have been applied. // setCallInfoCodec should only be called after CallOptions have been applied.
func setCallInfoCodec(c *callInfo) error { func setCallInfoCodec(c *callInfo) error {
if c.codec != nil { if c.codec != nil {
// codec was already set by a CallOption; use it. // codec was already set by a CallOption; use it, but set the content
// subtype if it is not set.
if c.contentSubtype == "" {
// c.codec is a baseCodec to hide the difference between grpc.Codec and
// encoding.Codec (Name vs. String method name). We only support
// setting content subtype from encoding.Codec to avoid a behavior
// change with the deprecated version.
if ec, ok := c.codec.(encoding.Codec); ok {
c.contentSubtype = strings.ToLower(ec.Name())
}
}
return nil return nil
} }
@ -822,40 +882,6 @@ func setCallInfoCodec(c *callInfo) error {
return nil return nil
} }
// parseDialTarget returns the network and address to pass to dialer
func parseDialTarget(target string) (net string, addr string) {
net = "tcp"
m1 := strings.Index(target, ":")
m2 := strings.Index(target, ":/")
// handle unix:addr which will fail with url.Parse
if m1 >= 0 && m2 < 0 {
if n := target[0:m1]; n == "unix" {
net = n
addr = target[m1+1:]
return net, addr
}
}
if m2 >= 0 {
t, err := url.Parse(target)
if err != nil {
return net, target
}
scheme := t.Scheme
addr = t.Path
if scheme == "unix" {
net = scheme
if addr == "" {
addr = t.Host
}
return net, addr
}
}
return net, target
}
// channelzData is used to store channelz related data for ClientConn, addrConn and Server. // channelzData is used to store channelz related data for ClientConn, addrConn and Server.
// These fields cannot be embedded in the original structs (e.g. ClientConn), since to do atomic // These fields cannot be embedded in the original structs (e.g. ClientConn), since to do atomic
// operation on int64 variable on 32-bit machine, user is responsible to enforce memory alignment. // operation on int64 variable on 32-bit machine, user is responsible to enforce memory alignment.
@ -871,10 +897,9 @@ type channelzData struct {
// The SupportPackageIsVersion variables are referenced from generated protocol // The SupportPackageIsVersion variables are referenced from generated protocol
// buffer files to ensure compatibility with the gRPC version used. The latest // buffer files to ensure compatibility with the gRPC version used. The latest
// support package version is 6. // support package version is 7.
// //
// Older versions are kept for compatibility. They may be removed if // Older versions are kept for compatibility.
// compatibility cannot be maintained.
// //
// These constants should not be referenced from any other code. // These constants should not be referenced from any other code.
const ( const (
@ -882,6 +907,7 @@ const (
SupportPackageIsVersion4 = true SupportPackageIsVersion4 = true
SupportPackageIsVersion5 = true SupportPackageIsVersion5 = true
SupportPackageIsVersion6 = true SupportPackageIsVersion6 = true
SupportPackageIsVersion7 = true
) )
const grpcUA = "grpc-go/" + Version const grpcUA = "grpc-go/" + Version

View File

@ -40,8 +40,10 @@ import (
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"
"google.golang.org/grpc/encoding/proto" "google.golang.org/grpc/encoding/proto"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/binarylog" "google.golang.org/grpc/internal/binarylog"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/internal/grpcsync" "google.golang.org/grpc/internal/grpcsync"
"google.golang.org/grpc/internal/transport" "google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
@ -55,9 +57,26 @@ import (
const ( const (
defaultServerMaxReceiveMessageSize = 1024 * 1024 * 4 defaultServerMaxReceiveMessageSize = 1024 * 1024 * 4
defaultServerMaxSendMessageSize = math.MaxInt32 defaultServerMaxSendMessageSize = math.MaxInt32
// Server transports are tracked in a map which is keyed on listener
// address. For regular gRPC traffic, connections are accepted in Serve()
// through a call to Accept(), and we use the actual listener address as key
// when we add it to the map. But for connections received through
// ServeHTTP(), we do not have a listener and hence use this dummy value.
listenerAddressForServeHTTP = "listenerAddressForServeHTTP"
) )
func init() {
internal.GetServerCredentials = func(srv *Server) credentials.TransportCredentials {
return srv.opts.creds
}
internal.DrainServerTransports = func(srv *Server, addr string) {
srv.drainServerTransports(addr)
}
}
var statusOK = status.New(codes.OK, "") var statusOK = status.New(codes.OK, "")
var logger = grpclog.Component("core")
type methodHandler func(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor UnaryServerInterceptor) (interface{}, error) type methodHandler func(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor UnaryServerInterceptor) (interface{}, error)
@ -78,26 +97,36 @@ type ServiceDesc struct {
Metadata interface{} Metadata interface{}
} }
// service consists of the information of the server serving this service and // serviceInfo wraps information about a service. It is very similar to
// the methods in this service. // ServiceDesc and is constructed from it for internal purposes.
type service struct { type serviceInfo struct {
server interface{} // the server for service methods // Contains the implementation for the methods in this service.
md map[string]*MethodDesc serviceImpl interface{}
sd map[string]*StreamDesc methods map[string]*MethodDesc
streams map[string]*StreamDesc
mdata interface{} mdata interface{}
} }
type serverWorkerData struct {
st transport.ServerTransport
wg *sync.WaitGroup
stream *transport.Stream
}
// Server is a gRPC server to serve RPC requests. // Server is a gRPC server to serve RPC requests.
type Server struct { type Server struct {
opts serverOptions opts serverOptions
mu sync.Mutex // guards following mu sync.Mutex // guards following
lis map[net.Listener]bool lis map[net.Listener]bool
conns map[transport.ServerTransport]bool // conns contains all active server transports. It is a map keyed on a
// listener address with the value being the set of active transports
// belonging to that listener.
conns map[string]map[transport.ServerTransport]bool
serve bool serve bool
drain bool drain bool
cv *sync.Cond // signaled when connections close for GracefulStop cv *sync.Cond // signaled when connections close for GracefulStop
m map[string]*service // service name -> service info services map[string]*serviceInfo // service name -> service info
events trace.EventLog events trace.EventLog
quit *grpcsync.Event quit *grpcsync.Event
@ -107,6 +136,8 @@ type Server struct {
channelzID int64 // channelz unique identification number channelzID int64 // channelz unique identification number
czData *channelzData czData *channelzData
serverWorkerChannels []chan *serverWorkerData
} }
type serverOptions struct { type serverOptions struct {
@ -116,6 +147,8 @@ type serverOptions struct {
dc Decompressor dc Decompressor
unaryInt UnaryServerInterceptor unaryInt UnaryServerInterceptor
streamInt StreamServerInterceptor streamInt StreamServerInterceptor
chainUnaryInts []UnaryServerInterceptor
chainStreamInts []StreamServerInterceptor
inTapHandle tap.ServerInHandle inTapHandle tap.ServerInHandle
statsHandler stats.Handler statsHandler stats.Handler
maxConcurrentStreams uint32 maxConcurrentStreams uint32
@ -131,6 +164,7 @@ type serverOptions struct {
connectionTimeout time.Duration connectionTimeout time.Duration
maxHeaderListSize *uint32 maxHeaderListSize *uint32
headerTableSize *uint32 headerTableSize *uint32
numServerWorkers uint32
} }
var defaultServerOptions = serverOptions{ var defaultServerOptions = serverOptions{
@ -149,7 +183,10 @@ type ServerOption interface {
// EmptyServerOption does not alter the server configuration. It can be embedded // EmptyServerOption does not alter the server configuration. It can be embedded
// in another structure to build custom server options. // in another structure to build custom server options.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type EmptyServerOption struct{} type EmptyServerOption struct{}
func (EmptyServerOption) apply(*serverOptions) {} func (EmptyServerOption) apply(*serverOptions) {}
@ -211,7 +248,7 @@ func InitialConnWindowSize(s int32) ServerOption {
// KeepaliveParams returns a ServerOption that sets keepalive and max-age parameters for the server. // KeepaliveParams returns a ServerOption that sets keepalive and max-age parameters for the server.
func KeepaliveParams(kp keepalive.ServerParameters) ServerOption { func KeepaliveParams(kp keepalive.ServerParameters) ServerOption {
if kp.Time > 0 && kp.Time < time.Second { if kp.Time > 0 && kp.Time < time.Second {
grpclog.Warning("Adjusting keepalive ping interval to minimum period of 1s") logger.Warning("Adjusting keepalive ping interval to minimum period of 1s")
kp.Time = time.Second kp.Time = time.Second
} }
@ -230,19 +267,55 @@ func KeepaliveEnforcementPolicy(kep keepalive.EnforcementPolicy) ServerOption {
// CustomCodec returns a ServerOption that sets a codec for message marshaling and unmarshaling. // CustomCodec returns a ServerOption that sets a codec for message marshaling and unmarshaling.
// //
// This will override any lookups by content-subtype for Codecs registered with RegisterCodec. // This will override any lookups by content-subtype for Codecs registered with RegisterCodec.
//
// Deprecated: register codecs using encoding.RegisterCodec. The server will
// automatically use registered codecs based on the incoming requests' headers.
// See also
// https://github.com/grpc/grpc-go/blob/master/Documentation/encoding.md#using-a-codec.
// Will be supported throughout 1.x.
func CustomCodec(codec Codec) ServerOption { func CustomCodec(codec Codec) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
o.codec = codec o.codec = codec
}) })
} }
// ForceServerCodec returns a ServerOption that sets a codec for message
// marshaling and unmarshaling.
//
// This will override any lookups by content-subtype for Codecs registered
// with RegisterCodec.
//
// See Content-Type on
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details. Also see the documentation on RegisterCodec and
// CallContentSubtype for more details on the interaction between encoding.Codec
// and content-subtype.
//
// This function is provided for advanced users; prefer to register codecs
// using encoding.RegisterCodec.
// The server will automatically use registered codecs based on the incoming
// requests' headers. See also
// https://github.com/grpc/grpc-go/blob/master/Documentation/encoding.md#using-a-codec.
// Will be supported throughout 1.x.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ForceServerCodec(codec encoding.Codec) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.codec = codec
})
}
// RPCCompressor returns a ServerOption that sets a compressor for outbound // RPCCompressor returns a ServerOption that sets a compressor for outbound
// messages. For backward compatibility, all outbound messages will be sent // messages. For backward compatibility, all outbound messages will be sent
// using this compressor, regardless of incoming message compression. By // using this compressor, regardless of incoming message compression. By
// default, server messages will be sent using the same compressor with which // default, server messages will be sent using the same compressor with which
// request messages were sent. // request messages were sent.
// //
// Deprecated: use encoding.RegisterCompressor instead. // Deprecated: use encoding.RegisterCompressor instead. Will be supported
// throughout 1.x.
func RPCCompressor(cp Compressor) ServerOption { func RPCCompressor(cp Compressor) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
o.cp = cp o.cp = cp
@ -253,7 +326,8 @@ func RPCCompressor(cp Compressor) ServerOption {
// messages. It has higher priority than decompressors registered via // messages. It has higher priority than decompressors registered via
// encoding.RegisterCompressor. // encoding.RegisterCompressor.
// //
// Deprecated: use encoding.RegisterCompressor instead. // Deprecated: use encoding.RegisterCompressor instead. Will be supported
// throughout 1.x.
func RPCDecompressor(dc Decompressor) ServerOption { func RPCDecompressor(dc Decompressor) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
o.dc = dc o.dc = dc
@ -263,7 +337,7 @@ func RPCDecompressor(dc Decompressor) ServerOption {
// MaxMsgSize returns a ServerOption to set the max message size in bytes the server can receive. // MaxMsgSize returns a ServerOption to set the max message size in bytes the server can receive.
// If this is not set, gRPC uses the default limit. // If this is not set, gRPC uses the default limit.
// //
// Deprecated: use MaxRecvMsgSize instead. // Deprecated: use MaxRecvMsgSize instead. Will be supported throughout 1.x.
func MaxMsgSize(m int) ServerOption { func MaxMsgSize(m int) ServerOption {
return MaxRecvMsgSize(m) return MaxRecvMsgSize(m)
} }
@ -311,6 +385,16 @@ func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
}) })
} }
// ChainUnaryInterceptor returns a ServerOption that specifies the chained interceptor
// for unary RPCs. The first interceptor will be the outer most,
// while the last interceptor will be the inner most wrapper around the real call.
// All unary interceptors added by this method will be chained.
func ChainUnaryInterceptor(interceptors ...UnaryServerInterceptor) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.chainUnaryInts = append(o.chainUnaryInts, interceptors...)
})
}
// StreamInterceptor returns a ServerOption that sets the StreamServerInterceptor for the // StreamInterceptor returns a ServerOption that sets the StreamServerInterceptor for the
// server. Only one stream interceptor can be installed. // server. Only one stream interceptor can be installed.
func StreamInterceptor(i StreamServerInterceptor) ServerOption { func StreamInterceptor(i StreamServerInterceptor) ServerOption {
@ -322,8 +406,23 @@ func StreamInterceptor(i StreamServerInterceptor) ServerOption {
}) })
} }
// ChainStreamInterceptor returns a ServerOption that specifies the chained interceptor
// for streaming RPCs. The first interceptor will be the outer most,
// while the last interceptor will be the inner most wrapper around the real call.
// All stream interceptors added by this method will be chained.
func ChainStreamInterceptor(interceptors ...StreamServerInterceptor) ServerOption {
return newFuncServerOption(func(o *serverOptions) {
o.chainStreamInts = append(o.chainStreamInts, interceptors...)
})
}
// InTapHandle returns a ServerOption that sets the tap handle for all the server // InTapHandle returns a ServerOption that sets the tap handle for all the server
// transport to be created. Only one can be installed. // transport to be created. Only one can be installed.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func InTapHandle(h tap.ServerInHandle) ServerOption { func InTapHandle(h tap.ServerInHandle) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
if o.inTapHandle != nil { if o.inTapHandle != nil {
@ -363,7 +462,10 @@ func UnknownServiceHandler(streamHandler StreamHandler) ServerOption {
// new connections. If this is not set, the default is 120 seconds. A zero or // new connections. If this is not set, the default is 120 seconds. A zero or
// negative value will result in an immediate timeout. // negative value will result in an immediate timeout.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ConnectionTimeout(d time.Duration) ServerOption { func ConnectionTimeout(d time.Duration) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
o.connectionTimeout = d o.connectionTimeout = d
@ -381,13 +483,79 @@ func MaxHeaderListSize(s uint32) ServerOption {
// HeaderTableSize returns a ServerOption that sets the size of dynamic // HeaderTableSize returns a ServerOption that sets the size of dynamic
// header table for stream. // header table for stream.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func HeaderTableSize(s uint32) ServerOption { func HeaderTableSize(s uint32) ServerOption {
return newFuncServerOption(func(o *serverOptions) { return newFuncServerOption(func(o *serverOptions) {
o.headerTableSize = &s o.headerTableSize = &s
}) })
} }
// NumStreamWorkers returns a ServerOption that sets the number of worker
// goroutines that should be used to process incoming streams. Setting this to
// zero (default) will disable workers and spawn a new goroutine for each
// stream.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func NumStreamWorkers(numServerWorkers uint32) ServerOption {
// TODO: If/when this API gets stabilized (i.e. stream workers become the
// only way streams are processed), change the behavior of the zero value to
// a sane default. Preliminary experiments suggest that a value equal to the
// number of CPUs available is most performant; requires thorough testing.
return newFuncServerOption(func(o *serverOptions) {
o.numServerWorkers = numServerWorkers
})
}
// serverWorkerResetThreshold defines how often the stack must be reset. Every
// N requests, by spawning a new goroutine in its place, a worker can reset its
// stack so that large stacks don't live in memory forever. 2^16 should allow
// each goroutine stack to live for at least a few seconds in a typical
// workload (assuming a QPS of a few thousand requests/sec).
const serverWorkerResetThreshold = 1 << 16
// serverWorkers blocks on a *transport.Stream channel forever and waits for
// data to be fed by serveStreams. This allows different requests to be
// processed by the same goroutine, removing the need for expensive stack
// re-allocations (see the runtime.morestack problem [1]).
//
// [1] https://github.com/golang/go/issues/18138
func (s *Server) serverWorker(ch chan *serverWorkerData) {
// To make sure all server workers don't reset at the same time, choose a
// random number of iterations before resetting.
threshold := serverWorkerResetThreshold + grpcrand.Intn(serverWorkerResetThreshold)
for completed := 0; completed < threshold; completed++ {
data, ok := <-ch
if !ok {
return
}
s.handleStream(data.st, data.stream, s.traceInfo(data.st, data.stream))
data.wg.Done()
}
go s.serverWorker(ch)
}
// initServerWorkers creates worker goroutines and channels to process incoming
// connections to reduce the time spent overall on runtime.morestack.
func (s *Server) initServerWorkers() {
s.serverWorkerChannels = make([]chan *serverWorkerData, s.opts.numServerWorkers)
for i := uint32(0); i < s.opts.numServerWorkers; i++ {
s.serverWorkerChannels[i] = make(chan *serverWorkerData)
go s.serverWorker(s.serverWorkerChannels[i])
}
}
func (s *Server) stopServerWorkers() {
for i := uint32(0); i < s.opts.numServerWorkers; i++ {
close(s.serverWorkerChannels[i])
}
}
// NewServer creates a gRPC server which has no service registered and has not // NewServer creates a gRPC server which has no service registered and has not
// started to accept requests yet. // started to accept requests yet.
func NewServer(opt ...ServerOption) *Server { func NewServer(opt ...ServerOption) *Server {
@ -398,18 +566,24 @@ func NewServer(opt ...ServerOption) *Server {
s := &Server{ s := &Server{
lis: make(map[net.Listener]bool), lis: make(map[net.Listener]bool),
opts: opts, opts: opts,
conns: make(map[transport.ServerTransport]bool), conns: make(map[string]map[transport.ServerTransport]bool),
m: make(map[string]*service), services: make(map[string]*serviceInfo),
quit: grpcsync.NewEvent(), quit: grpcsync.NewEvent(),
done: grpcsync.NewEvent(), done: grpcsync.NewEvent(),
czData: new(channelzData), czData: new(channelzData),
} }
chainUnaryServerInterceptors(s)
chainStreamServerInterceptors(s)
s.cv = sync.NewCond(&s.mu) s.cv = sync.NewCond(&s.mu)
if EnableTracing { if EnableTracing {
_, file, line, _ := runtime.Caller(1) _, file, line, _ := runtime.Caller(1)
s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line)) s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line))
} }
if s.opts.numServerWorkers > 0 {
s.initServerWorkers()
}
if channelz.IsOn() { if channelz.IsOn() {
s.channelzID = channelz.RegisterServer(&channelzServer{s}, "") s.channelzID = channelz.RegisterServer(&channelzServer{s}, "")
} }
@ -432,14 +606,29 @@ func (s *Server) errorf(format string, a ...interface{}) {
} }
} }
// ServiceRegistrar wraps a single method that supports service registration. It
// enables users to pass concrete types other than grpc.Server to the service
// registration methods exported by the IDL generated code.
type ServiceRegistrar interface {
// RegisterService registers a service and its implementation to the
// concrete type implementing this interface. It may not be called
// once the server has started serving.
// desc describes the service and its methods and handlers. impl is the
// service implementation which is passed to the method handlers.
RegisterService(desc *ServiceDesc, impl interface{})
}
// RegisterService registers a service and its implementation to the gRPC // RegisterService registers a service and its implementation to the gRPC
// server. It is called from the IDL generated code. This must be called before // server. It is called from the IDL generated code. This must be called before
// invoking Serve. // invoking Serve. If ss is non-nil (for legacy code), its type is checked to
// ensure it implements sd.HandlerType.
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) { func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {
if ss != nil {
ht := reflect.TypeOf(sd.HandlerType).Elem() ht := reflect.TypeOf(sd.HandlerType).Elem()
st := reflect.TypeOf(ss) st := reflect.TypeOf(ss)
if !st.Implements(ht) { if !st.Implements(ht) {
grpclog.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht) logger.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht)
}
} }
s.register(sd, ss) s.register(sd, ss)
} }
@ -449,26 +638,26 @@ func (s *Server) register(sd *ServiceDesc, ss interface{}) {
defer s.mu.Unlock() defer s.mu.Unlock()
s.printf("RegisterService(%q)", sd.ServiceName) s.printf("RegisterService(%q)", sd.ServiceName)
if s.serve { if s.serve {
grpclog.Fatalf("grpc: Server.RegisterService after Server.Serve for %q", sd.ServiceName) logger.Fatalf("grpc: Server.RegisterService after Server.Serve for %q", sd.ServiceName)
} }
if _, ok := s.m[sd.ServiceName]; ok { if _, ok := s.services[sd.ServiceName]; ok {
grpclog.Fatalf("grpc: Server.RegisterService found duplicate service registration for %q", sd.ServiceName) logger.Fatalf("grpc: Server.RegisterService found duplicate service registration for %q", sd.ServiceName)
} }
srv := &service{ info := &serviceInfo{
server: ss, serviceImpl: ss,
md: make(map[string]*MethodDesc), methods: make(map[string]*MethodDesc),
sd: make(map[string]*StreamDesc), streams: make(map[string]*StreamDesc),
mdata: sd.Metadata, mdata: sd.Metadata,
} }
for i := range sd.Methods { for i := range sd.Methods {
d := &sd.Methods[i] d := &sd.Methods[i]
srv.md[d.MethodName] = d info.methods[d.MethodName] = d
} }
for i := range sd.Streams { for i := range sd.Streams {
d := &sd.Streams[i] d := &sd.Streams[i]
srv.sd[d.StreamName] = d info.streams[d.StreamName] = d
} }
s.m[sd.ServiceName] = srv s.services[sd.ServiceName] = info
} }
// MethodInfo contains the information of an RPC including its method name and type. // MethodInfo contains the information of an RPC including its method name and type.
@ -492,16 +681,16 @@ type ServiceInfo struct {
// Service names include the package names, in the form of <package>.<service>. // Service names include the package names, in the form of <package>.<service>.
func (s *Server) GetServiceInfo() map[string]ServiceInfo { func (s *Server) GetServiceInfo() map[string]ServiceInfo {
ret := make(map[string]ServiceInfo) ret := make(map[string]ServiceInfo)
for n, srv := range s.m { for n, srv := range s.services {
methods := make([]MethodInfo, 0, len(srv.md)+len(srv.sd)) methods := make([]MethodInfo, 0, len(srv.methods)+len(srv.streams))
for m := range srv.md { for m := range srv.methods {
methods = append(methods, MethodInfo{ methods = append(methods, MethodInfo{
Name: m, Name: m,
IsClientStream: false, IsClientStream: false,
IsServerStream: false, IsServerStream: false,
}) })
} }
for m, d := range srv.sd { for m, d := range srv.streams {
methods = append(methods, MethodInfo{ methods = append(methods, MethodInfo{
Name: m, Name: m,
IsClientStream: d.ClientStreams, IsClientStream: d.ClientStreams,
@ -636,7 +825,7 @@ func (s *Server) Serve(lis net.Listener) error {
// s.conns before this conn can be added. // s.conns before this conn can be added.
s.serveWG.Add(1) s.serveWG.Add(1)
go func() { go func() {
s.handleRawConn(rawConn) s.handleRawConn(lis.Addr().String(), rawConn)
s.serveWG.Done() s.serveWG.Done()
}() }()
} }
@ -644,7 +833,7 @@ func (s *Server) Serve(lis net.Listener) error {
// handleRawConn forks a goroutine to handle a just-accepted connection that // handleRawConn forks a goroutine to handle a just-accepted connection that
// has not had any I/O performed on it yet. // has not had any I/O performed on it yet.
func (s *Server) handleRawConn(rawConn net.Conn) { func (s *Server) handleRawConn(lisAddr string, rawConn net.Conn) {
if s.quit.HasFired() { if s.quit.HasFired() {
rawConn.Close() rawConn.Close()
return return
@ -658,7 +847,7 @@ func (s *Server) handleRawConn(rawConn net.Conn) {
s.mu.Lock() s.mu.Lock()
s.errorf("ServerHandshake(%q) failed: %v", rawConn.RemoteAddr(), err) s.errorf("ServerHandshake(%q) failed: %v", rawConn.RemoteAddr(), err)
s.mu.Unlock() s.mu.Unlock()
grpclog.Warningf("grpc: Server.Serve failed to complete security handshake from %q: %v", rawConn.RemoteAddr(), err) channelz.Warningf(logger, s.channelzID, "grpc: Server.Serve failed to complete security handshake from %q: %v", rawConn.RemoteAddr(), err)
rawConn.Close() rawConn.Close()
} }
rawConn.SetDeadline(time.Time{}) rawConn.SetDeadline(time.Time{})
@ -672,15 +861,24 @@ func (s *Server) handleRawConn(rawConn net.Conn) {
} }
rawConn.SetDeadline(time.Time{}) rawConn.SetDeadline(time.Time{})
if !s.addConn(st) { if !s.addConn(lisAddr, st) {
return return
} }
go func() { go func() {
s.serveStreams(st) s.serveStreams(st)
s.removeConn(st) s.removeConn(lisAddr, st)
}() }()
} }
func (s *Server) drainServerTransports(addr string) {
s.mu.Lock()
conns := s.conns[addr]
for st := range conns {
st.Drain()
}
s.mu.Unlock()
}
// newHTTP2Transport sets up a http/2 transport (using the // newHTTP2Transport sets up a http/2 transport (using the
// gRPC http2 server transport in transport/http2_server.go). // gRPC http2 server transport in transport/http2_server.go).
func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) transport.ServerTransport { func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) transport.ServerTransport {
@ -705,7 +903,7 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr
s.errorf("NewServerTransport(%q) failed: %v", c.RemoteAddr(), err) s.errorf("NewServerTransport(%q) failed: %v", c.RemoteAddr(), err)
s.mu.Unlock() s.mu.Unlock()
c.Close() c.Close()
grpclog.Warningln("grpc: Server.Serve failed to create ServerTransport: ", err) channelz.Warning(logger, s.channelzID, "grpc: Server.Serve failed to create ServerTransport: ", err)
return nil return nil
} }
@ -715,12 +913,27 @@ func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) tr
func (s *Server) serveStreams(st transport.ServerTransport) { func (s *Server) serveStreams(st transport.ServerTransport) {
defer st.Close() defer st.Close()
var wg sync.WaitGroup var wg sync.WaitGroup
var roundRobinCounter uint32
st.HandleStreams(func(stream *transport.Stream) { st.HandleStreams(func(stream *transport.Stream) {
wg.Add(1) wg.Add(1)
if s.opts.numServerWorkers > 0 {
data := &serverWorkerData{st: st, wg: &wg, stream: stream}
select {
case s.serverWorkerChannels[atomic.AddUint32(&roundRobinCounter, 1)%s.opts.numServerWorkers] <- data:
default:
// If all stream workers are busy, fallback to the default code path.
go func() {
s.handleStream(st, stream, s.traceInfo(st, stream))
wg.Done()
}()
}
} else {
go func() { go func() {
defer wg.Done() defer wg.Done()
s.handleStream(st, stream, s.traceInfo(st, stream)) s.handleStream(st, stream, s.traceInfo(st, stream))
}() }()
}
}, func(ctx context.Context, method string) context.Context { }, func(ctx context.Context, method string) context.Context {
if !EnableTracing { if !EnableTracing {
return ctx return ctx
@ -755,18 +968,22 @@ var _ http.Handler = (*Server)(nil)
// Note that ServeHTTP uses Go's HTTP/2 server implementation which is totally // Note that ServeHTTP uses Go's HTTP/2 server implementation which is totally
// separate from grpc-go's HTTP/2 server. Performance and features may vary // separate from grpc-go's HTTP/2 server. Performance and features may vary
// between the two paths. ServeHTTP does not support some gRPC features // between the two paths. ServeHTTP does not support some gRPC features
// available through grpc-go's HTTP/2 server, and it is currently EXPERIMENTAL // available through grpc-go's HTTP/2 server.
// and subject to change. //
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
st, err := transport.NewServerHandlerTransport(w, r, s.opts.statsHandler) st, err := transport.NewServerHandlerTransport(w, r, s.opts.statsHandler)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
if !s.addConn(st) { if !s.addConn(listenerAddressForServeHTTP, st) {
return return
} }
defer s.removeConn(st) defer s.removeConn(listenerAddressForServeHTTP, st)
s.serveStreams(st) s.serveStreams(st)
} }
@ -794,7 +1011,7 @@ func (s *Server) traceInfo(st transport.ServerTransport, stream *transport.Strea
return trInfo return trInfo
} }
func (s *Server) addConn(st transport.ServerTransport) bool { func (s *Server) addConn(addr string, st transport.ServerTransport) bool {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.conns == nil { if s.conns == nil {
@ -806,15 +1023,28 @@ func (s *Server) addConn(st transport.ServerTransport) bool {
// immediately. // immediately.
st.Drain() st.Drain()
} }
s.conns[st] = true
if s.conns[addr] == nil {
// Create a map entry if this is the first connection on this listener.
s.conns[addr] = make(map[transport.ServerTransport]bool)
}
s.conns[addr][st] = true
return true return true
} }
func (s *Server) removeConn(st transport.ServerTransport) { func (s *Server) removeConn(addr string, st transport.ServerTransport) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.conns != nil {
delete(s.conns, st) conns := s.conns[addr]
if conns != nil {
delete(conns, st)
if len(conns) == 0 {
// If the last connection for this address is being removed, also
// remove the map entry corresponding to the address. This is used
// in GracefulStop() when waiting for all connections to be closed.
delete(s.conns, addr)
}
s.cv.Broadcast() s.cv.Broadcast()
} }
} }
@ -844,12 +1074,12 @@ func (s *Server) incrCallsFailed() {
func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error { func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Stream, msg interface{}, cp Compressor, opts *transport.Options, comp encoding.Compressor) error {
data, err := encode(s.getCodec(stream.ContentSubtype()), msg) data, err := encode(s.getCodec(stream.ContentSubtype()), msg)
if err != nil { if err != nil {
grpclog.Errorln("grpc: server failed to encode response: ", err) channelz.Error(logger, s.channelzID, "grpc: server failed to encode response: ", err)
return err return err
} }
compData, err := compress(data, cp, comp) compData, err := compress(data, cp, comp)
if err != nil { if err != nil {
grpclog.Errorln("grpc: server failed to compress response: ", err) channelz.Error(logger, s.channelzID, "grpc: server failed to compress response: ", err)
return err return err
} }
hdr, payload := msgHeader(data, compData) hdr, payload := msgHeader(data, compData)
@ -864,7 +1094,41 @@ func (s *Server) sendResponse(t transport.ServerTransport, stream *transport.Str
return err return err
} }
func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc, trInfo *traceInfo) (err error) { // chainUnaryServerInterceptors chains all unary server interceptors into one.
func chainUnaryServerInterceptors(s *Server) {
// Prepend opts.unaryInt to the chaining interceptors if it exists, since unaryInt will
// be executed before any other chained interceptors.
interceptors := s.opts.chainUnaryInts
if s.opts.unaryInt != nil {
interceptors = append([]UnaryServerInterceptor{s.opts.unaryInt}, s.opts.chainUnaryInts...)
}
var chainedInt UnaryServerInterceptor
if len(interceptors) == 0 {
chainedInt = nil
} else if len(interceptors) == 1 {
chainedInt = interceptors[0]
} else {
chainedInt = func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (interface{}, error) {
return interceptors[0](ctx, req, info, getChainUnaryHandler(interceptors, 0, info, handler))
}
}
s.opts.unaryInt = chainedInt
}
// getChainUnaryHandler recursively generate the chained UnaryHandler
func getChainUnaryHandler(interceptors []UnaryServerInterceptor, curr int, info *UnaryServerInfo, finalHandler UnaryHandler) UnaryHandler {
if curr == len(interceptors)-1 {
return finalHandler
}
return func(ctx context.Context, req interface{}) (interface{}, error) {
return interceptors[curr+1](ctx, req, info, getChainUnaryHandler(interceptors, curr+1, info, finalHandler))
}
}
func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, md *MethodDesc, trInfo *traceInfo) (err error) {
sh := s.opts.statsHandler sh := s.opts.statsHandler
if sh != nil || trInfo != nil || channelz.IsOn() { if sh != nil || trInfo != nil || channelz.IsOn() {
if channelz.IsOn() { if channelz.IsOn() {
@ -987,10 +1251,8 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
} }
d, err := recvAndDecompress(&parser{r: stream}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp) d, err := recvAndDecompress(&parser{r: stream}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp)
if err != nil { if err != nil {
if st, ok := status.FromError(err); ok { if e := t.WriteStatus(stream, status.Convert(err)); e != nil {
if e := t.WriteStatus(stream, st); e != nil { channelz.Warningf(logger, s.channelzID, "grpc: Server.processUnaryRPC failed to write status %v", e)
grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status %v", e)
}
} }
return err return err
} }
@ -1005,7 +1267,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
sh.HandleRPC(stream.Context(), &stats.InPayload{ sh.HandleRPC(stream.Context(), &stats.InPayload{
RecvTime: time.Now(), RecvTime: time.Now(),
Payload: v, Payload: v,
WireLength: payInfo.wireLength, WireLength: payInfo.wireLength + headerLen,
Data: d, Data: d,
Length: len(d), Length: len(d),
}) })
@ -1021,7 +1283,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
return nil return nil
} }
ctx := NewContextWithServerTransportStream(stream.Context(), stream) ctx := NewContextWithServerTransportStream(stream.Context(), stream)
reply, appErr := md.Handler(srv.server, ctx, df, s.opts.unaryInt) reply, appErr := md.Handler(info.serviceImpl, ctx, df, s.opts.unaryInt)
if appErr != nil { if appErr != nil {
appStatus, ok := status.FromError(appErr) appStatus, ok := status.FromError(appErr)
if !ok { if !ok {
@ -1034,7 +1296,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
trInfo.tr.SetError() trInfo.tr.SetError()
} }
if e := t.WriteStatus(stream, appStatus); e != nil { if e := t.WriteStatus(stream, appStatus); e != nil {
grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status: %v", e) channelz.Warningf(logger, s.channelzID, "grpc: Server.processUnaryRPC failed to write status: %v", e)
} }
if binlog != nil { if binlog != nil {
if h, _ := stream.Header(); h.Len() > 0 { if h, _ := stream.Header(); h.Len() > 0 {
@ -1061,9 +1323,9 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
// The entire stream is done (for unary RPC only). // The entire stream is done (for unary RPC only).
return err return err
} }
if s, ok := status.FromError(err); ok { if sts, ok := status.FromError(err); ok {
if e := t.WriteStatus(stream, s); e != nil { if e := t.WriteStatus(stream, sts); e != nil {
grpclog.Warningf("grpc: Server.processUnaryRPC failed to write status: %v", e) channelz.Warningf(logger, s.channelzID, "grpc: Server.processUnaryRPC failed to write status: %v", e)
} }
} else { } else {
switch st := err.(type) { switch st := err.(type) {
@ -1113,7 +1375,41 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
return err return err
} }
func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc, trInfo *traceInfo) (err error) { // chainStreamServerInterceptors chains all stream server interceptors into one.
func chainStreamServerInterceptors(s *Server) {
// Prepend opts.streamInt to the chaining interceptors if it exists, since streamInt will
// be executed before any other chained interceptors.
interceptors := s.opts.chainStreamInts
if s.opts.streamInt != nil {
interceptors = append([]StreamServerInterceptor{s.opts.streamInt}, s.opts.chainStreamInts...)
}
var chainedInt StreamServerInterceptor
if len(interceptors) == 0 {
chainedInt = nil
} else if len(interceptors) == 1 {
chainedInt = interceptors[0]
} else {
chainedInt = func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error {
return interceptors[0](srv, ss, info, getChainStreamHandler(interceptors, 0, info, handler))
}
}
s.opts.streamInt = chainedInt
}
// getChainStreamHandler recursively generate the chained StreamHandler
func getChainStreamHandler(interceptors []StreamServerInterceptor, curr int, info *StreamServerInfo, finalHandler StreamHandler) StreamHandler {
if curr == len(interceptors)-1 {
return finalHandler
}
return func(srv interface{}, ss ServerStream) error {
return interceptors[curr+1](srv, ss, info, getChainStreamHandler(interceptors, curr+1, info, finalHandler))
}
}
func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, info *serviceInfo, sd *StreamDesc, trInfo *traceInfo) (err error) {
if channelz.IsOn() { if channelz.IsOn() {
s.incrCallsStarted() s.incrCallsStarted()
} }
@ -1230,8 +1526,8 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp
} }
var appErr error var appErr error
var server interface{} var server interface{}
if srv != nil { if info != nil {
server = srv.server server = info.serviceImpl
} }
if s.opts.streamInt == nil { if s.opts.streamInt == nil {
appErr = sd.Handler(server, ss) appErr = sd.Handler(server, ss)
@ -1297,7 +1593,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
trInfo.tr.SetError() trInfo.tr.SetError()
} }
grpclog.Warningf("grpc: Server.handleStream failed to write status: %v", err) channelz.Warningf(logger, s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
} }
if trInfo != nil { if trInfo != nil {
trInfo.tr.Finish() trInfo.tr.Finish()
@ -1307,13 +1603,13 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
service := sm[:pos] service := sm[:pos]
method := sm[pos+1:] method := sm[pos+1:]
srv, knownService := s.m[service] srv, knownService := s.services[service]
if knownService { if knownService {
if md, ok := srv.md[method]; ok { if md, ok := srv.methods[method]; ok {
s.processUnaryRPC(t, stream, srv, md, trInfo) s.processUnaryRPC(t, stream, srv, md, trInfo)
return return
} }
if sd, ok := srv.sd[method]; ok { if sd, ok := srv.streams[method]; ok {
s.processStreamingRPC(t, stream, srv, sd, trInfo) s.processStreamingRPC(t, stream, srv, sd, trInfo)
return return
} }
@ -1338,7 +1634,7 @@ func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Str
trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true) trInfo.tr.LazyLog(&fmtStringer{"%v", []interface{}{err}}, true)
trInfo.tr.SetError() trInfo.tr.SetError()
} }
grpclog.Warningf("grpc: Server.handleStream failed to write status: %v", err) channelz.Warningf(logger, s.channelzID, "grpc: Server.handleStream failed to write status: %v", err)
} }
if trInfo != nil { if trInfo != nil {
trInfo.tr.Finish() trInfo.tr.Finish()
@ -1351,7 +1647,10 @@ type streamKey struct{}
// NewContextWithServerTransportStream creates a new context from ctx and // NewContextWithServerTransportStream creates a new context from ctx and
// attaches stream to it. // attaches stream to it.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func NewContextWithServerTransportStream(ctx context.Context, stream ServerTransportStream) context.Context { func NewContextWithServerTransportStream(ctx context.Context, stream ServerTransportStream) context.Context {
return context.WithValue(ctx, streamKey{}, stream) return context.WithValue(ctx, streamKey{}, stream)
} }
@ -1363,7 +1662,10 @@ func NewContextWithServerTransportStream(ctx context.Context, stream ServerTrans
// //
// See also NewContextWithServerTransportStream. // See also NewContextWithServerTransportStream.
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type ServerTransportStream interface { type ServerTransportStream interface {
Method() string Method() string
SetHeader(md metadata.MD) error SetHeader(md metadata.MD) error
@ -1375,7 +1677,10 @@ type ServerTransportStream interface {
// ctx. Returns nil if the given context has no stream associated with it // ctx. Returns nil if the given context has no stream associated with it
// (which implies it is not an RPC invocation context). // (which implies it is not an RPC invocation context).
// //
// This API is EXPERIMENTAL. // Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ServerTransportStreamFromContext(ctx context.Context) ServerTransportStream { func ServerTransportStreamFromContext(ctx context.Context) ServerTransportStream {
s, _ := ctx.Value(streamKey{}).(ServerTransportStream) s, _ := ctx.Value(streamKey{}).(ServerTransportStream)
return s return s
@ -1403,7 +1708,7 @@ func (s *Server) Stop() {
s.mu.Lock() s.mu.Lock()
listeners := s.lis listeners := s.lis
s.lis = nil s.lis = nil
st := s.conns conns := s.conns
s.conns = nil s.conns = nil
// interrupt GracefulStop if Stop and GracefulStop are called concurrently. // interrupt GracefulStop if Stop and GracefulStop are called concurrently.
s.cv.Broadcast() s.cv.Broadcast()
@ -1412,8 +1717,13 @@ func (s *Server) Stop() {
for lis := range listeners { for lis := range listeners {
lis.Close() lis.Close()
} }
for c := range st { for _, cs := range conns {
c.Close() for st := range cs {
st.Close()
}
}
if s.opts.numServerWorkers > 0 {
s.stopServerWorkers()
} }
s.mu.Lock() s.mu.Lock()
@ -1447,9 +1757,11 @@ func (s *Server) GracefulStop() {
} }
s.lis = nil s.lis = nil
if !s.drain { if !s.drain {
for st := range s.conns { for _, conns := range s.conns {
for st := range conns {
st.Drain() st.Drain()
} }
}
s.drain = true s.drain = true
} }

View File

@ -20,15 +20,16 @@ package grpc
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"reflect"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal" "google.golang.org/grpc/internal"
internalserviceconfig "google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/serviceconfig" "google.golang.org/grpc/serviceconfig"
) )
@ -40,29 +41,7 @@ const maxInt = int(^uint(0) >> 1)
// Deprecated: Users should not use this struct. Service config should be received // Deprecated: Users should not use this struct. Service config should be received
// through name resolver, as specified here // through name resolver, as specified here
// https://github.com/grpc/grpc/blob/master/doc/service_config.md // https://github.com/grpc/grpc/blob/master/doc/service_config.md
type MethodConfig struct { type MethodConfig = internalserviceconfig.MethodConfig
// WaitForReady indicates whether RPCs sent to this method should wait until
// the connection is ready by default (!failfast). The value specified via the
// gRPC client API will override the value set here.
WaitForReady *bool
// Timeout is the default timeout for RPCs sent to this method. The actual
// deadline used will be the minimum of the value specified here and the value
// set by the application via the gRPC client API. If either one is not set,
// then the other will be used. If neither is set, then the RPC has no deadline.
Timeout *time.Duration
// MaxReqSize is the maximum allowed payload size for an individual request in a
// stream (client->server) in bytes. The size which is measured is the serialized
// payload after per-message compression (but before stream compression) in bytes.
// The actual value used is the minimum of the value specified here and the value set
// by the application via the gRPC client API. If either one is not set, then the other
// will be used. If neither is set, then the built-in default is used.
MaxReqSize *int
// MaxRespSize is the maximum allowed payload size for an individual response in a
// stream (server->client) in bytes.
MaxRespSize *int
// RetryPolicy configures retry options for the method.
retryPolicy *retryPolicy
}
type lbConfig struct { type lbConfig struct {
name string name string
@ -79,7 +58,7 @@ type ServiceConfig struct {
serviceconfig.Config serviceconfig.Config
// LB is the load balancer the service providers recommends. The balancer // LB is the load balancer the service providers recommends. The balancer
// specified via grpc.WithBalancer will override this. This is deprecated; // specified via grpc.WithBalancerName will override this. This is deprecated;
// lbConfigs is preferred. If lbConfig and LB are both present, lbConfig // lbConfigs is preferred. If lbConfig and LB are both present, lbConfig
// will be used. // will be used.
LB *string LB *string
@ -126,34 +105,6 @@ type healthCheckConfig struct {
ServiceName string ServiceName string
} }
// retryPolicy defines the go-native version of the retry policy defined by the
// service config here:
// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#integration-with-service-config
type retryPolicy struct {
// MaxAttempts is the maximum number of attempts, including the original RPC.
//
// This field is required and must be two or greater.
maxAttempts int
// Exponential backoff parameters. The initial retry attempt will occur at
// random(0, initialBackoff). In general, the nth attempt will occur at
// random(0,
// min(initialBackoff*backoffMultiplier**(n-1), maxBackoff)).
//
// These fields are required and must be greater than zero.
initialBackoff time.Duration
maxBackoff time.Duration
backoffMultiplier float64
// The set of status codes which may be retried.
//
// Status codes are specified as strings, e.g., "UNAVAILABLE".
//
// This field is required and must be non-empty.
// Note: a set is used to store this for easy lookup.
retryableStatusCodes map[codes.Code]bool
}
type jsonRetryPolicy struct { type jsonRetryPolicy struct {
MaxAttempts int MaxAttempts int
InitialBackoff string InitialBackoff string
@ -224,19 +175,27 @@ func parseDuration(s *string) (*time.Duration, error) {
} }
type jsonName struct { type jsonName struct {
Service *string Service string
Method *string Method string
} }
func (j jsonName) generatePath() (string, bool) { var (
if j.Service == nil { errDuplicatedName = errors.New("duplicated name")
return "", false errEmptyServiceNonEmptyMethod = errors.New("cannot combine empty 'service' and non-empty 'method'")
)
func (j jsonName) generatePath() (string, error) {
if j.Service == "" {
if j.Method != "" {
return "", errEmptyServiceNonEmptyMethod
} }
res := "/" + *j.Service + "/" return "", nil
if j.Method != nil {
res += *j.Method
} }
return res, true res := "/" + j.Service + "/"
if j.Method != "" {
res += j.Method
}
return res, nil
} }
// TODO(lyuxuan): delete this struct after cleaning up old service config implementation. // TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
@ -249,12 +208,10 @@ type jsonMC struct {
RetryPolicy *jsonRetryPolicy RetryPolicy *jsonRetryPolicy
} }
type loadBalancingConfig map[string]json.RawMessage
// TODO(lyuxuan): delete this struct after cleaning up old service config implementation. // TODO(lyuxuan): delete this struct after cleaning up old service config implementation.
type jsonSC struct { type jsonSC struct {
LoadBalancingPolicy *string LoadBalancingPolicy *string
LoadBalancingConfig *[]loadBalancingConfig LoadBalancingConfig *internalserviceconfig.BalancerConfig
MethodConfig *[]jsonMC MethodConfig *[]jsonMC
RetryThrottling *retryThrottlingPolicy RetryThrottling *retryThrottlingPolicy
HealthCheckConfig *healthCheckConfig HealthCheckConfig *healthCheckConfig
@ -270,7 +227,7 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
var rsc jsonSC var rsc jsonSC
err := json.Unmarshal([]byte(js), &rsc) err := json.Unmarshal([]byte(js), &rsc)
if err != nil { if err != nil {
grpclog.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err)
return &serviceconfig.ParseResult{Err: err} return &serviceconfig.ParseResult{Err: err}
} }
sc := ServiceConfig{ sc := ServiceConfig{
@ -280,53 +237,25 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
healthCheckConfig: rsc.HealthCheckConfig, healthCheckConfig: rsc.HealthCheckConfig,
rawJSONString: js, rawJSONString: js,
} }
if rsc.LoadBalancingConfig != nil { if c := rsc.LoadBalancingConfig; c != nil {
for i, lbcfg := range *rsc.LoadBalancingConfig { sc.lbConfig = &lbConfig{
if len(lbcfg) != 1 { name: c.Name,
err := fmt.Errorf("invalid loadBalancingConfig: entry %v does not contain exactly 1 policy/config pair: %q", i, lbcfg) cfg: c.Config,
grpclog.Warningf(err.Error())
return &serviceconfig.ParseResult{Err: err}
}
var name string
var jsonCfg json.RawMessage
for name, jsonCfg = range lbcfg {
}
builder := balancer.Get(name)
if builder == nil {
continue
}
sc.lbConfig = &lbConfig{name: name}
if parser, ok := builder.(balancer.ConfigParser); ok {
var err error
sc.lbConfig.cfg, err = parser.ParseConfig(jsonCfg)
if err != nil {
return &serviceconfig.ParseResult{Err: fmt.Errorf("error parsing loadBalancingConfig for policy %q: %v", name, err)}
}
} else if string(jsonCfg) != "{}" {
grpclog.Warningf("non-empty balancer configuration %q, but balancer does not implement ParseConfig", string(jsonCfg))
}
break
}
if sc.lbConfig == nil {
// We had a loadBalancingConfig field but did not encounter a
// supported policy. The config is considered invalid in this
// case.
err := fmt.Errorf("invalid loadBalancingConfig: no supported policies found")
grpclog.Warningf(err.Error())
return &serviceconfig.ParseResult{Err: err}
} }
} }
if rsc.MethodConfig == nil { if rsc.MethodConfig == nil {
return &serviceconfig.ParseResult{Config: &sc} return &serviceconfig.ParseResult{Config: &sc}
} }
paths := map[string]struct{}{}
for _, m := range *rsc.MethodConfig { for _, m := range *rsc.MethodConfig {
if m.Name == nil { if m.Name == nil {
continue continue
} }
d, err := parseDuration(m.Timeout) d, err := parseDuration(m.Timeout)
if err != nil { if err != nil {
grpclog.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err)
return &serviceconfig.ParseResult{Err: err} return &serviceconfig.ParseResult{Err: err}
} }
@ -334,8 +263,8 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
WaitForReady: m.WaitForReady, WaitForReady: m.WaitForReady,
Timeout: d, Timeout: d,
} }
if mc.retryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil { if mc.RetryPolicy, err = convertRetryPolicy(m.RetryPolicy); err != nil {
grpclog.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err) logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to %v", js, err)
return &serviceconfig.ParseResult{Err: err} return &serviceconfig.ParseResult{Err: err}
} }
if m.MaxRequestMessageBytes != nil { if m.MaxRequestMessageBytes != nil {
@ -352,10 +281,20 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes)) mc.MaxRespSize = newInt(int(*m.MaxResponseMessageBytes))
} }
} }
for _, n := range *m.Name { for i, n := range *m.Name {
if path, valid := n.generatePath(); valid { path, err := n.generatePath()
sc.Methods[path] = mc if err != nil {
logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err)
return &serviceconfig.ParseResult{Err: err}
} }
if _, ok := paths[path]; ok {
err = errDuplicatedName
logger.Warningf("grpc: parseServiceConfig error unmarshaling %s due to methodConfig[%d]: %v", js, i, err)
return &serviceconfig.ParseResult{Err: err}
}
paths[path] = struct{}{}
sc.Methods[path] = mc
} }
} }
@ -370,7 +309,7 @@ func parseServiceConfig(js string) *serviceconfig.ParseResult {
return &serviceconfig.ParseResult{Config: &sc} return &serviceconfig.ParseResult{Config: &sc}
} }
func convertRetryPolicy(jrp *jsonRetryPolicy) (p *retryPolicy, err error) { func convertRetryPolicy(jrp *jsonRetryPolicy) (p *internalserviceconfig.RetryPolicy, err error) {
if jrp == nil { if jrp == nil {
return nil, nil return nil, nil
} }
@ -388,23 +327,23 @@ func convertRetryPolicy(jrp *jsonRetryPolicy) (p *retryPolicy, err error) {
*mb <= 0 || *mb <= 0 ||
jrp.BackoffMultiplier <= 0 || jrp.BackoffMultiplier <= 0 ||
len(jrp.RetryableStatusCodes) == 0 { len(jrp.RetryableStatusCodes) == 0 {
grpclog.Warningf("grpc: ignoring retry policy %v due to illegal configuration", jrp) logger.Warningf("grpc: ignoring retry policy %v due to illegal configuration", jrp)
return nil, nil return nil, nil
} }
rp := &retryPolicy{ rp := &internalserviceconfig.RetryPolicy{
maxAttempts: jrp.MaxAttempts, MaxAttempts: jrp.MaxAttempts,
initialBackoff: *ib, InitialBackoff: *ib,
maxBackoff: *mb, MaxBackoff: *mb,
backoffMultiplier: jrp.BackoffMultiplier, BackoffMultiplier: jrp.BackoffMultiplier,
retryableStatusCodes: make(map[codes.Code]bool), RetryableStatusCodes: make(map[codes.Code]bool),
} }
if rp.maxAttempts > 5 { if rp.MaxAttempts > 5 {
// TODO(retry): Make the max maxAttempts configurable. // TODO(retry): Make the max maxAttempts configurable.
rp.maxAttempts = 5 rp.MaxAttempts = 5
} }
for _, code := range jrp.RetryableStatusCodes { for _, code := range jrp.RetryableStatusCodes {
rp.retryableStatusCodes[code] = true rp.RetryableStatusCodes[code] = true
} }
return rp, nil return rp, nil
} }
@ -432,3 +371,34 @@ func getMaxSize(mcMax, doptMax *int, defaultVal int) *int {
func newInt(b int) *int { func newInt(b int) *int {
return &b return &b
} }
func init() {
internal.EqualServiceConfigForTesting = equalServiceConfig
}
// equalServiceConfig compares two configs. The rawJSONString field is ignored,
// because they may diff in white spaces.
//
// If any of them is NOT *ServiceConfig, return false.
func equalServiceConfig(a, b serviceconfig.Config) bool {
aa, ok := a.(*ServiceConfig)
if !ok {
return false
}
bb, ok := b.(*ServiceConfig)
if !ok {
return false
}
aaRaw := aa.rawJSONString
aa.rawJSONString = ""
bbRaw := bb.rawJSONString
bb.rawJSONString = ""
defer func() {
aa.rawJSONString = aaRaw
bb.rawJSONString = bbRaw
}()
// Using reflect.DeepEqual instead of cmp.Equal because many balancer
// configs are unexported, and cmp.Equal cannot compare unexported fields
// from unexported structs.
return reflect.DeepEqual(aa, bb)
}

View File

@ -19,7 +19,10 @@
// Package serviceconfig defines types and methods for operating on gRPC // Package serviceconfig defines types and methods for operating on gRPC
// service configs. // service configs.
// //
// This package is EXPERIMENTAL. // Experimental
//
// Notice: This package is EXPERIMENTAL and may be changed or removed in a
// later release.
package serviceconfig package serviceconfig
// Config represents an opaque data structure holding a service config. // Config represents an opaque data structure holding a service config.

View File

@ -16,8 +16,6 @@
* *
*/ */
//go:generate protoc --go_out=plugins=grpc:. grpc_testing/test.proto
// Package stats is for collecting and reporting various network and RPC stats. // Package stats is for collecting and reporting various network and RPC stats.
// This package is for monitoring purpose only. All fields are read-only. // This package is for monitoring purpose only. All fields are read-only.
// All APIs are experimental. // All APIs are experimental.
@ -81,6 +79,10 @@ type InHeader struct {
Client bool Client bool
// WireLength is the wire length of header. // WireLength is the wire length of header.
WireLength int WireLength int
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata received.
Header metadata.MD
// The following fields are valid only if Client is false. // The following fields are valid only if Client is false.
// FullMethod is the full RPC method string, i.e., /package.service/method. // FullMethod is the full RPC method string, i.e., /package.service/method.
@ -89,10 +91,6 @@ type InHeader struct {
RemoteAddr net.Addr RemoteAddr net.Addr
// LocalAddr is the local address of the corresponding connection. // LocalAddr is the local address of the corresponding connection.
LocalAddr net.Addr LocalAddr net.Addr
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata received.
Header metadata.MD
} }
// IsClient indicates if the stats information is from client side. // IsClient indicates if the stats information is from client side.
@ -141,6 +139,10 @@ func (s *OutPayload) isRPCStats() {}
type OutHeader struct { type OutHeader struct {
// Client is true if this OutHeader is from client side. // Client is true if this OutHeader is from client side.
Client bool Client bool
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata sent.
Header metadata.MD
// The following fields are valid only if Client is true. // The following fields are valid only if Client is true.
// FullMethod is the full RPC method string, i.e., /package.service/method. // FullMethod is the full RPC method string, i.e., /package.service/method.
@ -149,10 +151,6 @@ type OutHeader struct {
RemoteAddr net.Addr RemoteAddr net.Addr
// LocalAddr is the local address of the corresponding connection. // LocalAddr is the local address of the corresponding connection.
LocalAddr net.Addr LocalAddr net.Addr
// Compression is the compression algorithm used for the RPC.
Compression string
// Header contains the header metadata sent.
Header metadata.MD
} }
// IsClient indicates if this stats information is from client side. // IsClient indicates if this stats information is from client side.
@ -165,6 +163,9 @@ type OutTrailer struct {
// Client is true if this OutTrailer is from client side. // Client is true if this OutTrailer is from client side.
Client bool Client bool
// WireLength is the wire length of trailer. // WireLength is the wire length of trailer.
//
// Deprecated: This field is never set. The length is not known when this message is
// emitted because the trailer fields are compressed with hpack after that.
WireLength int WireLength int
// Trailer contains the trailer metadata sent to the client. This // Trailer contains the trailer metadata sent to the client. This
// field is only valid if this OutTrailer is from the server side. // field is only valid if this OutTrailer is from the server side.

View File

@ -29,88 +29,23 @@ package status
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
spb "google.golang.org/genproto/googleapis/rpc/status" spb "google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/internal" "google.golang.org/grpc/internal/status"
) )
func init() { // Status references google.golang.org/grpc/internal/status. It represents an
internal.StatusRawProto = statusRawProto // RPC status code, message, and details. It is immutable and should be
} // created with New, Newf, or FromProto.
// https://godoc.org/google.golang.org/grpc/internal/status
func statusRawProto(s *Status) *spb.Status { return s.s } type Status = status.Status
// statusError is an alias of a status proto. It implements error and Status,
// and a nil statusError should never be returned by this package.
type statusError spb.Status
func (se *statusError) Error() string {
p := (*spb.Status)(se)
return fmt.Sprintf("rpc error: code = %s desc = %s", codes.Code(p.GetCode()), p.GetMessage())
}
func (se *statusError) GRPCStatus() *Status {
return &Status{s: (*spb.Status)(se)}
}
// Is implements future error.Is functionality.
// A statusError is equivalent if the code and message are identical.
func (se *statusError) Is(target error) bool {
tse, ok := target.(*statusError)
if !ok {
return false
}
return proto.Equal((*spb.Status)(se), (*spb.Status)(tse))
}
// Status represents an RPC status code, message, and details. It is immutable
// and should be created with New, Newf, or FromProto.
type Status struct {
s *spb.Status
}
// Code returns the status code contained in s.
func (s *Status) Code() codes.Code {
if s == nil || s.s == nil {
return codes.OK
}
return codes.Code(s.s.Code)
}
// Message returns the message contained in s.
func (s *Status) Message() string {
if s == nil || s.s == nil {
return ""
}
return s.s.Message
}
// Proto returns s's status as an spb.Status proto message.
func (s *Status) Proto() *spb.Status {
if s == nil {
return nil
}
return proto.Clone(s.s).(*spb.Status)
}
// Err returns an immutable error representing s; returns nil if s.Code() is
// OK.
func (s *Status) Err() error {
if s.Code() == codes.OK {
return nil
}
return (*statusError)(s.s)
}
// New returns a Status representing c and msg. // New returns a Status representing c and msg.
func New(c codes.Code, msg string) *Status { func New(c codes.Code, msg string) *Status {
return &Status{s: &spb.Status{Code: int32(c), Message: msg}} return status.New(c, msg)
} }
// Newf returns New(c, fmt.Sprintf(format, a...)). // Newf returns New(c, fmt.Sprintf(format, a...)).
@ -135,12 +70,14 @@ func ErrorProto(s *spb.Status) error {
// FromProto returns a Status representing s. // FromProto returns a Status representing s.
func FromProto(s *spb.Status) *Status { func FromProto(s *spb.Status) *Status {
return &Status{s: proto.Clone(s).(*spb.Status)} return status.FromProto(s)
} }
// FromError returns a Status representing err if it was produced from this // FromError returns a Status representing err if it was produced by this
// package or has a method `GRPCStatus() *Status`. Otherwise, ok is false and a // package or has a method `GRPCStatus() *Status`.
// Status is returned with codes.Unknown and the original error message. // If err is nil, a Status is returned with codes.OK and no message.
// Otherwise, ok is false and a Status is returned with codes.Unknown and
// the original error message.
func FromError(err error) (s *Status, ok bool) { func FromError(err error) (s *Status, ok bool) {
if err == nil { if err == nil {
return nil, true return nil, true
@ -160,42 +97,6 @@ func Convert(err error) *Status {
return s return s
} }
// WithDetails returns a new status with the provided details messages appended to the status.
// If any errors are encountered, it returns nil and the first error encountered.
func (s *Status) WithDetails(details ...proto.Message) (*Status, error) {
if s.Code() == codes.OK {
return nil, errors.New("no error details for status with code OK")
}
// s.Code() != OK implies that s.Proto() != nil.
p := s.Proto()
for _, detail := range details {
any, err := ptypes.MarshalAny(detail)
if err != nil {
return nil, err
}
p.Details = append(p.Details, any)
}
return &Status{s: p}, nil
}
// Details returns a slice of details messages attached to the status.
// If a detail cannot be decoded, the error is returned in place of the detail.
func (s *Status) Details() []interface{} {
if s == nil || s.s == nil {
return nil
}
details := make([]interface{}, 0, len(s.s.Details))
for _, any := range s.s.Details {
detail := &ptypes.DynamicAny{}
if err := ptypes.UnmarshalAny(any, detail); err != nil {
details = append(details, err)
continue
}
details = append(details, detail.Message)
}
return details
}
// Code returns the Code of the error if it is a Status error, codes.OK if err // Code returns the Code of the error if it is a Status error, codes.OK if err
// is nil, or codes.Unknown otherwise. // is nil, or codes.Unknown otherwise.
func Code(err error) codes.Code { func Code(err error) codes.Code {

View File

@ -31,11 +31,13 @@ import (
"google.golang.org/grpc/balancer" "google.golang.org/grpc/balancer"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal/balancerload" "google.golang.org/grpc/internal/balancerload"
"google.golang.org/grpc/internal/binarylog" "google.golang.org/grpc/internal/binarylog"
"google.golang.org/grpc/internal/channelz" "google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/internal/grpcrand"
"google.golang.org/grpc/internal/grpcutil"
iresolver "google.golang.org/grpc/internal/resolver"
"google.golang.org/grpc/internal/serviceconfig"
"google.golang.org/grpc/internal/transport" "google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer" "google.golang.org/grpc/peer"
@ -50,14 +52,20 @@ import (
// of the RPC. // of the RPC.
type StreamHandler func(srv interface{}, stream ServerStream) error type StreamHandler func(srv interface{}, stream ServerStream) error
// StreamDesc represents a streaming RPC service's method specification. // StreamDesc represents a streaming RPC service's method specification. Used
// on the server when registering services and on the client when initiating
// new streams.
type StreamDesc struct { type StreamDesc struct {
StreamName string // StreamName and Handler are only used when registering handlers on a
Handler StreamHandler // server.
StreamName string // the name of the method excluding the service
Handler StreamHandler // the handler called for the method
// At least one of these is true. // ServerStreams and ClientStreams are used for registering handlers on a
ServerStreams bool // server as well as defining RPC behavior when passed to NewClientStream
ClientStreams bool // and ClientConn.NewStream. At least one must be true.
ServerStreams bool // indicates the server can perform streaming sends
ClientStreams bool // indicates the client can perform streaming sends
} }
// Stream defines the common interface a client or server stream has to satisfy. // Stream defines the common interface a client or server stream has to satisfy.
@ -164,13 +172,48 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
} }
}() }()
} }
c := defaultCallInfo()
// Provide an opportunity for the first RPC to see the first service config // Provide an opportunity for the first RPC to see the first service config
// provided by the resolver. // provided by the resolver.
if err := cc.waitForResolvedAddrs(ctx); err != nil { if err := cc.waitForResolvedAddrs(ctx); err != nil {
return nil, err return nil, err
} }
mc := cc.GetMethodConfig(method)
var mc serviceconfig.MethodConfig
var onCommit func()
var newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, opts...)
}
rpcInfo := iresolver.RPCInfo{Context: ctx, Method: method}
rpcConfig, err := cc.safeConfigSelector.SelectConfig(rpcInfo)
if err != nil {
return nil, toRPCErr(err)
}
if rpcConfig != nil {
if rpcConfig.Context != nil {
ctx = rpcConfig.Context
}
mc = rpcConfig.MethodConfig
onCommit = rpcConfig.OnCommitted
if rpcConfig.Interceptor != nil {
rpcInfo.Context = nil
ns := newStream
newStream = func(ctx context.Context, done func()) (iresolver.ClientStream, error) {
cs, err := rpcConfig.Interceptor.NewStream(ctx, rpcInfo, done, ns)
if err != nil {
return nil, toRPCErr(err)
}
return cs, nil
}
}
}
return newStream(ctx, func() {})
}
func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc serviceconfig.MethodConfig, onCommit, doneFunc func(), opts ...CallOption) (_ iresolver.ClientStream, err error) {
c := defaultCallInfo()
if mc.WaitForReady != nil { if mc.WaitForReady != nil {
c.failFast = !*mc.WaitForReady c.failFast = !*mc.WaitForReady
} }
@ -207,6 +250,7 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
Host: cc.authority, Host: cc.authority,
Method: method, Method: method,
ContentSubtype: c.contentSubtype, ContentSubtype: c.contentSubtype,
DoneFunc: doneFunc,
} }
// Set our outgoing compression according to the UseCompressor CallOption, if // Set our outgoing compression according to the UseCompressor CallOption, if
@ -272,13 +316,13 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
cancel: cancel, cancel: cancel,
beginTime: beginTime, beginTime: beginTime,
firstAttempt: true, firstAttempt: true,
onCommit: onCommit,
} }
if !cc.dopts.disableRetry { if !cc.dopts.disableRetry {
cs.retryThrottler = cc.retryThrottler.Load().(*retryThrottler) cs.retryThrottler = cc.retryThrottler.Load().(*retryThrottler)
} }
cs.binlog = binarylog.GetMethodLogger(method) cs.binlog = binarylog.GetMethodLogger(method)
cs.callInfo.stream = cs
// Only this initial attempt has stats/tracing. // Only this initial attempt has stats/tracing.
// TODO(dfawley): move to newAttempt when per-attempt stats are implemented. // TODO(dfawley): move to newAttempt when per-attempt stats are implemented.
if err := cs.newAttemptLocked(sh, trInfo); err != nil { if err := cs.newAttemptLocked(sh, trInfo); err != nil {
@ -348,7 +392,16 @@ func (cs *clientStream) newAttemptLocked(sh stats.Handler, trInfo *traceInfo) (r
if err := cs.ctx.Err(); err != nil { if err := cs.ctx.Err(); err != nil {
return toRPCErr(err) return toRPCErr(err)
} }
t, done, err := cs.cc.getTransport(cs.ctx, cs.callInfo.failFast, cs.callHdr.Method)
ctx := cs.ctx
if cs.cc.parsedTarget.Scheme == "xds" {
// Add extra metadata (metadata that will be added by transport) to context
// so the balancer can see them.
ctx = grpcutil.WithExtraMetadata(cs.ctx, metadata.Pairs(
"content-type", grpcutil.ContentType(cs.callHdr.ContentSubtype),
))
}
t, done, err := cs.cc.getTransport(ctx, cs.callInfo.failFast, cs.callHdr.Method)
if err != nil { if err != nil {
return err return err
} }
@ -366,6 +419,11 @@ func (a *csAttempt) newStream() error {
cs.callHdr.PreviousAttempts = cs.numRetries cs.callHdr.PreviousAttempts = cs.numRetries
s, err := a.t.NewStream(cs.ctx, cs.callHdr) s, err := a.t.NewStream(cs.ctx, cs.callHdr)
if err != nil { if err != nil {
if _, ok := err.(transport.PerformedIOError); ok {
// Return without converting to an RPC error so retry code can
// inspect.
return err
}
return toRPCErr(err) return toRPCErr(err)
} }
cs.attempt.s = s cs.attempt.s = s
@ -420,6 +478,7 @@ type clientStream struct {
attempt *csAttempt attempt *csAttempt
// TODO(hedging): hedging will have multiple attempts simultaneously. // TODO(hedging): hedging will have multiple attempts simultaneously.
committed bool // active attempt committed for retry? committed bool // active attempt committed for retry?
onCommit func()
buffer []func(a *csAttempt) error // operations to replay on retry buffer []func(a *csAttempt) error // operations to replay on retry
bufferSize int // current size of buffer bufferSize int // current size of buffer
} }
@ -448,6 +507,9 @@ type csAttempt struct {
} }
func (cs *clientStream) commitAttemptLocked() { func (cs *clientStream) commitAttemptLocked() {
if !cs.committed && cs.onCommit != nil {
cs.onCommit()
}
cs.committed = true cs.committed = true
cs.buffer = nil cs.buffer = nil
} }
@ -461,12 +523,22 @@ func (cs *clientStream) commitAttempt() {
// shouldRetry returns nil if the RPC should be retried; otherwise it returns // shouldRetry returns nil if the RPC should be retried; otherwise it returns
// the error that should be returned by the operation. // the error that should be returned by the operation.
func (cs *clientStream) shouldRetry(err error) error { func (cs *clientStream) shouldRetry(err error) error {
if cs.attempt.s == nil && !cs.callInfo.failFast { unprocessed := false
// In the event of any error from NewStream (attempt.s == nil), we if cs.attempt.s == nil {
pioErr, ok := err.(transport.PerformedIOError)
if ok {
// Unwrap error.
err = toRPCErr(pioErr.Err)
} else {
unprocessed = true
}
if !ok && !cs.callInfo.failFast {
// In the event of a non-IO operation error from NewStream, we
// never attempted to write anything to the wire, so we can retry // never attempted to write anything to the wire, so we can retry
// indefinitely for non-fail-fast RPCs. // indefinitely for non-fail-fast RPCs.
return nil return nil
} }
}
if cs.finished || cs.committed { if cs.finished || cs.committed {
// RPC is finished or committed; cannot retry. // RPC is finished or committed; cannot retry.
return err return err
@ -474,13 +546,12 @@ func (cs *clientStream) shouldRetry(err error) error {
// Wait for the trailers. // Wait for the trailers.
if cs.attempt.s != nil { if cs.attempt.s != nil {
<-cs.attempt.s.Done() <-cs.attempt.s.Done()
unprocessed = cs.attempt.s.Unprocessed()
} }
if cs.firstAttempt && (cs.attempt.s == nil || cs.attempt.s.Unprocessed()) { if cs.firstAttempt && unprocessed {
// First attempt, stream unprocessed: transparently retry. // First attempt, stream unprocessed: transparently retry.
cs.firstAttempt = false
return nil return nil
} }
cs.firstAttempt = false
if cs.cc.dopts.disableRetry { if cs.cc.dopts.disableRetry {
return err return err
} }
@ -498,13 +569,13 @@ func (cs *clientStream) shouldRetry(err error) error {
if len(sps) == 1 { if len(sps) == 1 {
var e error var e error
if pushback, e = strconv.Atoi(sps[0]); e != nil || pushback < 0 { if pushback, e = strconv.Atoi(sps[0]); e != nil || pushback < 0 {
grpclog.Infof("Server retry pushback specified to abort (%q).", sps[0]) channelz.Infof(logger, cs.cc.channelzID, "Server retry pushback specified to abort (%q).", sps[0])
cs.retryThrottler.throttle() // This counts as a failure for throttling. cs.retryThrottler.throttle() // This counts as a failure for throttling.
return err return err
} }
hasPushback = true hasPushback = true
} else if len(sps) > 1 { } else if len(sps) > 1 {
grpclog.Warningf("Server retry pushback specified multiple values (%q); not retrying.", sps) channelz.Warningf(logger, cs.cc.channelzID, "Server retry pushback specified multiple values (%q); not retrying.", sps)
cs.retryThrottler.throttle() // This counts as a failure for throttling. cs.retryThrottler.throttle() // This counts as a failure for throttling.
return err return err
} }
@ -517,8 +588,8 @@ func (cs *clientStream) shouldRetry(err error) error {
code = status.Convert(err).Code() code = status.Convert(err).Code()
} }
rp := cs.methodConfig.retryPolicy rp := cs.methodConfig.RetryPolicy
if rp == nil || !rp.retryableStatusCodes[code] { if rp == nil || !rp.RetryableStatusCodes[code] {
return err return err
} }
@ -527,7 +598,7 @@ func (cs *clientStream) shouldRetry(err error) error {
if cs.retryThrottler.throttle() { if cs.retryThrottler.throttle() {
return err return err
} }
if cs.numRetries+1 >= rp.maxAttempts { if cs.numRetries+1 >= rp.MaxAttempts {
return err return err
} }
@ -536,9 +607,9 @@ func (cs *clientStream) shouldRetry(err error) error {
dur = time.Millisecond * time.Duration(pushback) dur = time.Millisecond * time.Duration(pushback)
cs.numRetriesSincePushback = 0 cs.numRetriesSincePushback = 0
} else { } else {
fact := math.Pow(rp.backoffMultiplier, float64(cs.numRetriesSincePushback)) fact := math.Pow(rp.BackoffMultiplier, float64(cs.numRetriesSincePushback))
cur := float64(rp.initialBackoff) * fact cur := float64(rp.InitialBackoff) * fact
if max := float64(rp.maxBackoff); cur > max { if max := float64(rp.MaxBackoff); cur > max {
cur = max cur = max
} }
dur = time.Duration(grpcrand.Int63n(int64(cur))) dur = time.Duration(grpcrand.Int63n(int64(cur)))
@ -566,6 +637,7 @@ func (cs *clientStream) retryLocked(lastErr error) error {
cs.commitAttemptLocked() cs.commitAttemptLocked()
return err return err
} }
cs.firstAttempt = false
if err := cs.newAttemptLocked(nil, nil); err != nil { if err := cs.newAttemptLocked(nil, nil); err != nil {
return err return err
} }
@ -800,6 +872,15 @@ func (cs *clientStream) finish(err error) {
} }
cs.finished = true cs.finished = true
cs.commitAttemptLocked() cs.commitAttemptLocked()
if cs.attempt != nil {
cs.attempt.finish(err)
// after functions all rely upon having a stream.
if cs.attempt.s != nil {
for _, o := range cs.opts {
o.after(cs.callInfo, cs.attempt)
}
}
}
cs.mu.Unlock() cs.mu.Unlock()
// For binary logging. only log cancel in finish (could be caused by RPC ctx // For binary logging. only log cancel in finish (could be caused by RPC ctx
// canceled or ClientConn closed). Trailer will be logged in RecvMsg. // canceled or ClientConn closed). Trailer will be logged in RecvMsg.
@ -821,15 +902,6 @@ func (cs *clientStream) finish(err error) {
cs.cc.incrCallsSucceeded() cs.cc.incrCallsSucceeded()
} }
} }
if cs.attempt != nil {
cs.attempt.finish(err)
// after functions all rely upon having a stream.
if cs.attempt.s != nil {
for _, o := range cs.opts {
o.after(cs.callInfo)
}
}
}
cs.cancel() cs.cancel()
} }
@ -906,7 +978,7 @@ func (a *csAttempt) recvMsg(m interface{}, payInfo *payloadInfo) (err error) {
Payload: m, Payload: m,
// TODO truncate large payload. // TODO truncate large payload.
Data: payInfo.uncompressedBytes, Data: payInfo.uncompressedBytes,
WireLength: payInfo.wireLength, WireLength: payInfo.wireLength + headerLen,
Length: len(payInfo.uncompressedBytes), Length: len(payInfo.uncompressedBytes),
}) })
} }
@ -1067,7 +1139,6 @@ func newNonRetryClientStream(ctx context.Context, desc *StreamDesc, method strin
t: t, t: t,
} }
as.callInfo.stream = as
s, err := as.t.NewStream(as.ctx, as.callHdr) s, err := as.t.NewStream(as.ctx, as.callHdr)
if err != nil { if err != nil {
err = toRPCErr(err) err = toRPCErr(err)
@ -1489,7 +1560,7 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) {
Payload: m, Payload: m,
// TODO truncate large payload. // TODO truncate large payload.
Data: payInfo.uncompressedBytes, Data: payInfo.uncompressedBytes,
WireLength: payInfo.wireLength, WireLength: payInfo.wireLength + headerLen,
Length: len(payInfo.uncompressedBytes), Length: len(payInfo.uncompressedBytes),
}) })
} }

View File

@ -17,7 +17,12 @@
*/ */
// Package tap defines the function handles which are executed on the transport // Package tap defines the function handles which are executed on the transport
// layer of gRPC-Go and related information. Everything here is EXPERIMENTAL. // layer of gRPC-Go and related information.
//
// Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
package tap package tap
import ( import (
@ -32,16 +37,16 @@ type Info struct {
// TODO: More to be added. // TODO: More to be added.
} }
// ServerInHandle defines the function which runs before a new stream is created // ServerInHandle defines the function which runs before a new stream is
// on the server side. If it returns a non-nil error, the stream will not be // created on the server side. If it returns a non-nil error, the stream will
// created and a RST_STREAM will be sent back to the client with REFUSED_STREAM. // not be created and an error will be returned to the client. If the error
// The client will receive an RPC error "code = Unavailable, desc = stream // returned is a status error, that status code and message will be used,
// terminated by RST_STREAM with error code: REFUSED_STREAM". // otherwise PermissionDenied will be the code and err.Error() will be the
// message.
// //
// It's intended to be used in situations where you don't want to waste the // It's intended to be used in situations where you don't want to waste the
// resources to accept the new stream (e.g. rate-limiting). And the content of // resources to accept the new stream (e.g. rate-limiting). For other general
// the error will be ignored and won't be sent back to the client. For other // usages, please use interceptors.
// general usages, please use interceptors.
// //
// Note that it is executed in the per-connection I/O goroutine(s) instead of // Note that it is executed in the per-connection I/O goroutine(s) instead of
// per-RPC goroutine. Therefore, users should NOT have any // per-RPC goroutine. Therefore, users should NOT have any

View File

@ -19,4 +19,4 @@
package grpc package grpc
// Version is the current grpc version. // Version is the current grpc version.
const Version = "1.27.1" const Version = "1.38.0"