Enable event-driven function retries

This document describes how to enable retrying for event-driven functions . Automatic retrying is not available for HTTP functions.

Why event-driven functions fail to complete

On rare occasions, a function might exit prematurely due to an internal error, and by default the function might or might not be automatically retried.

More typically, an event-driven function might fail to successfully complete due to errors thrown in the function code itself. The reasons this might happen include:

  • The function contains a bug and the runtime throws an exception.
  • The function cannot reach a service endpoint, or times out while trying to do so.
  • The function intentionally throws an exception (for example, when a parameter fails validation).
  • A Node.js function returns a rejected promise, or passes a non- null value to a callback.

In any of the above cases, the function will stop executing and return an error. Event triggers producing the messages have retry policies that you can customize to meet the needs of your function.

Semantics of retry

Cloud Run functions provides at-least-once execution of an event-driven function for each event emitted by an event source. The way you configure retries depends on how you created your function:

  • Functions created in the Google Cloud console or with the Cloud Run Admin APIrequires you to separately create and manage the event triggers. Triggers have default retry behaviors that you can customize to suit the needs of your function.
  • Functions created with the Cloud Functions v2 APIwill implicitly create the necessary event triggers, for example Pub/Sub topics or Eventarc triggers. By default, the retries are disabled for these triggers and can be re-enabled using the Cloud Functions v2 API.

Event driven functions created with Cloud Run

Functions created in the Google Cloud console or with the Cloud Run Admin API requires you to separately create and manage the event triggers. We strongly recommend that you review the default behavior of each trigger type:

Event driven functions created with Cloud Functions v2 API

Functions created using the Cloud Functions v2 API ; for example, using the Cloud Functions gcloud CLI, the REST API, or Terraform, will create and manage event triggers on your behalf. By default, if a function invocation terminates with an error, the function is not invoked again and the event is dropped. When you enable retries on an event-driven function, Cloud Run functions retries a failed function invocation until it completes successfully or the retry window expires.

When retries are not enabled for a function, which is the default, the function always reports that it executed successfully, and 200 OK response codes might appear in its logs. This occurs even if the function encountered an error. To make it clear when your function encounters an error, be sure to appropriately.

Enable or disable retries

To enable or disable retries, you can use the Google Cloud CLI. By default, retries are disabled.

Configure retries from the Google Cloud CLI

To enable retries using the Google Cloud CLI, include the --retry flag when deploying your function:

gcloud  
functions  
deploy  
 FUNCTION_NAME 
  
--retry  
 FLAGS 
...

To disable retries, re-deploy the function without the --retry flag:

gcloud  
functions  
deploy  
 FUNCTION_NAME 
  
 FLAGS 
...

Retry window

This retry window expires after 24 hours. Cloud Run functions retries newly created event-driven functions using an exponential backoff strategy, with an increasing backoff of between 10 and 600 seconds.

Best practices

This section describes best practices for using retries.

Use retry to handle transient errors

Because your function is retried continuously until successful execution, permanent errors like bugs should be eliminated from your code through testing before enabling retries. Retries are best used to handle intermittent or transient failures that have a high likelihood of resolution upon retrying, such as a flaky service endpoint or timeout.

Set an end condition to avoid infinite retry loops

It is best practice to protect your function against continuous looping when using retries. You can do this by including a well-defined end condition, before the function begins processing. Note that this technique only works if your function starts successfully and is able to evaluate the end condition.

A simple yet effective approach is to discard events with timestamps older than a certain time. This helps to avoid excessive executions when failures are either persistent or longer-lived than expected.

For example, this code snippet discards all events older than 10 seconds:

Node.js

  const 
  
 functions 
  
 = 
  
 require 
 ( 
 '@google-cloud/functions-framework' 
 ); 
 /** 
 * Cloud Event Function that only executes within 
 * a certain time period after the triggering event 
 * 
 * @param {object} event The Cloud Functions event. 
 * @param {function} callback The callback function. 
 */ 
 functions 
 . 
 cloudEvent 
 ( 
 'avoidInfiniteRetries' 
 , 
  
 ( 
 event 
 , 
  
 callback 
 ) 
  
 = 
>  
 { 
  
 const 
  
 eventAge 
  
 = 
  
 Date 
 . 
 now 
 () 
  
 - 
  
 Date 
 . 
 parse 
 ( 
 event 
 . 
 time 
 ); 
  
 const 
  
 eventMaxAge 
  
 = 
  
 10000 
 ; 
  
 // Ignore events that are too old 
  
 if 
  
 ( 
 eventAge 
 > 
 eventMaxAge 
 ) 
  
 { 
  
 console 
 . 
 log 
 ( 
 `Dropping event 
 ${ 
 event 
 } 
 with age 
 ${ 
 eventAge 
 } 
 ms.` 
 ); 
  
 callback 
 (); 
  
 return 
 ; 
  
 } 
  
 // Do what the function is supposed to do 
  
 console 
 . 
 log 
 ( 
 `Processing event 
 ${ 
 event 
 } 
 with age 
 ${ 
 eventAge 
 } 
 ms.` 
 ); 
  
 // Retry failed function executions 
  
 const 
  
 failed 
  
 = 
  
 false 
 ; 
  
 if 
  
 ( 
 failed 
 ) 
  
 { 
  
 callback 
 ( 
 'some error' 
 ); 
  
 } 
  
 else 
  
 { 
  
 callback 
 (); 
  
 } 
 }); 
 

