Skip to content

Error handling

Dispatch retries the execution of a function whenever a failure occurs, whether it’s an exception, a loss of connection or power outage. However, not all failures are the same, so you might want to teach Dispatch how to handle exceptions specific to your application.

Temporary errors

When error is temporary, tell Dispatch that it’s safe to retry that function until it succeeds. For example, when application is in maintenance mode, tell Dispatch to retry the function until application is back up again.

import dispatch
from dispatch import Status
class MaintenanceException(Exception):
pass
@dispatch.function
async def notify_user(user_id):
if is_maintenance_on():
raise MaintenanceException()
await send_email_notification(user_id)
dispatch.register_error_type(MaintenanceException, Status.TEMPORARY_ERROR)
def main():
notify_user.dispatch(1)
dispatch.run(main)

register_error_type function tells Dispatch that whenever MaintenanceException occurs, treat it as a temporary error and retry.

Permanent errors

When error is fatal, retrying doesn’t make sense, because the function will keep running into it. Dispatch stops retrying a function when it returns a PERMANENT_ERROR status.

For example, the user we wanted to send email to is now deactivated.

import dispatch
from dispatch import Status
class UserDeactivated(Exception):
pass
@dispatch.function
async def notify_user(user_id):
user = await fetch_user_from_database(user_id)
if user.deactivated:
raise UserDeactivated()
await send_email_notification(user_id)
dispatch.register_error_type(MaintenanceException, Status.PERMANENT_ERROR)
def main():
notify_user.dispatch(1)
dispatch.run(main)

Retrying on success

There are also cases when a function has successfully completed, but you may still want to retry it.

For example, a hosting provider that waits for a user to add a DNS record, so they can connect their domain to a website.

import dispatch
from dispatch import Status
class DNSVerification:
def __init__(self, dns_records):
required_dns_records = filter(
lambda dns_record: dns_record.value == "1.2.3.4", dns_records
)
self.verified = len(required_dns_records) > 0
@dispatch.function
async def wait_for_dns_records(domain):
dns_records = await fetch_dns_records(domain)
return DNSVerification(dns_records)
# `dns_verification` is an instance of `DNSVerification`, returned from `wait_for_dns_record`
def confirm_dns_records_presence(dns_verification):
if not dns_verification.verified:
return Status.TEMPORARY_ERROR
return Status.OK
dispatch.register_output_type(DNSVerification, confirm_dns_records_presence)
@dispatch.function
async def connect_domain(domain):
await wait_for_dns_records(domain)
await mark_domain_as_connected_in_database(domain)
def main():
connect_domain.dispatch("example.com")
dispatch.run(main)

When Dispatch notices a DNSVerification instance as a return value, confirm_dns_records_presence is executed to tell Dispatch what it should do next — retry later or complete execution. If user hasn’t added DNS records yet, Dispatch will keep retrying wait_for_dns_records function. When DNS records show up, connect_domain will resume execution and mark domain as connected.

Status reference

StatusDescriptionBehavior
Status.OKFunction executed successfullyDone.
Status.TEMPORARY_ERRORTemporary error occurredRetry function later.
Status.PERMANENT_ERRORTemporary error occurredRetry function later.
Status.TIMEOUTOperation took too long.Retry function later.
Status.INVALID_ARGUMENTInvalid input.Fatal error, don’t retry.
Status.DNS_ERRORTODOTODO
Status.TCP_ERRORTODOTODO
Status.TLS_ERRORTODOTODO
Status.HTTP_ERRORTODOTODO
Status.UNAUTHENTICATEDTODOTODO
Status.PERMISSION_DENIEDTODOTODO
Status.NOT_FOUNDTODOTODO
Status.INVALID_RESPONSETODOTODO
Status.INCOMPATIBLE_STATETODOTODO