Catch error code from GCP pub/sub - go

I am using go package for pub/sub. On my API dashboard I see this error(google.pubsub.v1.Subscriber.StreamingPull - error code 503). Per docs(https://cloud.google.com/pubsub/docs/reference/error-codes) it seems it is transient condition but better to implement backoff strategy(https://cloud.google.com/storage/docs/exponential-backoff). the question is I am not able to wrap my head where this error code is coming on Receive method.
Here is func:
err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
// Dump message
// log.Printf("Got message: %s", m.Data)
// Decoding coming message
err = json.NewDecoder(bytes.NewReader(m.Data)).Decode(&msg)
if err != nil {
log.Printf("Error decoding - %v", err)
}
// See streaming messages
log.Printf(" %s : %s : Product updated for Product Id(%d) : Product Title(%s)",
msg.AuthID,
msg.TraceID,
msg.Product.ID,
msg.Product.Title,
)
//
// Some business logic
//
// Acknowledge on recieve method
m.Ack()
})
if err != context.Canceled {
// if err != nil {
return errors.Wrap(err, "Error occurred on recieve data from topic: blah")
}

The Cloud Pub/Sub Go client library will retry this transient error on its own, you shouldn't need to handle it. Internally, the client library uses StreamingPull, where it sends a request and receives messages from the service as they are available. Occasionally, there can be a disconnection event requiring the connection to be reestablished. This is why you see the 503 error in the API dashboard. It should not be the case that your code sees an error in this scenario since the underlying library is handling it (including the use of exponential backoff, where relevant).

Related

Using google PubSub with a Cloud Run gRPC service

I have a Cloud Run gRPC service and I want it to listen to PubSub topic.
The only way to do it from the official documentations is to use a trigger but this only works with REST server that accept http requests.
I couldn't find any good example on the web of how to use pubsub with cloud run grpc services.
My service is built with Go following the official grpc instructions
main.go:
func main() {
ctx := context.Background()
pubSubClient, err := pubsub.NewClient(ctx, 'project-id')
if err != nil {
log.Fatal(err)
}
pubSubSubscription := pubSubClient.Subscription("subscription-id")
go func(sub *pubsub.Subscription) {
err := sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
defer m.Ack()
log.Printf("sub.Receiv called %+v", m)
})
if err != nil {
// handle error
}
}(pubSubSubscription)
....
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s, _ := server.New(ctx, ...)
grpcServer := grpc.NewServer()
grpcpkg.RegisterReportServiceServer(grpcServer, s)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}
This will work as long as there is at least one instance up and listening. But if there is no instances active, and a new pubsub message publishes, cloud run won't "wake up" the insane because it's autoscaling is utilized based on http requests.
The only way to make the above code work is by setting min-instance 1 in cloud run, but this can be not efficient for a lot of use cases (i.e the service is not active at night)
Is there any work around for this?
Is there any way to trigger a grpc cloud run service from a pubsub message?
The sub.Receive method you are attempting to use on the client library is meant for Pull message delivery and not Push delivery. You can read about how these two delivery types compare to make a choice.
If you wish to receive messages as incoming requests, you must use Push delivery and can follow the the Cloud Run Pubsub usage guide. Note that Cloud Pub/Sub only delivers messages as HTTPS POST requests with the specified JSON payload. To handle these requests from a gRPC server, you can use the gRPC Gateway plugin to generate a reverse proxy that exposes an HTTP API.
Once your Cloud Run instance is accepting HTTP traffic using the reverse proxy, it will be autoscaled with incoming requests, so you won't need to keep 1 instance always running. Note that this may mean that message processing latency is upto 10s on a cold start.

How to send Custom proto in Details of grpc's status error object