Python

  from 
  
 datetime 
  
 import 
 datetime 
 , 
 timezone 
 # The 'python-dateutil' package must be included in requirements.txt. 
 from 
  
 dateutil 
  
 import 
 parser 
 import 
  
 functions_framework 
 @functions_framework 
 . 
 cloud_event 
 def 
  
 avoid_infinite_retries 
 ( 
 cloud_event 
 ): 
  
 """Cloud Event Function that only executes within a certain 
 time period after the triggering event. 
 Args: 
 cloud_event: The cloud event associated with the current trigger 
 Returns: 
 None; output is written to Stackdriver Logging 
 """ 
 timestamp 
 = 
 cloud_event 
 [ 
 "time" 
 ] 
 event_time 
 = 
 parser 
 . 
 parse 
 ( 
 timestamp 
 ) 
 event_age 
 = 
 ( 
 datetime 
 . 
 now 
 ( 
 timezone 
 . 
 utc 
 ) 
 - 
 event_time 
 ) 
 . 
 total_seconds 
 () 
 event_age_ms 
 = 
 event_age 
 * 
 1000 
 # Ignore events that are too old 
 max_age_ms 
 = 
 10000 
 if 
 event_age_ms 
> max_age_ms 
 : 
 print 
 ( 
 "Dropped 
 {} 
 (age 
 {} 
 ms)" 
 . 
 format 
 ( 
 cloud_event 
 [ 
 "id" 
 ], 
 event_age_ms 
 )) 
 return 
 "Timeout" 
 # Do what the function is supposed to do 
 print 
 ( 
 "Processed 
 {} 
 (age 
 {} 
 ms)" 
 . 
 format 
 ( 
 cloud_event 
 [ 
 "id" 
 ], 
 event_age_ms 
 )) 
 return 
 # To retry the execution, raise an exception here 
 

Go

  // Package tips contains tips for writing Cloud Functions in Go. 
 package 
  
 tips 
 import 
  
 ( 
  
 "context" 
  
 "fmt" 
  
 "log" 
  
 "time" 
  
 "github.com/GoogleCloudPlatform/functions-framework-go/functions" 
  
 "github.com/cloudevents/sdk-go/v2/event" 
 ) 
 func 
  
 init 
 () 
  
 { 
  
 functions 
 . 
 CloudEvent 
 ( 
 "FiniteRetryPubSub" 
 , 
  
 FiniteRetryPubSub 
 ) 
 } 
 // MessagePublishedData contains the full Pub/Sub message 
 // See the documentation for more details: 
 // https://cloud.google.com/eventarc/docs/cloudevents#pubsub 
 type 
  
 MessagePublishedData 
  
 struct 
  
 { 
  
 Message 
  
 PubSubMessage 
 } 
 // PubSubMessage is the payload of a Pub/Sub event. 
 // See the documentation for more details: 
 // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage 
 type 
  
 PubSubMessage 
  
 struct 
  
 { 
  
 Data 
  
 [] 
 byte 
  
 `json:"data"` 
 } 
 // FiniteRetryPubSub demonstrates how to avoid inifinite retries. 
 func 
  
 FiniteRetryPubSub 
 ( 
 ctx 
  
 context 
 . 
 Context 
 , 
  
 e 
  
 event 
 . 
 Event 
 ) 
  
 error 
  
 { 
  
 var 
  
 msg 
  
 MessagePublishedData 
  
 if 
  
 err 
  
 := 
  
 e 
 . 
 DataAs 
 ( 
& msg 
 ); 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 return 
  
 fmt 
 . 
 Errorf 
 ( 
 "event.DataAs: %w" 
 , 
  
 err 
 ) 
  
 } 
  
 // Ignore events that are too old. 
  
 expiration 
  
 := 
  
 e 
 . 
 Time 
 (). 
 Add 
 ( 
 10 
  
 * 
  
 time 
 . 
 Second 
 ) 
  
 if 
  
 time 
 . 
 Now 
 (). 
 After 
 ( 
 expiration 
 ) 
  
 { 
  
 log 
 . 
 Printf 
 ( 
 "event timeout: halting retries for expired event '%q'" 
 , 
  
 e 
 . 
 ID 
 ()) 
  
 return 
  
 nil 
  
 } 
  
 // Add your message processing logic. 
  
 return 
  
 processTheMessage 
 ( 
 msg 
 ) 
 } 
 

