@cloudflare/sandbox 0.13.0-next.788.1 → 0.13.0-next.802.1

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.
@@ -1,7 +1,7 @@
1
- import { i as ErrorCode, n as getSuggestion, r as getHttpStatus } from "./errors-CXR0xBpw.js";
2
- import { $ as TerminalControlError, G as BackupRestoreError, H as BackupCreateError, Q as StaleTerminalHandleError, U as BackupExpiredError, W as BackupNotFoundError, X as OperationInterruptedError, Z as RPCTransportError, _ as StaleProcessHandleError, n as RuntimeIdentityInactiveError, p as ProcessNotFoundError, q as InvalidBackupConfigError, r as RuntimeControlProtocolError, t as createErrorFromResponse, tt as SandboxError, v as CustomDomainRequiredError } from "./errors-QYlSkVGz.js";
1
+ import { i as ErrorCode, n as getSuggestion, r as getHttpStatus } from "./errors-Bb3s6nBS.js";
2
+ import { $ as TerminalControlError, G as BackupRestoreError, H as BackupCreateError, Q as StaleTerminalHandleError, U as BackupExpiredError, W as BackupNotFoundError, X as OperationInterruptedError, Z as RPCTransportError, _ as StaleProcessHandleError, n as RuntimeIdentityInactiveError, p as ProcessNotFoundError, q as InvalidBackupConfigError, r as RuntimeControlProtocolError, t as createErrorFromResponse, tt as SandboxError, v as CustomDomainRequiredError } from "./errors--yR3zLC_.js";
3
3
  import { a as TraceContext, d as partitionEnvVars, i as createNoOpLogger, o as logCanonicalEvent, r as createLogger, t as parseSSEFrames, u as getEnvString } from "./dist-n_eniPAR.js";
4
- import { a as PullSubscriptionTarget, i as createSandboxProcess, o as openRemoteSubscription, r as sandboxRuntimeCall, s as translateRPCError, t as SandboxExtension } from "./extensions-Be5HnDGt.js";
4
+ import { a as PullSubscriptionTarget, i as createSandboxProcess, o as openRemoteSubscription, r as sandboxRuntimeCall, s as translateRPCError, t as SandboxExtension } from "./extensions-BKL2zqL4.js";
5
5
  import { Container, ContainerProxy, getContainer, switchPort } from "@cloudflare/containers";
6
6
  import { RpcTarget } from "cloudflare:workers";
7
7
  import { AwsClient } from "aws4fetch";
@@ -161,6 +161,19 @@ function isDurableObjectCodeUpdateReset(error) {
161
161
  return false;
162
162
  }
163
163
  /**
164
+ * Whether an error was raised because the Durable Object itself was reset, by
165
+ * a code update or a storage startup failure, rather than by the operation
166
+ * that observed it.
167
+ */
168
+ function isDurableObjectReset(error) {
169
+ for (const candidate of selfAndCauses(error)) {
170
+ const message = errorMessageOf(candidate);
171
+ if (SUPERSEDED_ISOLATE_PATTERN.test(message)) return true;
172
+ if (DO_STORAGE_STARTUP_RESET_PATTERN.test(message)) return true;
173
+ }
174
+ return false;
175
+ }
176
+ /**
164
177
  * Whether an error represents a transient platform lifecycle/storage failure,
165
178
  * not an application-level sandbox failure.
166
179
  */