I'm trying to send a custom Proto in Error Response of my grpc calls. As per this, it should be possible.
Here is the code.
st := status.Newf(codes.NotFound, "user %s doesn't exist", req.Name)
desc := "The username Doesn't exist, please give a valid username"
customProtoError := &data.Error{
Message: "Check username",
Code: 1404,
Type: "Service",
DetailedMessage: desc,
}
st, err := st.WithDetails(customProtoError)
if err != nil {
panic(fmt.Sprintf("Unexpected error attaching metadata: %v", err))
}
I've both grpc & http Handlers for these GRPC APIs.
The Marshaler used in HTTP server is github.com/gogo/gateway
mux := runtime.NewServeMux(
runtime.WithMarshalerOption(JSONContentType, &gateway.JSONPb{}))
The configuration for HTTP and GRPC server is available here.
When I try to access the API using HTTP calls, here is what I get in response.
HTTP Error:
{
"error": "user kishore1 doesn't exist",
"code": 5,
"message": "user kishore1 doesn't exist",
"details": [
{
"type_url": "type.googleapis.com/data.Error",
"value": "Cg5DaGVjayB1c2VybmFtZRD8ChoHU2t5ZmxvdyI4VGhlIHVzZXJuYW1lIERvZXNuJ3QgZXhpc3QsIHBsZWFzZSBnaXZlIGEgdmFsaWQgdXNlcm5hbWU="
}
]
}
The type_url is coming with google, as its hardcoded in the golang proto code. But this says Any in JSON will be deserialized to embedded messages.
Grpc Client Code:
resp, err := client.GetUser(ctx, req)
if err != nil {
st := status.Convert(err)
for _, detail := range st.Details() {
switch t := detail.(type) {
case *pb.Error:
fmt.Println("Oops! Your request was rejected by the server.")
...
default:
fmt.Println("Error Received - ", detail, reflect.TypeOf(detail))
}
}
Grpc Error:
Error Received - proto: not found *errors.prefixError
Any idea what could be causing this mess? HTTP doesn't deserialize as an embedded message and GRPC gives proto.NotFound
Could this be due to gogo proto being the gateway? I even tried the same with googleapis proto errdetails.BadRequest but still the same.
After spending about a day, I just figured out the issue was due to incompatible versions.
So here is what worked for me, when using gogo proto.
For the GRPC errors, I had to upgrade my grpc-gateway version and use gogo/status instead of grpc/status
github.com/gogo/status v1.1.0
github.com/grpc-ecosystem/grpc-gateway v1.14.5
For HTTP errors, I had to downgrade my golang/protobuf
github.com/golang/protobuf v1.3.2

Got 4xx Error while subscribing on Podio Push service in go concurrent pattern

I'm experiencing some unexpected errors while trying to subscribe on Podio Push service. I use golang concurrency pattern defined here and here is the bayeux client library used for subscription.
Basically the flow tries to retrieve the item first and then subscribe into push object provided with the item object. There is channel object where i store each task (taskLoad: ~each item_id with credentials it needs for retrieval)
item := new(podio.Item)
item, err = podio.GetItem(itemId)
if err != nil {
log.Errorf("PODIO", "Could not get item %d -> %s", itemId, err)
return
}
and, later inside another func
messages := make(chan *bayeux.Message)
server := GetBayeux()
defer server.Close()
if err = push.Subscribe(server, messages); err != nil {
// log err, with item details
log.Errorf("PODIO", "%s", err, push)
// re-enqueue the task in taskLoad channel
go enqueueTask(true, messages, sigrepeat, timer)
// release sigwait channel on subscription error
<-sigwait
return
}
here GetBayeux func is just a singleton which wraps the client
func GetBayeux() *bayeux.Client {
bayeuxOnce.Do(func() {
Bayeux = bayeux.NewClient("https://push.podio.com/faye", nil)
})
return Bayeux
}
there is about ~15000 items to listen and I should subscribe to each of them but unfortunately sometimes I got one of these errors while processing subscriptions
401:k9jx3v4qq276k9q84gokpqirhjrfbyd:Unknown client [{"channel":"/item/9164xxxxx","signature":"46bd8ab3ef2a31297d8f4f5ddxxxx","timestamp":"2018-01-02T14:34:02Z","expires_in":21600}]
OR
HTTP Status 400 [{"channel":"/item/9158xxxxx","signature":"31bf8b4697ca2fda69bd7fd532d08xxxxxx","timestamp":"2018-01-02T14:37:02Z","expires_in":21600}]
OR
[WRN] Bayeux connect failed: HTTP Status 400
OR
Bayeux connect failed: Post https://push.podio.com/faye: http2: server sent GOAWAY and closed the connection; LastStreamID=1999, ErrCode=NO_ERROR, debug=""
So now, i'd like to know why i got these errors and most of all how i can fix them to ensure to listen to all items in the scope.
If anyone knows, is there any limitation about concurrent access into podio push service?
Thanks
UPDATE 2019-01-07
it was the singleton that messed the process. as it was in a goroutine context there was some subscriptions that was not allowed because the server has been closed by another goroutine. The fix was to exposing Unsubscribe method and use it instead of Close method which disconnect the client from the server.
defer server.Close()
became
defer push.Unsubscribe(server)

Getting Unimplemented desc = unknown service error gRPC

In one of my services that happens to be my loadbalancer, I am getting the following error when calling the server method in one of my deployed services:
rpc error: code = Unimplemented desc = unknown service
fooService.FooService
I have a few other services set up with gRPC and they work fine. It just seems to be this one and I am wondering if that is because it is the loadbalancer?
func GetResult(w http.ResponseWriter, r *http.Request) {
conn, errOne := grpc.Dial("redis-gateway:10006", grpc.WithInsecure())
defer conn.Close()
rClient := rs.NewRedisGatewayClient(conn)
result , errTwo := rClient.GetData(context.Background(), &rs.KeyRequest{Key: "trump", Value: "trumpVal"}, grpc.WithInsecure())
fmt.Fprintf(w, "print result: %s \n", result) //prints nil
fmt.Fprintf(w, "print error one: %v \n", errOne) // prints nil
fmt.Fprintf(w, "print error two: %s \n", errTwo) // prints error
}
The error says there is no service called fooService.FooService which is true because the dns name for the service I am calling is called foo-service. However it is the exact same setup for my other services that use gRPC and work fine. Also my proto files are correctly configured so that is not an issue.
server I am calling:
func main() {
lis, err := net.Listen("tcp", ":10006")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
grpcServer := grpc.NewServer()
newServer := &RedisGatewayServer{}
rs.RegisterRedisGatewayServer(grpcServer, newServer)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
The function I am trying to access from client:
func (s *RedisGatewayServer) GetData(ctx context.Context, in *rs.KeyRequest)(*rs.KeyRequest, error) {
return in, nil
}
My docker and yaml files are all correct also with the right naming and ports.
I had this exact problem, and it was because of a very simple mistake: I had put the call to the service registration after the server start. The code looked like this:
err = s.Serve(listener)
if err != nil {
log.Fatalf("[x] serve: %v", err)
}
primepb.RegisterPrimeServiceServer(s, &server{})
Obviously, the registration should have been called before the server was ran:
primepb.RegisterPrimeServiceServer(s, &server{})
err = s.Serve(listener)
if err != nil {
log.Fatalf("[x] serve: %v", err)
}
thanks #dolan's for the comment, it solved the problem.
Basically we have to make sure, the method value should be same in both server and client (you can even copy the method name from the pb.go file generated from server side)
func (cc *ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...CallOption) error {
this invoke function will be there inside all the methods which you have implemented in gRPC service.
for me, my client was connecting to the wrong server port.
My server listens to localhost:50052
My client connects to localhost:50051
and hence I see this error.
I had the same problem - I was able to perform rpc call from Postman to the service whereas apigateway was able to connect to the service but on method call It gave error code 12 unknown service and the reason was in my proto files client was under package X whereas server was under package Y.
Quite silly but yeah making the package for both proto under apigateway and service solved my problem.
There are many scenarios in which this can happen. The underlying issue seems to be the same - the GRPC server is reachable but cannot service client requests.
The two scenarios I faced that are not documented in previous answers are:
1. Client and server are not running the same contract version
A breaking change introduced in the contract can cause this error.
In my case, the server was running the older version of the contract while the client was running the latest one.
A breaking change meant that the server could not resolve the service my client was asking for thus returning the unimplemented error.
2. The client is connecting to the wrong GRPC server
The client reached the incorrect server that doesn't implement the contract.
Consider this scenario if you're running multiple different GRPC services. You might be mistakingly dialing the wrong one.

Is it OK to use gRPC to push data?

I'm wondering if it's a good idea to push data from gRPC server to a client. Basically I want to use a pub/sub pattern with gRPC.
The way I do is that I return a response stream on the server implementation that I never close. Then, the client has a never ending go routine in charge of reading this stream.
Here is an example:
service Service {
rpc RegularChanges (Void) returns (stream Change) {}
}
On the server side:
func (self *MyServiceImpl) RegularChanges(in *pb.Void, stream pb.Service_RegularChangesServer) error {
for {
d, err := time.ParseDuration("1s")
if err != nil {
log.Fatalf("Cannot parse duration")
break;
}
time.Sleep(d)
stream.Send(&pb.Change{Name:"toto", Description:"status changed"})
}
return nil
}
On client:
for {
change, err := streamChanges.Recv()
if err != nil {
log.Fatalf("Error retrieving change")
} else {
log.Println(change)
}
}
I just began with go and gRPC but I know it's based on HTTP2, hence it should support pushing datas. However, I'm not sure this is the way gRPC should be used.
gRPC is intended to be used in this way.
You should still consider how the client should behave on failures and how you may want to re-balance across backends. If your connection is going across the Internet, you may also want to enable keepalive to detect connection breakages by providing KeepaliveParams to the client and server.

Resources