Java

  import 
  
 com.google.cloud.functions.CloudEventsFunction 
 ; 
 import 
  
 io.cloudevents.CloudEvent 
 ; 
 import 
  
 java.time.Duration 
 ; 
 import 
  
 java.time.ZoneOffset 
 ; 
 import 
  
 java.time.ZonedDateTime 
 ; 
 import 
  
 java.util.logging.Logger 
 ; 
 public 
  
 class 
 RetryTimeout 
  
 implements 
  
 CloudEventsFunction 
  
 { 
  
 private 
  
 static 
  
 final 
  
 Logger 
  
 logger 
  
 = 
  
 Logger 
 . 
 getLogger 
 ( 
 RetryTimeout 
 . 
 class 
 . 
 getName 
 ()); 
  
 private 
  
 static 
  
 final 
  
 long 
  
 MAX_EVENT_AGE 
  
 = 
  
 10_000 
 ; 
  
 /** 
 * Cloud Event Function that only executes within 
 * a certain time period after the triggering event 
 */ 
  
 @Override 
  
 public 
  
 void 
  
 accept 
 ( 
 CloudEvent 
  
 event 
 ) 
  
 throws 
  
 Exception 
  
 { 
  
 ZonedDateTime 
  
 utcNow 
  
 = 
  
 ZonedDateTime 
 . 
 now 
 ( 
 ZoneOffset 
 . 
 UTC 
 ); 
  
 ZonedDateTime 
  
 timestamp 
  
 = 
  
 event 
 . 
 getTime 
 (). 
 atZoneSameInstant 
 ( 
 ZoneOffset 
 . 
 UTC 
 ); 
  
 long 
  
 eventAge 
  
 = 
  
 Duration 
 . 
 between 
 ( 
 timestamp 
 , 
  
 utcNow 
 ). 
 toMillis 
 (); 
  
 // Ignore events that are too old 
  
 if 
  
 ( 
 eventAge 
 > 
 MAX_EVENT_AGE 
 ) 
  
 { 
  
 logger 
 . 
 info 
 ( 
 String 
 . 
 format 
 ( 
 "Dropping event with timestamp %s." 
 , 
  
 timestamp 
 )); 
  
 return 
 ; 
  
 } 
  
 // Process events that are recent enough 
  
 // To retry this invocation, throw an exception here 
  
 logger 
 . 
 info 
 ( 
 String 
 . 
 format 
 ( 
 "Processing event with timestamp %s." 
 , 
  
 timestamp 
 )); 
  
 } 
 } 
 