@@ -2516,11 +2529,6 @@ async function mountR2EgressBucket(context, bucket, mountPath, options) {
2516
2529
  const lifecycle = await context.lifecycle.capture(context.runtime);
2517
2530
  passwordFilePath = generatePasswordFilePath();
2518
2531
  additionalHeaderFilePath = generateS3FSAdditionalHeaderFilePath();
2519
- await createPasswordFile(context.s3fsHost, passwordFilePath, bucket, {
2520
- accessKeyId: "x",
2521
- secretAccessKey: "x"
2522
- });
2523
- await createDisableExpectHeaderFile(context.s3fsHost, additionalHeaderFilePath);
2524
2532
  mountInfo = {
2525
2533
  mountId: crypto.randomUUID(),
2526
2534
  mountType: "r2-egress",
@@ -2532,14 +2540,21 @@ async function mountR2EgressBucket(context, bucket, mountPath, options) {
2532
2540
  prefix,
2533
2541
  readOnly: options.readOnly ?? false
2534
2542
  };
2535
- await configureR2EgressOutbound(context.getOutboundHost(), { buckets: {
2536
- ...buildR2EgressParams(context.registry).buckets,
2537
- [bucket]: {
2538
- prefix,
2539
- readOnly: options.readOnly ?? false
2540
- }
2541
- } });
2542
- await context.runRuntimeCall("mount.ensureDirectory", (control) => control.mounts.ensureDirectory(mountPath));
2543
+ await allOrFirstError([
2544
+ createPasswordFile(context.s3fsHost, passwordFilePath, bucket, {
2545
+ accessKeyId: "x",
2546
+ secretAccessKey: "x"
2547
+ }),
2548
+ createDisableExpectHeaderFile(context.s3fsHost, additionalHeaderFilePath),
2549
+ configureR2EgressOutbound(context.getOutboundHost(), { buckets: {
2550
+ ...buildR2EgressParams(context.registry).buckets,
2551
+ [bucket]: {
2552
+ prefix,
2553
+ readOnly: options.readOnly ?? false
2554
+ }
2555
+ } }),
2556
+ context.runRuntimeCall("mount.ensureDirectory", (control) => control.mounts.ensureDirectory(mountPath))
2557
+ ]);
2543
2558
  s3fsStarted = true;
2544
2559
  await executeS3FSMount(context.s3fsHost, {
2545
2560
  bucket,
@@ -2603,6 +2618,14 @@ async function mountR2EgressBucket(context, bucket, mountPath, options) {
2603
2618
  });
2604
2619
  }
2605
2620
  }
2621
+ /**
2622
+ * Like `Promise.all`, but waits for every step to settle before rejecting, so
2623
+ * cleanup never runs while a support-file write or route update is in flight.
2624
+ */
2625
+ async function allOrFirstError(steps) {
2626
+ const failure = (await Promise.allSettled(steps)).find((result) => result.status === "rejected");
2627
+ if (failure) throw failure.reason;
2628
+ }
2606
2629
 
2607
2630
  //#endregion
2608
2631
  //#region src/storage-mount/validation/credentials.ts
@@ -4601,8 +4624,13 @@ async function forwardPreviewRequest(tcpPort, request, lease) {
4601
4624
  };
4602
4625
  } catch (error) {
4603
4626
  release();
4627
+ if (interruptedError) throw interruptedError;
4628
+ if (isDurableObjectReset(error)) throw error;
4604
4629
  if (error instanceof Error && error.message.includes("Network connection lost.")) return { status: "network-lost" };
4605
- throw error;
4630
+ return {
4631
+ status: "failed",
4632
+ error
4633
+ };
4606
4634
  }
4607
4635
  }
