Skip to content

feat(kernel): ctx-deadline cancellation, ABI/mTLS/CloudFetch controls, and log forwarding#409

Open
mani-mathur-arch wants to merge 13 commits into
mainfrom
mani/sea-kernel-ctx-cancel
Open

feat(kernel): ctx-deadline cancellation, ABI/mTLS/CloudFetch controls, and log forwarding#409
mani-mathur-arch wants to merge 13 commits into
mainfrom
mani/sea-kernel-ctx-cancel

Conversation

@mani-mathur-arch

@mani-mathur-arch mani-mathur-arch commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

What

Hardens the SEA-via-kernel backend across three areas — context cancellation, connection-security/CloudFetch controls, and log forwarding — all behind the cgo && databricks_kernel build tag. The default CGO_ENABLED=0 Thrift build is unchanged; every kernel-only option is rejected loudly on the Thrift path.

1. Honor context deadlines on connect and mid-fetch

Both blocking cgo calls now route through cancellable C-ABI entry points via a ctxWatcher that bridges a Go context onto a kernel cancel token:

  • OpenSessionkernel_session_open_cancellable (connect)
  • rows.go nextBatchkernel_result_stream_next_batch_cancellable (mid-fetch)

Previously each blocked in an uninterruptible cgo call — a slow warehouse cold-start or a hung CloudFetch chunk ignored the caller's ctx deadline. The watcher fires the token on ctx.Done(), dropping the in-flight kernel request future (a real abort, not a detach). The call sites return the ctx error while preserving the kernel error as its cause, and evict a session-fatal connection before returning. A non-cancellable ctx yields a nil watcher → NULL token → the plain, unchanged path (zero overhead). CloseSession stays fire-and-forget.

Closes PECOBLR-3645 and the connect half of PECOBLR-3646.

2. ABI-version handshake + mTLS + CloudFetch toggle

Wires four new kernel C-ABI symbols onto the driver's experimental-config surface:

  • ABI-version handshake — a sync.Once check at the top of OpenSession compares the linked library's kernel_abi_version() against the header's DATABRICKS_KERNEL_ABI_VERSION and refuses to connect on a mismatch, so a differently-built prebuilt archive can't be silently misread.
  • WithKernelClientCertificate(cert, key) — mTLS client identity forwarded via the paired kernel_session_config_set_tls_client_certificate. Both PEM halves are required together; validateKernelConfig rejects an incomplete/unpaired credential loudly (via an explicit "configured" marker robust against nil-vs-empty) rather than failing open with no client identity. The private key is never logged.
  • WithKernelCloudFetch(enabled) — a tri-state toggle (nil keeps the kernel default on; set forwards kernel_session_config_set_cloudfetch_enabled), distinct from the plain-bool WithCloudFetch whose unset state can't be told from false.

A reflective exhaustiveness guard ensures a future KernelExperimental field can't ship unforwarded/unrejected.

3. Forward kernel (Rust) logs into the driver logger

The kernel's internal Rust tracing events are routed into the same driver logger sink as the Go binding lines (including a custom logger.SetLogOutput) via a reverse-call callback (kernel_set_log_callback) — instead of going to stderr through kernel_init_logging. The one-knob log level (DATABRICKS_LOG_LEVEL / DBSQL_KERNEL_DEBUG + RUST_LOG) is mirrored into the kernel subscriber. The Go trampoline has a panic firewall (recover) and is nil-/wrong-type-safe; the forwarding sink snapshots the logger at first connect so forwarded events aren't re-gated by a later driver-level change.

Build

KERNEL_REV is pinned to the kernel revision that exports the cancellable, ABI-version, mTLS, CloudFetch, and log-callback symbols this driver code links against.

Tests

  • Unit (tagged, in the kernel-backend CI job):
    • ctxWatcher — fires the token on cancel and on deadline, is nil-safe on an uncancellable ctx, and tears down cleanly without firing (TestCtxWatcher*).
    • mTLS/CloudFetch — option wiring, defensive PEM copy, incomplete-pair rejection, DeepCopy, and the Thrift-path rejection (kernel_config_test.go, kernel_experimental_test.go).
    • Log forwarding — level mapping, nil no-op, panic firewall, nil-/wrong-type-safe trampoline, and a regression test proving forwarded kernel events are not re-gated by the live driver level (TestLogSinkForward*, TestLogCallback*).
  • Unit (untagged, CGO_ENABLED=0): the ABI-version compare (match + mismatch, TestCompareABI / TestABIVersionMatches), config classification/exhaustiveness guard, and pure config-assembly all run in the default build with no kernel lib linked.
  • Staging e2e (kernel_e2e_test.go): connect under an already-cancelled ctx fails fast; a long streaming query past its deadline is cancelled promptly with context.DeadlineExceeded; query cancel mid-execution.