C#

  using 
  
 CloudNative.CloudEvents 
 ; 
 using 
  
 Google.Cloud.Functions.Framework 
 ; 
 using 
  
 Google.Events.Protobuf.Cloud.PubSub.V1 
 ; 
 using 
  
 Microsoft.Extensions.Logging 
 ; 
 using 
  
 System 
 ; 
 using 
  
 System.Threading 
 ; 
 using 
  
 System.Threading.Tasks 
 ; 
 namespace 
  
 TimeBoundedRetries 
 ; 
 public 
  
 class 
  
 Function 
  
 : 
  
 ICloudEventFunction<MessagePublishedData> 
 { 
  
 private 
  
 static 
  
 readonly 
  
 TimeSpan 
  
 MaxEventAge 
  
 = 
  
 TimeSpan 
 . 
 FromSeconds 
 ( 
 10 
 ); 
  
 private 
  
 readonly 
  
 ILogger 
  
 _logger 
 ; 
  
 // Note: for additional testability, use an injectable clock abstraction. 
  
 public 
  
 Function 
 ( 
 ILogger<Function> 
  
 logger 
 ) 
  
 = 
>  
 _logger 
  
 = 
  
 logger 
 ; 
  
 public 
  
 Task 
  
 HandleAsync 
 ( 
 CloudEvent 
  
 cloudEvent 
 , 
  
 MessagePublishedData 
  
 data 
 , 
  
 CancellationToken 
  
 cancellationToken 
 ) 
  
 { 
  
 string 
  
 textData 
  
 = 
  
 data 
 . 
 Message 
 . 
 TextData 
 ; 
  
 DateTimeOffset 
  
 utcNow 
  
 = 
  
 DateTimeOffset 
 . 
 UtcNow 
 ; 
  
 // Every PubSub CloudEvent will contain a timestamp. 
  
 DateTimeOffset 
  
 timestamp 
  
 = 
  
 cloudEvent 
 . 
 Time 
 . 
 Value 
 ; 
  
 DateTimeOffset 
  
 expiry 
  
 = 
  
 timestamp 
  
 + 
  
 MaxEventAge 
 ; 
  
 // Ignore events that are too old. 
  
 if 
  
 ( 
 utcNow 
 > 
 expiry 
 ) 
  
 { 
  
 _logger 
 . 
 LogInformation 
 ( 
 "Dropping PubSub message '{text}'" 
 , 
  
 textData 
 ); 
  
 return 
  
 Task 
 . 
 CompletedTask 
 ; 
  
 } 
  
 // Process events that are recent enough. 
  
 // If this processing throws an exception, the message will be retried until either 
  
 // processing succeeds or the event becomes too old and is dropped by the code above. 
  
 _logger 
 . 
 LogInformation 
 ( 
 "Processing PubSub message '{text}'" 
 , 
  
 textData 
 ); 
  
 return 
  
 Task 
 . 
 CompletedTask 
 ; 
  
 } 
 } 
 

Ruby

  require 
  
 "functions_framework" 
 FunctionsFramework 
 . 
 cloud_event 
  
 "avoid_infinite_retries" 
  
 do 
  
 | 
 event 
 | 
  
 # Use the event timestamp to determine the event age. 
  
 event_age_secs 
  
 = 
  
 Time 
 . 
 now 
  
 - 
  
 event 
 . 
 time 
 . 
 to_time 
  
 event_age_ms 
  
 = 
  
 ( 
 event_age_secs 
  
 * 
  
 1000 
 ) 
 . 
 to_i 
  
 max_age_ms 
  
 = 
  
 10_000 
  
 if 
  
 event_age_ms 
 > 
 max_age_ms 
  
 # Ignore events that are too old. 
  
 logger 
 . 
 info 
  
 "Dropped 
 #{ 
 event 
 . 
 id 
 } 
 (age 
 #{ 
 event_age_ms 
 } 
 ms)" 
  
 else 
  
 # Do what the function is supposed to do. 
  
 logger 
 . 
 info 
  
 "Handling 
 #{ 
 event 
 . 
 id 
 } 
 (age 
 #{ 
 event_age_ms 
 } 
 ms)..." 
  
 failed 
  
 = 
  
 true 
  
 # Raise an exception to signal failure and trigger a retry. 
  
 raise 
  
 "I failed!" 
  
 if 
  
 failed 
  
 end 
 end 
 

