Google Cloud Tasks Execution¶
CloudTasksExecutionBackend (configured via CloudTasksExecutionConfig) provides scale-to-zero HTTP push execution on Google Cloud Run without running an always-on polling worker process.
ID-Only Wire Contract & At-Least-Once Delivery¶
Every Cloud Tasks HTTP delivery body carries only the protocol version and the record's id:
Task arguments, keyword arguments, result payloads, retry state, and metadata stay in your storage backend and never cross the network inside the Cloud Tasks message. When Cloud Tasks posts the delivery payload to your consumer service, the route handler delegates to consume_one(service, task_id), which claims the authoritative record from storage by ID and executes the attempt.
Google Cloud Tasks provides an at-least-once delivery guarantee. Although consume_one() fences task claims on expected_retry_count in your storage backend, transient network drops after external side effects can trigger redelivery or retry, so task handlers with external side effects must be idempotent.
Runnable Configuration Example¶
"""Configuring CloudTasksExecutionConfig for consumer and producer-only services."""
from litestar_queues import QueueConfig, WorkerConfig
from litestar_queues.backends.redis import RedisBackendConfig
from litestar_queues.execution.cloudtasks import CloudTasksExecutionConfig
DISPATCH_DEADLINE_SECONDS = 900
def build_consumer_cloud_tasks_config() -> QueueConfig:
"""Configure a Cloud Run service that both enqueues and receives Cloud Tasks HTTP deliveries."""
return QueueConfig(
queue_backend=RedisBackendConfig(url="redis://redis.internal:6379/0"),
execution_backend=CloudTasksExecutionConfig(
project_id="my-gcp-project",
location="us-central1",
queue_id="primary-tasks",
service_url="https://worker-service-uc.a.run.app",
service_account_email="cloud-tasks-invoker@my-gcp-project.iam.gserviceaccount.com",
dispatch_deadline=DISPATCH_DEADLINE_SECONDS,
response_margin=30.0,
default_task_timeout=840.0,
mount_route=True,
trust_platform_auth=True,
verify_oidc=True,
),
worker=WorkerConfig(placement="external", stale_after=930.0),
)
def build_producer_only_cloud_tasks_config() -> QueueConfig:
"""Configure a public web tier that dispatches to Cloud Tasks without mounting the delivery route."""
return QueueConfig(
queue_backend=RedisBackendConfig(url="redis://redis.internal:6379/0"),
execution_backend=CloudTasksExecutionConfig(
project_id="my-gcp-project",
location="us-central1",
queue_id="primary-tasks",
service_url="https://worker-service-uc.a.run.app",
service_account_email="cloud-tasks-invoker@my-gcp-project.iam.gserviceaccount.com",
mount_route=False,
),
worker=WorkerConfig(placement="external"),
)
async def main() -> None:
"""Verify consumer and producer-only CloudTasksExecutionConfig settings."""
consumer = build_consumer_cloud_tasks_config()
producer = build_producer_only_cloud_tasks_config()
assert isinstance(consumer.execution_backend, CloudTasksExecutionConfig)
assert isinstance(producer.execution_backend, CloudTasksExecutionConfig)
assert consumer.execution_backend.mount_route is True
assert consumer.execution_backend.verify_oidc is True
assert consumer.execution_backend.dispatch_deadline == DISPATCH_DEADLINE_SECONDS
assert producer.execution_backend.mount_route is False
Provisioning Cloud Tasks & Cloud Run¶
The package never creates queues, Cloud Run services, or IAM bindings at runtime; operators provision them via Terraform or gcloud before enqueuing records.
Create the Cloud Tasks Queue¶
gcloud tasks queues create primary-tasks \
--location=us-central1 \
--max-dispatches-per-second=50 \
--max-concurrent-dispatches=100 \
--max-attempts=5 \
--min-backoff=10s \
--max-backoff=300s
Deploy the Cloud Run Consumer Service¶
Because Cloud Tasks pushes each task as an active HTTP request, the Cloud Run service can scale to zero when idle and keep standard request-scoped CPU allocation (--cpu-throttling):
gcloud run deploy worker-service \
--image=us-central1-docker.pkg.dev/my-gcp-project/apps/worker:latest \
--region=us-central1 \
--no-allow-unauthenticated \
--cpu-throttling \
--concurrency=8 \
--min-instances=0 \
--timeout=900
Required IAM Roles¶
| Principal | IAM Role | Target Resource | Purpose |
|---|---|---|---|
Cloud Tasks delivery service account (service_account_email) |
roles/run.invoker |
Cloud Run consumer service | Allows Cloud Tasks to invoke the private --no-allow-unauthenticated Cloud Run service |
| Application runtime service account (producer & consumer) | roles/cloudtasks.enqueuer |
Cloud Tasks queue | Allows QueueService.enqueue() and remote cancellation to create and delete Cloud Tasks |
| Application runtime service account (producer & consumer) | roles/iam.serviceAccountUser |
Cloud Tasks delivery service account | Allows the producer to attach oidc_token.service_account_email when creating Cloud Tasks |
Route Mounting & OIDC Authentication¶
When CloudTasksExecutionConfig.mount_route=True (the default), QueuePlugin registers the delivery endpoint at /_litestar-queues/cloud-tasks (or /_<namespace>/cloud-tasks when a custom QueueNamespace is configured) with opt={"exclude_from_auth": True, "exclude_from_csrf": True} so application-wide session or CSRF middleware does not intercept Cloud Tasks deliveries.
To prevent unauthenticated delivery spoofing, CloudTasksExecutionConfig requires at least one route protection layer when mount_route=True:
- In-Process OIDC Verification (
verify_oidc=True): AttachesCloudTasksOIDCGuard, which verifies theAuthorization: Bearer <token>JWT signature against Google's certs, checks that the tokenaudmatchesaudience(defaulting to theservice_urlorigin), and verifies thatemailmatchesservice_account_email. - Platform IAM (
trust_platform_auth=True): Accepts Cloud Run's--no-allow-unauthenticatedIAM perimeter (Combine bothtrust_platform_auth=Trueandverify_oidc=Truefor defense in depth). - Custom Litestar Guards (
guards=(...)): Attaches application-supplied LitestarGuardcallables to the route.
For public web tiers that only enqueue tasks and never execute them, set mount_route=False.
Delivery HTTP Response Semantics¶
The /_litestar-queues/cloud-tasks endpoint translates consume_one() outcomes into deterministic HTTP status codes understood by Cloud Tasks:
| HTTP Status | Condition | Cloud Tasks Behavior |
|---|---|---|
204 No Content |
Task completed, permanently failed, was already terminal/cancelled, or was rescheduled for a future retry | Marks the Cloud Tasks delivery succeeded (no transport redelivery needed because QueueService already scheduled any retry in storage) |
503 Service Unavailable |
Task is currently claimed by another live attempt or crashed mid-attempt before reaching a durable status transition | Retries the HTTP delivery according to the Cloud Tasks queue backoff policy |
400 Bad Request / 422 Unprocessable Entity |
Malformed JSON body, unknown field, or unsupported protocol version |
Permanent delivery failure |
401 Unauthorized / 403 Forbidden |
Missing or invalid OIDC Bearer token rejected by CloudTasksOIDCGuard |
Rejects unauthorized caller |
Retry Layers & Inline Stale Recovery¶
- Application Task Retries (
@task(retries=..., retry_backoff=...)): When a task raises a Python exception,QueueServicecatches it, incrementsretry_countin storage, schedules a new Cloud Task atscheduled_at = now + backoff, and returns204 No Contentso the completed HTTP delivery is acknowledged cleanly. - Transport Redelivery & Inline Stale Recovery (
WorkerConfig.stale_after): If a Cloud Run container crashes or hits an OOM kill while a task isrunning, Cloud Tasks receives a5xxor connection reset and redelivers the same{version, task_id}payload. WhenWorkerConfig.stale_afteris configured on the consumer service, the delivery route automatically checks whether the existingrunningclaim'sheartbeat_athas exceededstale_afterand recovers the stale claim inline before executing the redelivered attempt.
Deadlines & the 30-Day Scheduling Horizon¶
- Timeout Budget Equation: Keep task timeouts, response margins, Cloud Tasks dispatch deadlines (
15 secondsto1800seconds), and Cloud Run request timeouts ordered as:
$$\text{task.timeout} + \text{response_margin} \le \text{dispatch_deadline} < \text{Cloud Run } \texttt{--timeout}$$
- 30-Day Horizon (
CLOUD_TASKS_MAX_SCHEDULE_HORIZON): Google Cloud Tasks acceptsschedule_timeup to 30 days in the future. Enqueuing a task or recurring schedule whosescheduled_atexceeds 30 days on"cloudtasks"raisesQueueConfigurationErrorbefore mutating active schedule records.
Cost Model & Scale-to-Zero Trade-Offs (2026)¶
Each successful task execution performs two operations in Cloud Tasks (one create and one HTTP delivery), billed after the monthly free tier while letting the Cloud Run consumer service scale to zero idle instances (--min-instances=0 --cpu-throttling). Note that scaling the Cloud Run worker tier to zero removes idle compute charges for the worker container, not the always-on cost of the backing database (such as Cloud SQL or Memorystore) unless paired with a serverless database tier.
Hybrid Routing with Cloud Run Jobs¶
Because Cloud Tasks caps dispatch_deadline at 30 minutes (1800 seconds), combine CloudTasksExecutionConfig and CloudRunExecutionConfig in QueueConfig.execution_backends to run fast tasks over HTTP push and multi-hour jobs on Cloud Run Jobs:
config = QueueConfig(
queue_backend=backend_config,
execution_backend=cloud_tasks_config,
execution_backends=(cloud_tasks_config, cloud_run_config),
worker=WorkerConfig(placement="external"),
)
Delivery Repair & Remote Cancellation¶
- Two-Phase Enqueue Reservation & Repair:
QueueService.enqueue()writes anexternal-dispatch:<uuid>reservation sentinel onQueuedTaskRecord.execution_refbefore calling the Cloud Tasks API, then finalizesexecution_refwith the created Cloud Task resource path. If a producer process crashes between storage commit and the Cloud Tasks API call,litestar queues run-maintenance --phase external(orQueueService.reconcile_external_tasks()) performs maintenance repair for the missing Cloud Task automatically. - Remote Cancellation: Calling
await queue_service.cancel_task(task_id)deletes the scheduled Cloud Task in Google Cloud Tasks (delete_task) before marking the storage recordcancelled. - Local Testing Without an Emulator: Google does not ship an official Cloud Tasks emulator. Because Cloud Tasks routes through
consume_one(service, task_id), unit and integration tests can test the exact execution path withexecution_backend="local"andawait consume_one(service, handle.id)without running a Cloud Tasks emulator.
Choosing Between Google Cloud Execution Products¶
| Workload Shape | Recommended Transport | Why |
|---|---|---|
Short-to-medium tasks (<= 30 min) with scale-to-zero and sub-second HTTP push |
Google Cloud Tasks (CloudTasksExecutionConfig) |
Direct HTTP push to --min-instances 0 --cpu-throttling Cloud Run services with native schedule_time delays |
Heavy batch, ETL, or GPU jobs (> 30 min, up to 24 hours) |
Google Cloud Run Jobs (CloudRunExecutionConfig) |
Dedicated one-shot job containers per task with named hardware profiles |
| High-fanout event streaming into always-on consumer pools | Google Cloud Pub/Sub (PubSubExecutionConfig) |
High-throughput pull subscriptions consumed by litestar queues run-consumer --backend pubsub |
Periodic cron triggers for litestar queues run-maintenance |
Cloud Scheduler | Invokes maintenance or reconciliation endpoints on a fixed cron schedule rather than dispatching per-task payloads |
| Cloud storage or audit-log event routing | Eventarc | Routes Google Cloud infrastructure events rather than application task queue records (not an execution backend) |