How we know it works: the tagged suite exercises the real cgo ctx→token bridge and the real callback round-trip (not mocks), the untagged suite guarantees the default build and its config contract are unaffected, and the staging e2e confirms the cancellation and connection-security behavior end-to-end against a live warehouse.

Co-authored-by: Isaac

Comment thread internal/backend/kernel/backend.go Outdated
Comment thread kernel_e2e_test.go
Base automatically changed from mani/sea-kernel-consolidated to main July 17, 2026 09:36
Wire the kernel backend's two blocking cgo calls to the new cancellable
C-ABI entry points via a ctxWatcher that bridges a Go context onto a kernel
cancel token:

  - OpenSession -> kernel_session_open_cancellable
  - rows.go nextBatch -> kernel_result_stream_next_batch_cancellable

Previously each blocked in an uninterruptible cgo call: a slow warehouse
cold-start or a hung CloudFetch chunk ignored the caller's ctx deadline. The
watcher fires the token on ctx.Done(), which drops the in-flight kernel request
future (a real abort), and the call sites prefer the ctx error on cancellation
while preserving the kernel error as cause (matching the execute path and the
database/sql convention). A session-fatal error is evicted before the
ctx-cancelled return so a failure racing a cancel still evicts the conn. A
non-cancellable ctx (nil Done) yields a nil watcher -> NULL token -> the plain,
unchanged path, so there is zero watcher overhead on the common case.

Requires the kernel cancel-token symbols; bump KERNEL_REV to the merged kernel
revision before this builds in CI (it links a locally-staged archive today).