PHP

  /** 
 * This function shows an example method for avoiding infinite retries in 
 * Google Cloud Functions. By default, functions configured to automatically 
 * retry execution on failure will be retried indefinitely - causing an 
 * infinite loop. To avoid this, we stop retrying executions (by not throwing 
 * exceptions) for any events that are older than a predefined threshold. 
 */ 
 use Google\CloudFunctions\CloudEvent; 
 function avoidInfiniteRetries(CloudEvent $event): void 
 { 
 $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); 
 $eventId = $event->getId(); 
 // The maximum age of events to process. 
 $maxAge = 10; // 10 seconds 
 // The age of the event being processed. 
 $eventAge = time() - strtotime($event->getTime()); 
 // Ignore events that are too old 
 if ($eventAge > $maxAge) { 
 fwrite($log, 'Dropping event ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); 
 return; 
 } 
 // Do what the function is supposed to do 
 fwrite($log, 'Processing event: ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); 
 // infinite_retries failed function executions 
 $failed = true; 
 if ($failed) { 
 throw new Exception('Event ' . $eventId . ' failed; retrying...'); 
 } 
 } 
 

Distinguish between functions that can be retried and fatal errors

If your function has retries enabled, any unhandled error will trigger a retry. Make sure that your code captures any errors that shouldn't result in a retry.

Node.js

  const 
  
 functions 
  
 = 
  
 require 
 ( 
 '@google-cloud/functions-framework' 
 ); 
 /** 
 * Register a Cloud Event Function that demonstrates 
 * how to toggle retries using a promise 
 * 
 * @param {object} event The Cloud Event for the function trigger. 
 */ 
 functions 
 . 
 cloudEvent 
 ( 
 'retryPromise' 
 , 
  
 cloudEvent 
  
 = 
>  
 { 
  
 // The Pub/Sub event payload is passed as the CloudEvent's data payload. 
  
 // See the documentation for more details: 
  
 // https://cloud.google.com/eventarc/docs/cloudevents#pubsub 
  
 const 
  
 base64PubsubMessage 
  
 = 
  
 cloudEvent 
 . 
 data 
 . 
 message 
 . 
 data 
 ; 
  
 const 
  
 jsonString 
  
 = 
  
 Buffer 
 . 
 from 
 ( 
 base64PubsubMessage 
 , 
  
 'base64' 
 ). 
 toString 
 (); 
  
 const 
  
 tryAgain 
  
 = 
  
 JSON 
 . 
 parse 
 ( 
 jsonString 
 ). 
 retry 
 ; 
  
 if 
  
 ( 
 tryAgain 
 ) 
  
 { 
  
 throw 
  
 new 
  
 Error 
 ( 
 'Retrying...' 
 ); 
  
 } 
  
 else 
  
 { 
  
 console 
 . 
 error 
 ( 
 'Not retrying...' 
 ); 
  
 return 
  
 Promise 
 . 
 resolve 
 (); 
  
 } 
 }); 
 /** 
 * Cloud Event Function that demonstrates 
 * how to toggle retries using a callback 
 * 
 * @param {object} event The Cloud Event for the function trigger. 
 * @param {function} callback The callback function. 
 */ 
 functions 
 . 
 cloudEvent 
 ( 
 'retryCallback' 
 , 
  
 ( 
 cloudEvent 
 , 
  
 callback 
 ) 
  
 = 
>  
 { 
  
 // The Pub/Sub event payload is passed as the CloudEvent's data payload. 
  
 // See the documentation for more details: 
  
 // https://cloud.google.com/eventarc/docs/cloudevents#pubsub 
  
 const 
  
 base64PubsubMessage 
  
 = 
  
 cloudEvent 
 . 
 data 
 . 
 message 
 . 
 data 
 ; 
  
 const 
  
 jsonString 
  
 = 
  
 Buffer 
 . 
 from 
 ( 
 base64PubsubMessage 
 , 
  
 'base64' 
 ). 
 toString 
 (); 
  
 const 
  
 tryAgain 
  
 = 
  
 JSON 
 . 
 parse 
 ( 
 jsonString 
 ). 
 retry 
 ; 
  
 const 
  
 err 
  
 = 
  
 new 
  
 Error 
 ( 
 'Error!' 
 ); 
  
 if 
  
 ( 
 tryAgain 
 ) 
  
 { 
  
 console 
 . 
 error 
 ( 
 'Retrying:' 
 , 
  
 err 
 ); 
  
 callback 
 ( 
 err 
 ); 
  
 } 
  
 else 
  
 { 
  
 console 
 . 
 error 
 ( 
 'Not retrying:' 
 , 
  
 err 
 ); 
  
 callback 
 (); 
  
 } 
 }); 
 

Python

  import 
  
 base64 
 import 
  
 json 
 import 
  
 functions_framework 
 from 
  
 google.cloud 
  
 import 
 error_reporting 
 error_client 
 = 
 error_reporting 
 . 
  Client 
 
 () 
 @functions_framework 
 . 
 cloud_event 
 def 
  
 retry_or_not 
 ( 
 cloud_event 
 ): 
  
 """Cloud Event Function that demonstrates how to toggle retries. 
 Args: 
 cloud_event: The cloud event with a Pub/Sub data payload 
 Returns: 
 None; output is written to Stackdriver Logging 
 """ 
 # The Pub/Sub event payload is passed as the CloudEvent's data payload. 
 # See the documentation for more details: 
 # https://cloud.google.com/eventarc/docs/cloudevents#pubsub 
 encoded_pubsub_message 
 = 
 cloud_event 
 . 
 data 
 [ 
 "message" 
 ][ 
 "data" 
 ] 
 # Retry based on a user-defined parameter 
 try_again 
 = 
 json 
 . 
 loads 
 ( 
 base64 
 . 
 b64decode 
 ( 
 encoded_pubsub_message 
 ) 
 . 
 decode 
 ())[ 
 "retry" 
 ] 
 try 
 : 
 raise 
 RuntimeError 
 ( 
 "I failed you" 
 ) 
 except 
 RuntimeError 
 : 
 error_client 
 . 
  report_exception 
 
 () 
 if 
 try_again 
 : 
 raise 
 # Raise the exception and try again 
 else 
 : 
 pass 
 # Swallow the exception and don't retry 
 

Go

  package 
  
 tips 
 import 
  
 ( 
  
 "context" 
  
 "errors" 
  
 "fmt" 
  
 "log" 
  
 "github.com/GoogleCloudPlatform/functions-framework-go/functions" 
  
 "github.com/cloudevents/sdk-go/v2/event" 
 ) 
 func 
  
 init 
 () 
  
 { 
  
 functions 
 . 
 CloudEvent 
 ( 
 "RetryPubSub" 
 , 
  
 RetryPubSub 
 ) 
 } 
 // MessagePublishedData contains the full Pub/Sub message 
 // See the documentation for more details: 
 // https://cloud.google.com/eventarc/docs/cloudevents#pubsub 
 type 
  
 MessagePublishedData 
  
 struct 
  
 { 
  
 Message 
  
 PubSubMessage 
 } 
 // PubSubMessage is the payload of a Pub/Sub event. 
 // See the documentation for more details: 
 // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage 
 type 
  
 PubSubMessage 
  
 struct 
  
 { 
  
 Data 
  
 [] 
 byte 
  
 `json:"data"` 
 } 
 // RetryPubSub demonstrates how to toggle using retries. 
 func 
  
 RetryPubSub 
 ( 
 ctx 
  
 context 
 . 
 Context 
 , 
  
 e 
  
 event 
 . 
 Event 
 ) 
  
 error 
  
 { 
  
 var 
  
 msg 
  
 MessagePublishedData 
  
 if 
  
 err 
  
 := 
  
 e 
 . 
 DataAs 
 ( 
& msg 
 ); 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 return 
  
 fmt 
 . 
 Errorf 
 ( 
 "event.DataAs: %w" 
 , 
  
 err 
 ) 
  
 } 
  
 name 
  
 := 
  
 string 
 ( 
 msg 
 . 
 Message 
 . 
 Data 
 ) 
  
 if 
  
 name 
  
 == 
  
 "" 
  
 { 
  
 name 
  
 = 
  
 "World" 
  
 } 
  
 // A misconfigured client will stay broken until the function is redeployed. 
  
 client 
 , 
  
 err 
  
 := 
  
 MisconfiguredDataClient 
 () 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Printf 
 ( 
 "MisconfiguredDataClient (retry denied):  %v" 
 , 
  
 err 
 ) 
  
 // A nil return indicates that the function does not need a retry. 
  
 return 
  
 nil 
  
 } 
  
 // Runtime error might be resolved with a new attempt. 
  
 if 
  
 err 
  
 = 
  
 FailedWriteOperation 
 ( 
 client 
 , 
  
 name 
 ); 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Printf 
 ( 
 "FailedWriteOperation (retry expected): %v" 
 , 
  
 err 
 ) 
  
 // A non-nil return indicates that a retry is needed. 
  
 return 
  
 err 
  
 } 
  
 return 
  
 nil 
 } 
 

