@restatedev/restate-sdk-tunnel 1.15.0-rc.6 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,21 @@ environment variables when not given (option > environment > throw):
62
62
  | `signingPublicKey` | `RESTATE_INPROC_SIGNING_PUBLIC_KEY` |
63
63
  | `authToken` | the file named by `RESTATE_INPROC_AUTH_TOKEN_FILE` (see below) |
64
64
 
65
+ For operator log attribution, set `tunnelWorkerId` or
66
+ `RESTATE_TUNNEL_WORKER_ID` to a stable worker/pod identifier. When omitted,
67
+ the SDK derives a process-stable, hostname-based id. Each h2 tunnel connection
68
+ also gets a generated `tunnel-connection-id`; both ids are sent during the
69
+ tunnel handshake for diagnostics only.
70
+
71
+ If your process has asynchronous startup work before it can safely execute
72
+ handlers, pass `startupReady` or call `connectTunnel` only after that work has
73
+ completed. Without `startupReady`, the tunnel dials immediately. With it, the
74
+ tunnel supervisor waits for the promise or callback before it dials any tunnel
75
+ server, so the tunnel-server cannot select the pod before the in-process
76
+ handler is ready. A stuck startup gate fails fatally after
77
+ `startupReadyTimeoutMs` (default 120s), making a broken worker visible instead
78
+ of silently absent from the fleet.
79
+
65
80
  The [restate-operator](https://github.com/restatedev/restate-operator)
66
81
  injects the first four into the pods of a `tunnelMode: in-process`
67
82
  RestateDeployment — with a per-revision `tunnelName` — and registers the
@@ -80,6 +95,44 @@ connection failure, not a crash). Mount the Secret as a whole volume rather
80
95
  than via `subPath` — Kubernetes does not update `subPath` mounts in place, so
81
96
  a rotated token would never reach the file.
82
97
 
98
+ ### Kubernetes shutdown and eviction
99
+
100
+ Client-side graceful shutdown is on by default. On `SIGTERM`,
101
+ `connectTunnel` calls `shutdown()`: each established tunnel session sends an
102
+ HTTP/2 GOAWAY immediately so Restate Cloud stops opening new streams on that
103
+ connection, any raced streams are refused with
104
+ `x-restate-tunnel-draining: true`, in-flight invocations are allowed to finish
105
+ for `drainGraceMs` (default 120s), then the session is closed gracefully. If
106
+ the grace expires first, the session/socket is force-closed. Once the
107
+ registered tunnels in the process finish draining, the default signal handler
108
+ calls `process.exit(0)`.
109
+
110
+ If a process creates multiple tunnels with default signal handling, one shared
111
+ process-level signal handler drains all tunnels registered for that signal
112
+ before exiting. Embedded applications that need to coordinate other shutdown
113
+ work should pass `gracefulShutdown: false`, handle signals themselves, call
114
+ `shutdown()` on every tunnel, and exit only after their own cleanup is complete.
115
+
116
+ Set the pod's `terminationGracePeriodSeconds` to at least
117
+ `ceil(drainGraceMs / 1000)` plus the longest handler drain slack you are
118
+ prepared to allow. Kubernetes sends `SIGTERM` first and sends `SIGKILL` when
119
+ the pod grace period expires; a too-short pod grace period will cut off the
120
+ SDK's drain.
121
+
122
+ On managed Kubernetes platforms, also account for the effective grace window
123
+ the platform grants for the specific eviction event. The SDK can only drain for
124
+ the smaller of `drainGraceMs`, the pod's `terminationGracePeriodSeconds`, and
125
+ the grace the platform actually grants. Some providers document event-specific
126
+ limits; for example,
127
+ [GKE Autopilot documents bounded grace for GKE-initiated evictions](https://cloud.google.com/kubernetes-engine/docs/how-to/extended-duration-pods#about_gke-initiated_pod_eviction).
128
+ Validate this path in your deployment environment by confirming `SIGTERM`
129
+ reaches Node and the process remains alive long enough to complete the drain.
130
+
131
+ Run the Node process as the container's main process with an exec-form
132
+ `ENTRYPOINT`/`CMD`, or use a small init wrapper such as `tini`/`dumb-init` if
133
+ your image starts through a shell or spawns child processes. The SDK installs
134
+ the `SIGTERM` handler, but wrappers still need to forward signals to Node.
135
+
83
136
  ## How it works
84
137
 
85
138
  `connectTunnel` is the in-process analog of Restate Cloud's standalone
@@ -95,12 +148,15 @@ a rotated token would never reach the file.
95
148
  2. **Role-flip:** Restate Cloud drives HTTP/2 as the _client_ over the
96
149
  connection we dialed; the deployment becomes the HTTP/2 _server_ on it.
97
150
  3. **Handshake:** the cloud opens `GET /_/start-tunnel`; we answer with the
98
- environment credentials and receive the tunnel confirmation (including
99
- the public `proxy-url`) as HTTP/2 trailers.
151
+ environment credentials plus advisory diagnostic ids
152
+ (`tunnel-worker-id` and `tunnel-connection-id`) and receive the tunnel
153
+ confirmation (including the public `proxy-url`) as HTTP/2 trailers.
100
154
  4. **Serve:** each invocation arrives as one HTTP/2 stream. The tunnel's
101
155
  `/<scheme>/<host>/<port>` destination prefix is stripped and the request
102
156
  is handed to the SDK's own endpoint handler (full-duplex streaming —
103
- `BIDI_STREAM`), exactly as if it had arrived on a local listener.
157
+ `BIDI_STREAM`), exactly as if it had arrived on a local listener. For
158
+ `connectTunnel`, the `/http/in-process/9080/` deployment URL segment is
159
+ vestigial: this package does not dial a local `:9080` socket.
104
160
  5. **Verify:** every forwarded request carries Restate's request-identity
