1

I have a Go application, where I create a Pub/Sub client and then I create a subscriber receiving from a topic/subscription I've created locally using a local Docker gcloud emulator container. Everything works fine when the emulator is running - I can publish and subscribe just fine. However, when I kill the container and then start the subscriber app again, I see no errors, and the subscriber's Receive function hangs indefinitely, presumably doing retries over and over again.

I can ensure the client can actually connect to the Pub/Sub server by calling SubscriptionAdminClient.GetSubscription() function and adding a timeout to the context, but I'm not convinced this is necessary or the correct way.

Here is the code from the last iteration:

package my_pubsub

import (
    "context"
    "fmt"

    "cloud.google.com/go/pubsub/v2" // v2.0.0
    "cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
)

type Client struct {
    client *pubsub.Client
}

func NewClient(ctx context.Context) (*Client, error) {
    cl, err := pubsub.NewClient(ctx, "my-project")
    if err != nil {
        return nil, err
    }

    _, err = cl.SubscriptionAdminClient.GetSubscription(ctx, &pubsubpb.GetSubscriptionRequest{
        Subscription: "my-subscription",
    })

    if err != nil {
        return nil, fmt.Errorf("failed to get subscription: %w", err)
    }

    return &Client{
        client: cl,
    }, nil
}

And in my main function:

ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()

// this will time out if the emulator isn't running
client, err := my_pubsub.NewClient(ctx)
if err != nil {
    panic(fmt.Errorf("failed to create pubsub client: %w", err))
}
defer client.Close()

sub := client.Subscriber("my-subscription")

ctx2, cancel2 := context.WithCancel(context.Background())

go func() {
    sigchan := make(chan os.Signal, 1)
    signal.Notify(sigchan, os.Interrupt)
    <-sigchan
    cancel2()
    client.Close()
}()

err := sub.Receive(ctx2, func(ctx context.Context, msg *pubsub.Message){
    
    // do stuff

})

if err != nil {
    panic(err(
}

In this example, the GetSubscription() times out after 10 seconds and I get:

panic: failed to create pubsub client: failed to get subscription: context deadline exceeded

Is there a better way to achieve this?

3
  • 1
    I think SubscriptionAdminClient.GetSubscription is appropriate. You could also TopicAdminClient.ListTopics(...).Next() which is similarly lightweight. NewClient is lazy and so there's no request against the service without methods like these. Unfortunately, Google's underlying gRPC services don't implement public health-checking and so there's no generic mechanism either. Commented Aug 29 at 2:57
  • @DazWilkin Would you say this is a conventional thing to do, checking the health of a cloud service when you start the app? Commented Aug 29 at 9:38
  • 1
    It's very good practice to code defensively and, as you've experienced, an indefinite block isn't a good user experience. It's challenging to cover|understand all exceptions with cloud services because it's not always easy to trigger the events (service down/failing etc.). One caveat here is that the emulator is not a facsimile of Cloud Pub/Sub and you should aim only to code for the behavior of the production service. Commented Aug 29 at 14:29

0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.