Java

  import 
  
 com.google.cloud.functions.CloudEventsFunction 
 ; 
 import 
  
 com.google.gson.Gson 
 ; 
 import 
  
 com.google.gson.JsonElement 
 ; 
 import 
  
 com.google.gson.JsonObject 
 ; 
 import 
  
 functions.eventpojos.PubSubBody 
 ; 
 import 
  
 io.cloudevents.CloudEvent 
 ; 
 import 
  
 java.nio.charset.StandardCharsets 
 ; 
 import 
  
 java.util.Base64 
 ; 
 import 
  
 java.util.logging.Logger 
 ; 
 public 
  
 class 
 RetryPubSub 
  
 implements 
  
 CloudEventsFunction 
  
 { 
  
 private 
  
 static 
  
 final 
  
 Logger 
  
 logger 
  
 = 
  
 Logger 
 . 
 getLogger 
 ( 
 RetryPubSub 
 . 
 class 
 . 
 getName 
 ()); 
  
 // Use Gson (https://github.com/google/gson) to parse JSON content. 
  
 private 
  
 static 
  
 final 
  
 Gson 
  
 gson 
  
 = 
  
 new 
  
 Gson 
 (); 
  
 @Override 
  
 public 
  
 void 
  
 accept 
 ( 
 CloudEvent 
  
 event 
 ) 
  
 throws 
  
 Exception 
  
 { 
  
 if 
  
 ( 
 event 
 . 
 getData 
 () 
  
 == 
  
 null 
 ) 
  
 { 
  
 logger 
 . 
 warning 
 ( 
 "No data found in event!" 
 ); 
  
 return 
 ; 
  
 } 
  
 // Extract Cloud Event data and convert to PubSubBody 
  
 String 
  
 cloudEventData 
  
 = 
  
 new 
  
 String 
 ( 
 event 
 . 
 getData 
 (). 
 toBytes 
 (), 
  
 StandardCharsets 
 . 
 UTF_8 
 ); 
  
 PubSubBody 
  
 body 
  
 = 
  
 gson 
 . 
 fromJson 
 ( 
 cloudEventData 
 , 
  
 PubSubBody 
 . 
 class 
 ); 
  
 String 
  
 encodedData 
  
 = 
  
 body 
 . 
 getMessage 
 (). 
 getData 
 (); 
  
 String 
  
 decodedData 
  
 = 
  
 new 
  
 String 
 ( 
 Base64 
 . 
 getDecoder 
 (). 
 decode 
 ( 
 encodedData 
 ), 
  
 StandardCharsets 
 . 
 UTF_8 
 ); 
  
 // Retrieve and decode PubSubMessage data into a JsonElement. 
  
 // Function is expecting a user-supplied JSON message which determines whether 
  
 // to retry or not. 
  
 JsonElement 
  
 jsonPubSubMessageElement 
  
 = 
  
 gson 
 . 
 fromJson 
 ( 
 decodedData 
 , 
  
 JsonElement 
 . 
 class 
 ); 
  
 boolean 
  
 retry 
  
 = 
  
 false 
 ; 
  
 // Get the value of the "retry" JSON parameter, if one exists 
  
 if 
  
 ( 
 jsonPubSubMessageElement 
  
 != 
  
 null 
 && 
 jsonPubSubMessageElement 
 . 
 isJsonObject 
 ()) 
  
 { 
  
 JsonObject 
  
 jsonPubSubMessageObject 
  
 = 
  
 jsonPubSubMessageElement 
 . 
 getAsJsonObject 
 (); 
  
 if 
  
 ( 
 jsonPubSubMessageObject 
 . 
 has 
 ( 
 "retry" 
 ) 
 && 
 jsonPubSubMessageObject 
 . 
 get 
 ( 
 "retry" 
 ). 
 getAsBoolean 
 ()) 
  
 { 
  
 retry 
  
 = 
  
 true 
 ; 
  
 } 
  
 } 
  
 // Retry if appropriate 
  
 if 
  
 ( 
 retry 
 ) 
  
 { 
  
 // Throwing an exception causes the execution to be retried 
  
 throw 
  
 new 
  
 RuntimeException 
 ( 
 "Retrying..." 
 ); 
  
 } 
  
 else 
  
 { 
  
 logger 
 . 
 info 
 ( 
 "Not retrying..." 
 ); 
  
 } 
  
 } 
 } 
 