105
161
  JWT (`x-restate-jwt-v1`). Verification is delegated to the SDK against
106
162
  `signingPublicKey`, so only requests signed by _your_ environment are
@@ -114,6 +170,12 @@ a rotated token would never reach the file.
114
170
  immediately dials a replacement while the old connection keeps serving
115
171
  its in-flight invocations (up to `drainGraceMs`, default 120s) — zero
116
172
  dropped requests across cloud rollovers.
173
+ 8. **Shut down gracefully:** the engine advertises `supports-client-drain`.
174
+ On `shutdown()` or the default `SIGTERM` handler, each live connection
175
+ sends HTTP/2 GOAWAY proactively, refuses any raced streams with the drain
176
+ sentinel, drains in-flight invocations up to `drainGraceMs`, and then
177
+ closes the h2 session gracefully. The final forced close is only used after
178
+ the grace window expires.
117
179
 
118
180
  ## API
119
181
 
@@ -122,6 +184,7 @@ function connectTunnel(options: ConnectTunnelOptions): TunnelConnection;
122
184
 
123
185
  interface TunnelConnection {
124
186
  close(): Promise<void>; // stop reconnecting + close
187
+ shutdown(opts?: { graceMs?: number }): Promise<void>; // graceful drain + close
125
188
  readonly ready: Promise<void>; // first successful handshake (rejects on fatal)
126
189
  readonly connectionCount: number;
127
190
  readonly tunnelName: string | undefined; // learned from the handshake
@@ -142,11 +205,15 @@ Key options (see `ConnectTunnelOptions` for the full surface and defaults):
142
205
  | `authToken` | Cloud API key (`key_...`, Full role) presented in the handshake |
143
206
  | `signingPublicKey` | `publickeyv1_...` — request-identity verification (required) |
144
207
  | `tunnelName` | The deployment's identity — unique per deployment, shared by its replicas |
208
+ | `tunnelWorkerId` | Stable SDK worker/process diagnostic id; defaults to `RESTATE_TUNNEL_WORKER_ID` or hostname-based |
209
+ | `startupReady` | One-shot startup readiness gate; no tunnel connections are dialed until it completes |
210
+ | `startupReadyTimeoutMs` | Fatal deadline for `startupReady` (120s; only used when `startupReady` is set) |
145
211
  | `services` | Same shape `restate.serve` accepts |
146
212
  | `tls` | Default on (system trust, ALPN `h2`); object form for CA/mTLS |
147
213
  | `connectTimeoutMs` | TCP+TLS dial deadline (5s, mirrors the standalone client) |
148
214
  | `reconnectInitialMs/MaxMs/Factor` | Jittered exponential backoff (10ms → 120s, reset after a stable connection) |
149
215
  | `supportsDrain` / `drainGraceMs` | Graceful-drain handover on cloud rollovers (on, 120s grace) |
216
+ | `supportsClientDrain` / `gracefulShutdown` | Client shutdown drain with h2 GOAWAY and default `SIGTERM` handling |
150
217
  | `pingIntervalMs/TimeoutMs/MaxMissed` | Liveness watchdog (75s cadence) |
151
218
  | `maxConcurrentStreams` etc. | HTTP/2 tuning for high-concurrency serving |
152
219