🐙 tako
Reference

Migrating to 2.6

Update session stores, idempotency identity, body limits, and queue lifecycle behavior when upgrading to Tako 2.6.

Tako 2.6.0 adds supervised queues, optional queue limits, HTTP/3 qlog, and built-in gRPC health and reflection. It also changes session stores, idempotency, request limits, and OpenAPI conversion. Rust 1.95 or newer is required.

Session stores and revocation

Custom SessionStore implementations must replace unconditional store/upsert with atomic create, update, rotate, and remove operations. load returns StoredSession { data, version }; updates, rotations, and removals must validate that generation so stale requests cannot restore revoked sessions or delete their replacements. Persist the generation with existing shared-store records. The custom session store contract describes transaction requirements and migration of stored data.

Idempotency identity

Keyed requests require a trusted IdempotencyIdentity by default. After authentication and authorization, insert IdempotencyIdentity::new(user_id).tenant(tenant_id) or configure identity_fn to read the application's identity. Missing identity returns 401. Public endpoints must opt into IdempotencyBuilder::new().anonymous() explicitly. Keep authorization before cache lookup, including retries; see the idempotency guide for middleware ordering.

JWT key types

The jwt-simple integration now uses 0.13 with patched elliptic curve arithmetic. Applications that construct AnyVerifyKey variants or use JWTClaims from a direct jwt-simple dependency must update that dependency to 0.13 as well. Existing tokens and supported signing algorithms remain compatible.

Strict unary gRPC requests

GrpcRequest<T> requires exactly one message and the end of the body. Additional messages and malformed trailing data are rejected. To intentionally read only the first message, use GrpcClientStream<T> and StreamExt::next. Native extractors accept only application/grpc and application/grpc+proto, with MIME parameters; use the separate gRPC-Web adapter for web framing.

Unary requests now obey the router's 2 MiB default BodyLimit, including the frame prefix. Raise it with router.body_limit(bytes) or disable the aggregate cap with router.disable_body_limit(). The 4 MiB per-message ceiling remains active for unary and streaming requests.

Exhaustive matches on GrpcError need GrpcError::BodyTooLarge, which reports the shared cap separately from MessageTooLarge. See gRPC.

Typed multipart limits

TakoTypedMultipart now applies the router's 2 MiB default BodyLimit across all fields. The smaller of this cap and MultipartConfig::total_size_limit applies. Set router.body_limit(bytes) for larger forms; disabling the shared cap leaves explicit multipart limits active. Aggregate failures return 413 and remove temporary files already created by the request.

Exhaustive matches on TypedMultipartError need TypedMultipartError::BodyTooLarge; aggregate size failures now use this variant instead of FieldError.

GraphQL HTTP bodies

GraphQL POST extractors now obey the router's 2 MiB default BodyLimit alongside the existing 4 MiB protocol ceiling. Raise the shared cap with router.body_limit(bytes) when needed; disable_body_limit() leaves the protocol ceiling active.

application/graphql reads a raw UTF-8 query such as { __typename }. JSON requests and batches retain their parsing behavior and avoid a second body buffer. Multipart uploads retain their parser and options. See GraphQL body extractors.

OpenAPI method conversion

openapi::vespera::http_method_to_vespera returns Option<HttpMethod>. Handle None for CONNECT and extension methods instead of treating them as GET. generate_openapi_from_routes omits these routes and logs their method and path; supported operations on the same path remain intact.

use tako::openapi::vespera::http_method_to_vespera;
use tako::Method;

assert!(http_method_to_vespera(&Method::GET).is_some());
assert!(http_method_to_vespera(&Method::CONNECT).is_none());

Queue lifecycle

Queue::start() still returns (). Repeated start() or spawn() calls share one set of workers, and a stopped queue cannot restart. Queue::spawn() returns a cloneable QueueHandle with trigger(), join(), result(), and shutdown(timeout). Keep this handle while workers are needed: dropping its last clone cancels them. With start(), dropping the last queue clone stops workers unless an explicit handle remains.

Shutdown stops admission and moves unstarted jobs to dead letters. Running jobs may finish, but are not retried after shutdown. handle.shutdown(timeout).await returns QueueRunError::Timeout if the deadline expires; call join().await afterward to await cleanup. Cancellation preserves unfinished jobs as dead letters, subject to retention limits. Handlers must yield for cancellation to run.

queue.shutdown(timeout) remains available and discards the result. It waits for workers and cleanup until the deadline, then requests cancellation. See the queue lifecycle guide for ownership rules and worker errors.

Optional queue limits

Queues remain unbounded by default. Configure admission and dead-letter retention separately with QueueLimits:

use tako::queue::{Queue, QueueLimits};

let queue = Queue::builder()
  .limits(QueueLimits::new().max_jobs(1_000).max_dead_letters(100))
  .build();

max_jobs counts ready, delayed, running, and retrying work together. max_payload_bytes caps serialized payload bytes, not total process memory. Use try_push, try_push_delayed, or try_push_dedup to handle typed EnqueueError::Capacity failures. Existing push methods enforce the same limits but report capacity failures as QueueError::HandlerError. The separate MemoryBackend accepts with_limits; see capacity and retention.

HTTP/3 qlog

Enable http3-qlog and configure .h3_qlog_dir(...) on either server builder. The feature implies http3 and works on Tokio and Compio, including WebTransport connections. Logging stays off until a directory is configured; create that directory before starting the server. See qlog configuration for trace contents, output limits, and retention.

Built-in gRPC health and reflection

With grpc, call register(&mut router) on a HealthRegistry to serve Check, Watch, and List. Set the empty service name explicitly for overall server health. Call the same method on a ReflectionRegistry to serve v1 and v1alpha reflection. Import application descriptor sets with register_encoded_file_descriptor_set, including their dependencies. Built-in schemas ship with Tako; consumers need no protoc step for these services. Existing registry methods remain available; reflection service lists are now sorted and deduplicated. See gRPC services.

Request parsing fixes

Host and tenant lookup reject malformed or conflicting HTTP authorities, even when forwarding headers are trusted. QueryMulti form-decodes CSV values before splitting: + becomes a space, %2B stays a plus, and %2C is a separator. WebSockets reject non-minimal payload lengths and reserved length bits. These checks can reject requests that older releases accepted.

Last updated on

On this page