C#

  using 
  
 CloudNative.CloudEvents 
 ; 
 using 
  
 Google.Cloud.Functions.Framework 
 ; 
 using 
  
 Google.Events.Protobuf.Cloud.PubSub.V1 
 ; 
 using 
  
 Microsoft.Extensions.Logging 
 ; 
 using 
  
 System 
 ; 
 using 
  
 System.Text.Json 
 ; 
 using 
  
 System.Threading 
 ; 
 using 
  
 System.Threading.Tasks 
 ; 
 namespace 
  
 Retry 
 ; 
 public 
  
 class 
  
 Function 
  
 : 
  
 ICloudEventFunction<MessagePublishedData> 
 { 
  
 private 
  
 readonly 
  
 ILogger 
  
 _logger 
 ; 
  
 public 
  
 Function 
 ( 
 ILogger<Function> 
  
 logger 
 ) 
  
 = 
>  
 _logger 
  
 = 
  
 logger 
 ; 
  
 public 
  
 Task 
  
 HandleAsync 
 ( 
 CloudEvent 
  
 cloudEvent 
 , 
  
 MessagePublishedData 
  
 data 
 , 
  
 CancellationToken 
  
 cancellationToken 
 ) 
  
 { 
  
 bool 
  
 retry 
  
 = 
  
 false 
 ; 
  
 string 
  
 text 
  
 = 
  
 data 
 . 
 Message 
 ?. 
 TextData 
 ; 
  
 // Get the value of the "retry" JSON parameter, if one exists. 
  
 if 
  
 ( 
 ! 
 string 
 . 
 IsNullOrEmpty 
 ( 
 text 
 )) 
  
 { 
  
 JsonElement 
  
 element 
  
 = 
  
 JsonSerializer 
 . 
 Deserialize<JsonElement> 
 ( 
 data 
 . 
 Message 
 . 
 TextData 
 ); 
  
 retry 
  
 = 
  
 element 
 . 
 TryGetProperty 
 ( 
 "retry" 
 , 
  
 out 
  
 var 
  
 property 
 ) 
  
&&  
 property 
 . 
 ValueKind 
  
 == 
  
 JsonValueKind 
 . 
 True 
 ; 
  
 } 
  
 // Throwing an exception causes the execution to be retried. 
  
 if 
  
 ( 
 retry 
 ) 
  
 { 
  
 throw 
  
 new 
  
 InvalidOperationException 
 ( 
 "Retrying..." 
 ); 
  
 } 
  
 else 
  
 { 
  
 _logger 
 . 
 LogInformation 
 ( 
 "Not retrying..." 
 ); 
  
 } 
  
 return 
  
 Task 
 . 
 CompletedTask 
 ; 
  
 } 
 } 
 