Tests: tagged ctxWatcher unit tests (fires on cancel/deadline, nil-safe on an
uncancellable ctx, clean teardown without fire) exercising the real cgo
ctx->token bridge in the build-and-test-kernel CI job. Default CGO_ENABLED=0
build unchanged.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
Bumps the kernel pin from the placeholder statement-surface rev to the tip of
the tier2-features branch (databricks-sql-kernel#167 @ 02c0a43), the head of the
unmerged kernel PR stack (#165 -> #166 cancel-token -> #167 tier2). It's a linear
superset carrying every new C-ABI symbol these driver branches link against:
kernel_session_open_cancellable / kernel_result_stream_next_batch_cancellable
(cancel token, #166) plus kernel_abi_version, kernel_session_close_blocking,
set_tls_client_certificate, set_cloudfetch_enabled (#167). The kernel-lib build
fetches the bare commit via its PR-head-ref fallback, so an unmerged SHA is
buildable. Temporary: re-pin to the squash-merge SHA once the kernel stack lands.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch
mani-mathur-arch force-pushed the mani/sea-kernel-ctx-cancel branch from 42af196 to 26cf09b Compare July 17, 2026 10:07
mani-mathur-arch added a commit that referenced this pull request Jul 17, 2026
…etch cancel

Address PR #409 review findings:
- OpenSession's cancelled-connect path now wraps BOTH the ctx error and the
  underlying kernel error (two %w), so errors.Is still matches the ctx error
  while the *KernelError stays reachable via errors.As. Mirrors the execute
  (operation.go) and next_batch (rows.go) cancelled paths, which already did
  this; the connect path was dropping the kernel diagnostics.
- Add TestKernelE2ECancelDuringFetch, which deterministically reaches
  rows.Next() after QueryContext succeeds and then observes cancellation during
  fetch — closing the coverage gap where the read-path cancel could pass without
  exercising kernel_result_stream_next_batch_cancellable.
…etch cancel

Address PR #409 review findings:
- OpenSession's cancelled-connect path now wraps BOTH the ctx error and the
  underlying kernel error (two %w), so errors.Is still matches the ctx error
  while the *KernelError stays reachable via errors.As. Mirrors the execute
  (operation.go) and next_batch (rows.go) cancelled paths, which already did
  this; the connect path was dropping the kernel diagnostics.
- Add TestKernelE2ECancelDuringFetch, which deterministically reaches
  rows.Next() after QueryContext succeeds and then observes cancellation during
  fetch — closing the coverage gap where the read-path cancel could pass without
  exercising kernel_result_stream_next_batch_cancellable.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch
mani-mathur-arch force-pushed the mani/sea-kernel-ctx-cancel branch from 662aedc to 7c094f8 Compare July 17, 2026 11:12
The previous TestKernelE2ECancelDuringFetch cancelled between batches, so
nextBatch's pre-fetch ctx.Err() guard short-circuited before ever calling
kernel_result_stream_next_batch_cancellable — it only proved the read path
honored ctx, not the in-flight token abort.

Rework it to cancel while a fetch is actually blocked: a watcher goroutine fires
the cancel only once a single rows.Next() has been blocked longer than any
buffered-row return could take (i.e. it is inside the network fetch). The run is
verified to reach the cancellable call by the "next_batch cancelled" wrapper the
C-call error path emits (the pre-fetch guard returns a bare context error);
attempts are retried a bounded number of times to absorb the timing window.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch
mani-mathur-arch marked this pull request as ready for review July 18, 2026 11:32
…ions

Wire the four new kernel C-ABI symbols on the driver side, extending the
existing experimental-config surface:

  - checkABIVersion(): a sync.Once handshake at the top of OpenSession compares
    the linked library's kernel_abi_version() against the header's
    DATABRICKS_KERNEL_ABI_VERSION and refuses to connect on a mismatch, so a
    differently-built prebuilt .a can't be silently misread.
  - WithKernelClientCertificate(cert, key): mTLS client identity forwarded via
    the paired kernel_session_config_set_tls_client_certificate. Both PEM halves
    are required together — validateKernelConfig rejects an unpaired credential
    loudly so a lone key can't be silently dropped — and the key is never logged.
  - WithKernelCloudFetch(enabled): a tri-state *bool (nil keeps the kernel
    default on; set forwards kernel_session_config_set_cloudfetch_enabled).
    Distinct from the plain-bool WithCloudFetch, whose unset state can't be told
    from false.

All three are kernel-only and rejected loudly on the Thrift path (the connector
fails when KernelExperimental is non-nil). The reflective classification guard is
extended so a new experimental field can't ship unforwarded/unrejected.

CloseSession stays fire-and-forget: the C ABI now also offers
kernel_session_close_blocking, but adopting it would make close a blocking
network round-trip with no deadline honored, so swapping to it is grouped with
the cancellable-close follow-up.

Requires the kernel ABI-version / mTLS / CloudFetch symbols; bump KERNEL_REV to
the merged kernel revision before this builds in CI (it links a locally-staged
archive today).

Tests: experimental-field classification guard + option wiring + Thrift
rejection + DeepCopy (untagged); TestSetKernelTLS mTLS case, TestABIVersionMatches,
and mTLS-pairing validation (tagged / config). Default CGO_ENABLED=0 build
unchanged.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
WithKernelClientCertificate marks the kernel experimental config as set,
but an empty cert+key pair (e.g. from a failed PEM load) was indistinguishable
from the option never being called: the old XOR validation accepted it and
applyKernelTLS skipped the setter, so a caller who explicitly requested mTLS
connected with no client identity.

Add an explicit TLSClientCertConfigured marker (robust against nil-vs-empty),
set it whenever the option is invoked, and tighten validateKernelConfig to
reject any incomplete mTLS request (missing cert, missing key, or both empty).
Covered by new option/validation/DeepCopy tests and the exhaustiveness guard.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
The ABI-version handshake was only exercised on the happy path
(TestABIVersionMatches, cgo build). The mismatch branch — the runtime hazard
the check exists for, a driver header linked against a differently-built
prebuilt .a — had no coverage because it lived behind the cgo symbols and the
one-shot abiCheckOnce cache.

Extract the pure got-vs-want verdict into compareABI (untagged) and have
checkABIVersion delegate to it, then add TestCompareABI asserting the mismatch
returns a non-nil error naming both versions and the remediation. Being
untagged, the negative test runs under the default CGO_ENABLED=0 build with no
kernel lib linked. Production behavior is unchanged.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
Register a cgo-exported trampoline (kernelLogTrampoline) as the kernel's log sink
(kernel_set_log_callback) so kernel-internal tracing events flow into the driver's
logger — the unified Go+kernel stream, honouring logger.SetLogOutput — instead of
only reaching stderr via kernel_init_logging. This completes the one-knob logging
story: #399 unified the LEVEL across the Go binding lines and the kernel's Rust
lines (PECOBLR-3650) against the stderr sink; this replaces that sink with the
callback so the Rust lines land in the same writer as everything else.

The reverse-call machinery: a runtime/cgo.Handle round-trips the *logSink through
the C void* ctx (cgo pointer rules), and a defer recover() firewall converts any
panic into a dropped line (a panic across the cgo boundary would abort the
process) — the same shape a kernel->host token-provider callback would need. The
driver maps its own log level (resolveKernelLogArg, unchanged) and passes it as
the callback's level argument, so the kernel's Rust lines follow
DATABRICKS_LOG_LEVEL, not just RUST_LOG; DBSQL_KERNEL_DEBUG still defers to
RUST_LOG (NULL level).

The forwarding sink logs through a SNAPSHOT of the driver logger taken at install
and pinned to TraceLevel, for two reasons: (1) no double gating — the kernel
already filtered events against the level we passed it, so re-gating through the
live logger.Logger (at the driver level) would re-drop exactly the events the
DBSQL_KERNEL_DEBUG override was meant to surface; (2) no data race — the kernel
drain thread reads only its immutable snapshot rather than the mutable global
logger a concurrent SetLogLevel/SetLogOutput would rewrite. A non-Success install
is surfaced at Warn (visible at the default level), not the Debug-gated klog.

The two C-ABI logging routes share the one process-global subscriber and are
mutually exclusive; we install the callback, and a non-Success install (host
already installed a subscriber) is logged, never fatal to connect.

Level mapping and sink routing are pure Go (logforward.go, untagged) so they test
under CGO_ENABLED=0 — including the per-level mapping assertions and the
not-re-gated-by-driver-level property. The reverse-call round-trip, the recover
firewall (a panicking sink must not crash the process), the wrong-type/nil-ctx
guards, and delivery after a panic are exercised by tagged tests via a C invoke
seam.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
Remove ticket codes, review shorthand, and ops jargon so the PR reads cleanly for OSS reviewers.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
Rely on resolveKernelLogArg returning an empty level for the RUST_LOG override instead of normalizing it twice.

Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
The consolidated branch now carries the log-callback forwarding
(log_callback.go references kernel_set_log_callback / kernel_log_callback),
so KERNEL_REV must point at a kernel revision that exports those symbols.
Bump from the tier2 rev 02c0a434 (pre-log-callback) to 946c263, the head of
kernel PR #169 (mani/c-abi-log-callback), which defines them. The kernel-lib
build resolves this unmerged SHA via its PR-head-ref fallback, so the tagged
kernel-backend build links a header carrying the symbols.

Temporary: re-pin to the squash-merge SHA once kernel #169 lands.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch mani-mathur-arch changed the title feat(kernel): honor ctx deadlines on connect and mid-fetch feat(kernel): ctx-deadline cancellation, ABI/mTLS/CloudFetch controls, and log forwarding Jul 18, 2026
Comment thread kernel_config.go
Comment thread kernel_e2e_test.go
Comment thread kernel_config.go
Comment thread internal/backend/kernel/logforward.go
Comment thread internal/backend/kernel/rows.go Outdated
Comment thread internal/backend/kernel/backend.go
@mani-mathur-arch

Copy link
Copy Markdown
Collaborator Author

Code Review Squad — Review

Score: 69/100 — HIGH RISK

The C-ABI/FFI mechanics are sound — security and language reviewers independently verified cert handling, memory ownership, cancel-token lifecycle, and the log trampoline against the kernel header at the pinned rev, both clean. Risk is concentrated in two test-coverage gaps (a dropped forwarding line or a broken connect-cancel path would ship green) and a UX/contract inconsistency where the standard WithCloudFetch(false) is silently ignored on the kernel path (flagged by 3 reviewers). No confirmed production correctness bug; the HIGH RISK band is driven by the test gaps, not by shipped defects.


Medium Findings

M2doc.go explicitly denies the headline mid-fetch-cancellation feature this PR ships (doc.go:275-277, unchanged by this diff)

This PR adds mid-fetch cancellation: rows.go nextBatch now calls kernel_result_stream_next_batch_cancellable with a ctx-driven cancel token (proven by the new TestKernelE2EQueryCancelDuringExecution). But the read-path cancellation paragraph in doc.go still asserts the opposite: "On the read path, context cancellation is honored at result-batch boundaries, not mid-fetch: an in-flight CloudFetch batch runs to completion before the cancel takes effect."

go doc github.com/databricks/databricks-sql-go on the merged commit would print a mid-fetch-cancel disclaimer directly contradicted by code in the same commit. Users read the stale doc, assume a hung CloudFetch GET is uninterruptible, and design around a limitation that no longer exists.

This finding has no in-diff line to anchor an inline comment (the paragraph sits just past the last doc.go hunk, which ends at line 266).

  • Suggested fix: Replace the paragraph with the now-true behavior (mid-fetch cancel via the kernel cancel token; a NULL token on a non-cancellable ctx preserves the plain path).

Test coverage:
- Assert buildKernelConfig forwards TLSClientCertPEM / TLSClientKeyPEM /
  CloudFetchEnabled (previously only trusted-certs + hostname-skip were
  asserted, so a dropped forwarding line could ship green), plus an
  unset-CloudFetchEnabled-stays-nil case.
- Add a tagged, hermetic mid-connect cancel test: a black-hole TLS listener
  makes OpenSession block, and a short ctx deadline must fire the cancel token
  mid-connect and surface as "session_open cancelled" matching
  DeadlineExceeded. The prior connect-cancel e2e only hit the pre-connect
  guard; its docstring is corrected to say so.

Contract:
- Reject a bare WithCloudFetch(false) on the kernel path (steering to
  WithKernelCloudFetch(false)) instead of silently dropping it and leaving
  CloudFetch on; classify UseCloudFetch as rejected. When WithKernelCloudFetch
  is set it stays authoritative.
- doc.go: mid-fetch cancellation is now supported (not a limitation); refresh
  the "nothing silently ignored" list.

Operational:
- Forward kernel ERROR/WARN one severity lower by default (->Warn/Info, tagged
  with the native level) so kernel-internal retries don't inflate the driver's
  alert-keyed WARN/ERROR rate; DBSQL_KERNEL_DEBUG preserves native severity.

Cleanup:
- Extract cancelledErr() for the shared dual-%w cancelled-path wrap used by
  OpenSession, execute, and nextBatch.
- Create one ctxWatcher per result set (in newKernelRows, stopped in Close)
  instead of one per Arrow batch, matching the execute path's amortization.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch
mani-mathur-arch force-pushed the mani/sea-kernel-ctx-cancel branch from c111977 to 4137103 Compare July 18, 2026 18:13
…erage

Add a tagged, warehouse-free test that exercises the experimental kernel TLS
options end-to-end through the real cgo -> kernel -> rustls stack against a local
httptest TLS server:

- WithKernelClientCertificate: with a client cert the mTLS handshake completes
  (handler reached, peer cert observed); against a RequireAndVerifyClientCert
  server with no cert, the handshake is rejected before the handler runs.
- WithKernelTrustedCerts: a privately-signed server cert validates only when its
  CA is trusted; an untrusted CA fails the chain.
- WithKernelSkipHostnameVerify: a wrong-SAN server cert is rejected on hostname
  grounds unless the skip is set.

Certs are minted at runtime with crypto/x509 (PKCS#8 keys, the form the kernel's
tls-rustls Identity::from_pem accepts) — no committed fixtures, no external tools.
The kernel speaks SEA so the query itself fails at the app layer; the property
under test is that the handshake completes with the forwarded material, observed
via a per-connection reached/saw-cert probe. Each case cancels the connect the
instant the outcome is decided rather than riding the kernel's connect-retry
budget, so the file runs in a few seconds. Runs in the tagged build-and-test-kernel
job (no warehouse needed) — closing the mTLS/custom-CA gap that had only
byte-marshalling unit coverage before.

Co-authored-by: Isaac
Signed-off-by: Mani Kaustubh Mathur <mani.mathur@databricks.com>
@mani-mathur-arch

mani-mathur-arch commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator Author

Re M2 (from the review summary — doc.go read-path paragraph denied the mid-fetch cancellation this PR ships): fixed in 4137103. The paragraph now states that context cancellation is honored both at result-batch boundaries and mid-fetch (a cancel/deadline firing while a CloudFetch batch is in flight aborts that fetch), with the non-cancellable-context zero-overhead note. The stale "an in-flight CloudFetch batch runs to completion before the cancel takes effect" sentence is gone. (No inline thread to resolve — the paragraph sat past the last doc.go hunk.)

All 6 inline findings (#1#6) are fixed and their threads resolved; details in each thread. Head is now 3c0a7c2, which also adds a hermetic mTLS/custom-CA/hostname-skip handshake test. CI is green.

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.

1 participant