4608
4636
  function retainPreviewBody(body, release, bindInterrupt) {
@@ -4754,10 +4782,6 @@ function buildPreviewProxyRequest(request, { port, sandboxId, sandboxName }) {
4754
4782
  headers.set("X-Forwarded-Host", url.hostname);
4755
4783
  headers.set("X-Forwarded-Proto", url.protocol.replace(":", ""));
4756
4784
  headers.set("X-Sandbox-Name", sandboxName ?? sandboxId);
4757
- if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") return new Request(request, {
4758
- headers,
4759
- redirect: "manual"
4760
- });
4761
4785
  return new Request(proxyURL, {
4762
4786
  method: request.method,
4763
4787
  headers,
@@ -5091,6 +5115,13 @@ var PreviewService = class {
5091
5115
  if (!container?.running) return this.stalePreviewURLResponse();
5092
5116
  const result = await forwardPreviewRequest(container.getTcpPort(port), request, lease);
5093
5117
  if (result.status === "network-lost") return this.stalePreviewURLResponse();
5118
+ if (result.status === "failed") {
5119
+ this.deps.logger.error("Preview request to container port failed", result.error instanceof Error ? result.error : new Error(String(result.error)), {
5120
+ port,
5121
+ method: request.method
5122
+ });
5123
+ return new Response(`Could not reach port ${port} in the container`, { status: 502 });
5124
+ }
5094
5125
  return result.response;
5095
5126
  }
5096
5127
  async validatePreviewURLForRuntime(port, token) {
@@ -7589,6 +7620,22 @@ function randomId(size = 20) {
7589
7620
  function createQuickTunnelId() {
7590
7621
  return `quick-${randomId()}`;
7591
7622
  }
7623
+ /**
7624
+ * The run holding `request.port` when the container rejected `request`
7625
+ * with a port conflict, or `null` when the conflict does not name both
7626
+ * the active run and its tunnel, which `stopTunnelRun` needs.
7627
+ */
7628
+ function conflictingRunOnPort(error, request) {
7629
+ const e = error;
7630
+ if (e?.code !== "TUNNEL_RUN_CONFLICT") return null;
7631
+ const runId = e.context?.activeRunId;
7632
+ const tunnelId = e.context?.activeTunnelId;
7633
+ if (e.context?.port !== request.port || typeof runId !== "string" || typeof tunnelId !== "string" || runId === request.runId) return null;
7634
+ return {
7635
+ tunnelId,
7636
+ runId
7637
+ };
7638
+ }
7592
7639
  var TunnelProvisioner = class {
7593
7640
  #host;
7594
7641
  #zoneNamePromise = null;
@@ -7596,7 +7643,7 @@ var TunnelProvisioner = class {
7596
7643
  this.#host = host;
7597
7644
  }
7598
7645
  async provisionQuickTunnel(tunnels, port, tunnelRunId, tunnelId = createQuickTunnelId()) {
7599
- const result = await this.#ensureTunnelRun(tunnels, {
7646
+ const result = await this.#ensureQuickTunnelRun(tunnels, {
7600
7647
  mode: "quick",
7601
7648
  tunnelId,
7602
7649
  runId: tunnelRunId,
@@ -7712,6 +7759,26 @@ var TunnelProvisioner = class {
7712
7759
  async #ensureTunnelRun(tunnels, request) {
7713
7760
  return await tunnels.ensureTunnelRun(request);
7714
7761
  }
7762
+ /**
7763
+ * Start a quick tunnel run. The Durable Object owns every tunnel run in
7764
+ * its container and holds the port lock, so a different run already on
7765
+ * the port lost its storage record. Stop it and retry once instead of
7766
+ * failing every later `get()` for the port.
7767
+ */
7768
+ async #ensureQuickTunnelRun(tunnels, request) {
7769
+ try {
7770
+ return await this.#ensureTunnelRun(tunnels, request);
7771
+ } catch (error) {
7772
+ const orphan = conflictingRunOnPort(error, request);
7773
+ if (!orphan) throw error;
7774
+ this.#host.logger?.warn("tunnel.get: stopping orphaned run on port", {
7775
+ port: request.port,
7776
+ ...orphan
7777
+ });
7778
+ await tunnels.stopTunnelRun(orphan);
7779
+ return await this.#ensureTunnelRun(tunnels, request);
7780
+ }
7781
+ }
7715
7782
  async #getZoneName(config) {
7716
7783
  if (!this.#zoneNamePromise) {
7717
7784
  const pending = getZoneName({
@@ -7944,13 +8011,37 @@ var TunnelService = class {
7944
8011
  await this.#assertSandboxLifetime(lifetime, "committing");
7945
8012
  return spawned;
7946
8013
  } catch (error) {
7947
- if (committedInfo) await this.#invalidateCommittedPortIfUnchanged(port, committedInfo, lease.runtime, runId);
8014
+ try {
8015
+ if (committedInfo) await this.#invalidateCommittedPortIfUnchanged(port, committedInfo, lease.runtime, runId);
8016
+ } finally {
8017
+ await this.#stopAbandonedQuickRun(lease.tunnels, port, {
8018
+ tunnelId,
8019
+ runId
8020
+ });
8021
+ }
7948
8022
  throw error;
7949
8023
  } finally {
7950
8024
  hold.release();
7951
8025
  }
7952
8026
  });
7953
8027
  }
8028
+ /**
8029
+ * Stop a quick tunnel run that provisioning is giving up on, so its
8030
+ * cloudflared does not keep holding the port without a stored record.
8031
+ * Best effort: the run may never have started, or may have died with a
8032
+ * replaced runtime.
8033
+ */
8034
+ async #stopAbandonedQuickRun(tunnels, port, run) {
8035
+ try {
8036
+ await tunnels.stopTunnelRun(run);
8037
+ } catch (error) {
8038
+ this.#host.logger.warn("tunnel.get: abandoned run cleanup failed", {
8039
+ port,
8040
+ ...run,
8041
+ error: error instanceof Error ? error.message : String(error)
8042
+ });
8043
+ }
8044
+ }
7954
8045
  async #provisionNamedTunnel(port, name) {
7955
8046
  const lifetime = await this.#host.currentLifetime?.getOrCreate();
7956
8047
  return await this.#host.runProvision(async (lease) => {
@@ -10111,4 +10202,4 @@ var Sandbox = class extends Container {
10111
10202
 
10112
10203
  //#endregion
10113
10204
  export { proxyTerminal as _, responseToAsyncIterable as a, collectFile as b, BucketUnmountError as c, S3FSMountError as d, parsePreviewRoute as f, validateTunnelName as g, validatePort as h, parseSSEStream as i, InvalidMountConfigError as l, SandboxSecurityError as m, getSandbox as n, ContainerProxy$1 as o, withPreviewProxyMetadata as p, asyncIterableToSSEStream as r, BucketMountError as s, Sandbox as t, MissingCredentialsError as u, isDurableObjectCodeUpdateReset as v, streamFile as x, isPlatformTransientError as y };
10114
- //# sourceMappingURL=sandbox-pJ4JSnDx.js.map
10205
+ //# sourceMappingURL=sandbox-CaesWCLh.js.map