Skip to content

Sync lambda deadline preservable#1032

Open
kans wants to merge 2 commits into
mainfrom
sync-lambda-deadline-preservable
Open

Sync lambda deadline preservable#1032
kans wants to merge 2 commits into
mainfrom
sync-lambda-deadline-preservable

Conversation

@kans

@kans kans commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Treat DeadlineExceeded as preservable and fix grpc-timeout propagation

Execution timeouts (e.g. a serverless connector hitting its platform's hard execution limit) currently cause syncs to throw away all checkpointed progress, and the lambda gRPC transport's deadline-propagation path doesn't actually work. This PR fixes both, so timed-out syncs resume from their checkpoint instead of restarting from zero.

Changes

1. pkg/sync: IsSyncPreservable now treats timeouts as preservable

The lambda transport already maps function timeouts to codes.DeadlineExceeded (a retryable, resumable condition), but IsSyncPreservable omitted it from the allowlist, so the c1z checkpoint was discarded on timeout. This adds:

  • codes.DeadlineExceeded to the gRPC status allowlist.
  • An errors.Is(err, context.DeadlineExceeded) check for wrapped bare context deadline errors, which carry no gRPC status (status.FromError does not map context.DeadlineExceeded; only status.FromContextError does).

2. pkg/lambda/grpc: fix dead grpc-timeout check in the server

TimeoutForRequest read the header with metadata.MD.Get, which returns []string, then gated on len(v) > 1 — requiring two header values. A single grpc-timeout header (the only realistic case) never applied, making the server-side timeout path dead code. Now len(v) > 0.

3. pkg/lambda/grpc: client propagates the caller's deadline

clientConn.Invoke now resolves the long-standing TODO: if the context has a deadline, it sets the grpc-timeout header (on a copy of the caller's metadata) so the server can bound the handler context; if the deadline has already expired it returns codes.DeadlineExceeded without invoking the function. Adds encodeTimeout (ported from grpc-go's internal/grpcutil, mirroring the existing decodeTimeout).

Tests

  • IsSyncPreservable unit tests: nil, ErrSyncNotComplete, ErrTooManyWarnings, Unavailable, DeadlineExceeded status, wrapped context.DeadlineExceeded, Internal (false), plain error (false).
  • TimeoutForRequest regression tests: single header applies, no header, malformed value errors.
  • encodeTimeout/decodeTimeout round-trips across unit ranges (1ns through 2,000,000h).
  • clientConn.Invoke: header set when deadline present, absent otherwise, expired deadline short-circuits without calling the transport; caller metadata is not mutated.

go build ./..., go test ./pkg/sync/... ./pkg/lambda/..., and make lint all pass.

Notes for reviewers

  • This is the SDK half of the fix; the host-side error handling (preserving the DeadlineExceeded status through the invoker and persisting the c1z on timeout) lands separately.
  • Deadline propagation is a correctness fix for

@kans
kans requested a review from a team July 23, 2026 00:40
Comment thread pkg/connectorbuilder/resource_syncer.go Outdated
err = status.Error(codes.InvalidArgument, "error: list entitlements requires a resource")
b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start), err)
return nil, err
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion (low confidence): A nil Resource now returns codes.InvalidArgument, whereas previously it fell through to the map lookup on an empty resource type and returned codes.NotFound. This is more correct, but per the SDK's status-code-stability guideline it's a callable-visible status change — worth a note in the PR description so downstream callers that branch on NotFound for this case are aware. ListGrants has the same change at line 339.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

General PR Review: Sync lambda deadline preservable

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: loaded .claude/skills/ci-review.md from trusted base 00a4b95fc8ff
Review mode: incremental since 96e513dd
View review run

Review Summary
The full PR diff was scanned for security and correctness. The new commit adds TestServerHandlerAppliesGrpcTimeoutEndToEnd, which exercises the server Handler deriving a context.WithTimeout from the grpc-timeout header end-to-end, addressing the prior test-gap finding, which is now resolved. The client-side deadline propagation, the len(v) > 1 to len(v) > 0 server fix, the ported encodeTimeout, and the new DeadlineExceeded handling in IsSyncPreservable are all correct. No new issues found; one carried-over suggestion about the version bump remains.

Security Issues
None found.

Correctness Issues
None found.

Suggestions

  • pkg/sdk/version.go:3 - Version remains v0.20.1 despite two default-behavior changes: IsSyncPreservable now treats DeadlineExceeded as preservable, and the grpc-timeout fixes make server-side deadline enforcement live for the first time (handlers that previously ran unbounded now get cancelled at the caller deadline). Per repo criteria a 0.x minor bump is the compatibility signal for default-behavior changes - consider bumping and/or adding a migration note before release (low confidence; carried over from prior review).
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

Suggestions

In pkg/sdk/version.go:
- Around line 3: The constant Version is still v0.20.1, but this PR changes default behavior in two ways: pkg/sync/syncer.go IsSyncPreservable now treats bare and gRPC DeadlineExceeded errors as preservable, and pkg/lambda/grpc/server.go plus client.go now enforce grpc-timeout deadlines server-side for the first time (the len(v) > 1 to len(v) > 0 fix activated a previously dead code path). Per the repo compatibility criteria, a 0.x minor bump is the signal for default-behavior changes. Consider bumping Version (e.g. to v0.21.0) and/or adding a migration note describing the new deadline-cancellation behavior before release.

…ut propagation

- pkg/sync: IsSyncPreservable now returns true for codes.DeadlineExceeded
  and wrapped context.DeadlineExceeded errors, so checkpoint c1z files
  survive AWS Lambda hard timeouts and the sync can resume.
- pkg/lambda/grpc: fix dead grpc-timeout check in TimeoutForRequest
  (len(v) > 1 could never match a single header value; now len(v) > 0).
- pkg/lambda/grpc: clientConn.Invoke propagates the ctx deadline via the
  grpc-timeout header (encodeTimeout ported from grpc-go), returning
  DeadlineExceeded without invoking when the deadline already passed.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@kans
kans force-pushed the sync-lambda-deadline-preservable branch from 3e5db5b to 96e513d Compare July 23, 2026 16:41

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@kans kans changed the title [wip] Sync lambda deadline preservable Sync lambda deadline preservable Jul 23, 2026
The len(v) > 0 fix made the server timeout path live for the first time.
Lock in the behavior: a request with a grpc-timeout header yields a handler
context carrying the deadline, a handler blocking past it produces a
DeadlineExceeded status response, and requests without the header get no
deadline.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/lambda/grpc/server.go
func TimeoutForRequest(req *Request) (time.Duration, bool, error) {
v := req.Headers().Get("grpc-timeout")
if len(v) > 1 {
if len(v) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this 1? We were only checking for multiple grpc-timeout headers?

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants