GRPC Node client retry interceptor

42 Views Asked by At

I'm have implemented node grpc client retry using interceptor but it is only retrying once and not upto maxRetries which is specified. Also, the error is not propagating to the caller. Can you please where i'm missing in fixing this?

function createRetryInterceptor(maxRetries = 3, backoffInterval) {
  return function (options, nextCall) {
    let retries = 0;

    return new grpc.InterceptingCall(nextCall(options), {
      start: function (metadata, listener, next) {
        function retryOrNext(status) {
          if (status.code === grpc.status.UNAVAILABLE && retries < maxRetries) {
            retries++;

              console.log(`Retry ${retries} after ${backoffInterval}ms`);
              console.error('Error message:', status.details);

            // Create a new call with the same options
            const call = nextCall(options);
            call.start(metadata, listener, retryOrNext);

            // Add a timeout for retry
            setTimeout(() => {
              call.cancel(); // Cancel the retry call if it takes too long
            }, backoffInterval);
          } else {
            // Propagate errors from interceptor as well
            if (status.code !== grpc.status.OK) {
              next(status);
              //return; // Exit the retry loop if not OK or UNAVAILABLE
            }
            // Proceed with successful call or other non-retriable errors
            next(status);
          }
        }

        // Wrap the listener to handle potential errors in onReceiveStatus
        const wrappedListener = {
          onReceiveStatus: function (status, next) {
            try {
              retryOrNext(status);
            } catch (error) {
              console.error('Error in onReceiveStatus:', error);
              next(status);
            }
          },
          // Forward other listener methods if present
          ...listener,
        };

        next(metadata, wrappedListener);
      }
    });
  };
}

static promiseBuilderFn(resolve, reject) {
    return (err, response) => {
      if (err != null) {
        console.log(`error in calling promise err: ${err}`);
        reject(err);
      } else {
        console.log(`success: ${response}`);
        resolve(response);
      }
    };
  }

The grpc client i have created is like below

const client = new MyClient(
  'localhost:5001',
  grpc.credentials.createInsecure(),
    {interceptors: [createRetryInterceptor(3, 5000)]}
);

and i'm calling the grpc service method as follows

return new Promise((resolve, reject) => {
      client.getData(
        request,
        metadata,
        promiseBuilderFn(resolve, reject)
      );
    })

I tried implementing the interceptor as mentioned in https://github.com/grpc/proposal/blob/master/L5-node-client-interceptors.md#examples but it is giving error as TypeError: nextCall is not a function

1

There are 1 best solutions below

0
murgatroid99 On

Your retryOrNext function closes over the next parameter to start, so that is what it calls when it doesn't retry. What you actually want is to call the next parameter to onReceiveStatus. You can do that by having retryOrNext itself take a next parameter and call that, and pass that in when calling it from onReceiveStatus.