Ruby

  require 
  
 "functions_framework" 
 FunctionsFramework 
 . 
 cloud_event 
  
 "retry_or_not" 
  
 do 
  
 | 
 event 
 | 
  
 try_again 
  
 = 
  
 event 
 . 
 data 
 [ 
 "retry" 
 ] 
  
 begin 
  
 # Simulate a failure 
  
 raise 
  
 "I failed!" 
  
 rescue 
  
 RuntimeError 
  
 = 
>  
 e 
  
 logger 
 . 
 warn 
  
 "Caught an error: 
 #{ 
 e 
 } 
 " 
  
 if 
  
 try_again 
  
 # Raise an exception to return a 500 and trigger a retry. 
  
 logger 
 . 
 info 
  
 "Trying again..." 
  
 raise 
  
 ex 
  
 else 
  
 # Return normally to end processing of this event. 
  
 logger 
 . 
 info 
  
 "Giving up." 
  
 end 
  
 end 
 end 
 

PHP

  use Google\CloudFunctions\CloudEvent; 
 function tipsRetry(CloudEvent $event): void 
 { 
 $cloudEventData = $event->getData(); 
 $pubSubData = $cloudEventData['message']['data']; 
 $json = json_decode(base64_decode($pubSubData), true); 
 // Determine whether to retry the invocation based on a parameter 
 $tryAgain = $json['some_parameter']; 
 if ($tryAgain) { 
 /** 
 * Functions with automatic retries enabled should throw exceptions to 
 * indicate intermittent failures that a retry might fix. In this 
 * case, a thrown exception will cause the original function 
 * invocation to be re-sent. 
 */ 
 throw new Exception('Intermittent failure occurred; retrying...'); 
 } 
 /** 
 * If a function with retries enabled encounters a non-retriable 
 * failure, it should return *without* throwing an exception. 
 */ 
 $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); 
 fwrite($log, 'Not retrying' . PHP_EOL); 
 } 
 

Make retryable event-driven functions idempotent

Event-driven functions that can be retried must be idempotent. Here are some general guidelines for making such a function idempotent:

  • Many external APIs (such as Stripe) let you supply an idempotency key as a parameter. If you are using such an API, you should use the event ID as the idempotency key.
  • Idempotency works well with at-least-once delivery, because it makes it safe to retry. So a general best practice for writing reliable code is to combine idempotency with retries.
  • Make sure that your code is internally idempotent. For example:
    • Make sure that mutations can happen more than once without changing the outcome.
    • Query database state in a transaction before mutating the state.
    • Make sure that all side effects are themselves idempotent.
  • Impose a transactional check outside the function, independent of the code. For example, persist state somewhere recording that a given event ID has already been processed.
  • Deal with duplicate function calls out-of-band. For example, have a separate clean up process that cleans up after duplicate function calls.

Configure the retry policy

Depending on the needs of your Cloud Run function, you may want to configure the retry policy directly. This would allow you to set up any combination of the following:

  • Shorten the retry window from 7 days to as little as 10 minutes.
  • Change the minimum and maximum backoff time for the exponential backoff retry strategy.
  • Change the retry strategy to retry immediately.
  • Configure a dead-letter topic .
  • Set a maximum and minimum number of delivery attempts.

To configure the retry policy:

  1. Write an HTTP function.
  2. Use the Pub/Sub API to create a Pub/Sub subscription, specifying the URL of the function as the target.

See Pub/Sub documentation on handling failures for a more information on configuring Pub/Sub directly.

Design a Mobile Site
View Site in Mobile | Classic
Share by: