@vzn/vx-reapi 0.0.594 → 0.0.595

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
@@ -223,11 +223,12 @@ through; a Tree file's setuid and setgid bits are dropped.
223
223
  ## Artifacts stream
224
224
 
225
225
  An artifact up to 256 KiB is read whole and sent in one batch, with no
226
- probe first. Past that the cache layer never holds an artifact whole: `put` digests the file-backed
227
- `Blob` vx hands it in one pass over its stream, asks `FindMissingBlobs`, and
228
- uploads from a second pass: past the batch limit (about 4 MiB) the file is
229
- read `chunkBytes` at a time as the ByteStream write drains, identity-encoded
230
- (the artifact is zstd already). `get` returns the ByteStream read as a
226
+ probe first. Up to the batch limit (about 4 MiB) it is read whole once,
227
+ probed with `FindMissingBlobs`, and those bytes sent. Past that the cache
228
+ layer never holds an artifact whole: `put` digests the file-backed `Blob`
229
+ vx hands it in one pass over its stream, asks `FindMissingBlobs`, and
230
+ uploads from a second pass, read `chunkBytes` at a time as the ByteStream
231
+ write drains, identity-encoded (the artifact is zstd already). `get` returns the ByteStream read as a
231
232
  `Response`, each message taken from the call as vx writes the previous one
232
233
  to disk.
233
234
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-reapi",
3
- "version": "0.0.594",
3
+ "version": "0.0.595",
4
4
  "license": "MIT",
5
5
  "description": "Bazel Remote Execution API plugin for vx — remote cache (ActionCache + CAS) against NativeLink, BuildBuddy, Buildbarn or bazel-remote.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "peerDependencies": {
42
- "@vzn/vx": "^0.0.594"
42
+ "@vzn/vx": "^0.0.595"
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
package/src/cache.ts CHANGED
@@ -159,32 +159,32 @@ export class ReapiRemoteCache {
159
159
  }
160
160
 
161
161
  /**
162
- * A large artifact takes two passes over `body`, never one in memory: the
163
- * digest first (the CAS address must be known before the server is asked),
164
- * then the upload from a second read of the stream.
162
+ * An artifact past `writeBlob`'s whole read takes two passes over `body`,
163
+ * never one in memory: the digest first (the CAS address must be known
164
+ * before the server is asked), then the upload from a second read of the
165
+ * stream.
165
166
  */
166
167
  async put(hash: string, body: Blob, meta: { durationMs: number }): Promise<void> {
167
- let digest: Digest
168
- if (body.size <= SMALL_PUT_BYTES) {
168
+ // Read once, and those bytes hashed and sent, wherever `writeBlob` would
169
+ // read the Blob whole anyway: a second writer of the key renames its
170
+ // artifact over the file, and two reads sent its bytes under this one's
171
+ // digest (F-50).
172
+ const data = body.size <= this.client.wholeWriteBytes ? await body.bytes() : undefined
173
+ const digest = data === undefined ? await streamedDigestOf(body) : digestOf(data)
174
+ if (data !== undefined && data.length <= SMALL_PUT_BYTES) {
169
175
  // A small artifact is sent in one batch without asking first: the
170
176
  // probe was a round trip of its own to save an upload no larger than
171
177
  // it (102 → 71 ms per save at 15 ms one-way, F-26). Content-addressed,
172
178
  // so a re-send is harmless.
173
- // Read once, and those bytes hashed and sent: a second writer of the
174
- // key renames its artifact over the file, and two reads sent its bytes
175
- // under this one's digest (F-50).
176
179
  // A server whose batch limit is smaller refuses it; stream instead.
177
- const data = await body.bytes()
178
- digest = digestOf(data)
179
180
  await this.client
180
181
  .batchUpdateBlobs([{ digest, data }])
181
182
  .catch(() => this.client.writeBlob(digest, data))
182
183
  } else {
183
- digest = await streamedDigestOf(body)
184
184
  // Upload only what the server lacks: for a large artifact the probe is
185
185
  // cheap next to the bytes it can skip.
186
186
  const missing = await this.client.findMissingBlobs([digest])
187
- if (missing.length > 0) await this.client.writeBlob(digest, body)
187
+ if (missing.length > 0) await this.client.writeBlob(digest, data ?? body)
188
188
  }
189
189
  await this.client.updateActionResult(actionDigestFor(hash), {
190
190
  exit_code: 0,
package/src/executor.ts CHANGED
@@ -12,7 +12,14 @@
12
12
  import { mkdir, writeFile, chmod, readlink, realpath, rm, symlink, unlink } from 'node:fs/promises'
13
13
  import { constants, existsSync } from 'node:fs'
14
14
  import path from 'node:path'
15
- import { executorFallback, isLiteralPattern, isUserError, normalizeGlob, UserError } from '@vzn/vx'
15
+ import {
16
+ executorFallback,
17
+ isLiteralPattern,
18
+ isUserError,
19
+ normalizeGlob,
20
+ UserError,
21
+ withForwardArgs,
22
+ } from '@vzn/vx'
16
23
  import type { ExecuteRequest, ExecuteResult, TaskExecutor, TaskPlacement } from '@vzn/vx'
17
24
  import {
18
25
  buildInputTree,
@@ -1252,12 +1259,9 @@ async function this_readStream(
1252
1259
  return ''
1253
1260
  }
1254
1261
 
1255
- /** Forwarded args are appended shell-quoted, exactly as the local executor does. */
1262
+ /** Forwarded args are placed by core's own `withForwardArgs`, as the local executor's are. */
1256
1263
  function fullCommand(req: ExecuteRequest, cdInto: string, projectRel: string): string {
1257
- const quoted =
1258
- req.forwardArgs.length === 0
1259
- ? req.command
1260
- : `${req.command} ${req.forwardArgs.map((a) => `'${a.replaceAll("'", `'\\''`)}'`).join(' ')}`
1264
+ const command = withForwardArgs(req.command, req.forwardArgs)
1261
1265
  // A remote action gets NO PATH from this machine — sending one would put a
1262
1266
  // host path in the action digest and split every laptop from every runner.
1263
1267
  // But a task's command is normally a package binary (`oxlint`, `tsc`), and
@@ -1285,7 +1289,7 @@ function fullCommand(req: ExecuteRequest, cdInto: string, projectRel: string): s
1285
1289
  return (
1286
1290
  `VX_ROOT=${root}; ${cd}` +
1287
1291
  `export PATH="$VX_ROOT/node_modules/.bin:$PWD/node_modules/.bin:$PATH"; ` +
1288
- quoted
1292
+ command
1289
1293
  )
1290
1294
  }
1291
1295
 
package/src/wire.ts CHANGED
@@ -1002,6 +1002,11 @@ export class ReapiClient {
1002
1002
  )
1003
1003
  }
1004
1004
 
1005
+ /** The largest `Blob` `writeBlob` reads whole rather than streams. */
1006
+ get wholeWriteBytes(): number {
1007
+ return this.negotiatedBatchBytes || SAFE_BATCH_BYTES
1008
+ }
1009
+
1005
1010
  /**
1006
1011
  * Upload a blob via ByteStream, chunked at `chunkBytes`, RESUMING an
1007
1012
  * interrupted identity upload from the server's committed offset
@@ -1017,8 +1022,7 @@ export class ReapiClient {
1017
1022
  * zstd artifact already. A Blob under the limit is small enough to read.
1018
1023
  */
1019
1024
  async writeBlob(digest: Digest, source: Uint8Array | Blob): Promise<void> {
1020
- const streamed =
1021
- source instanceof Blob && source.size > (this.negotiatedBatchBytes || SAFE_BATCH_BYTES)
1025
+ const streamed = source instanceof Blob && source.size > this.wholeWriteBytes
1022
1026
  const body = source instanceof Blob && !streamed ? await source.bytes() : source
1023
1027
  const compressed = this.compression && body instanceof Uint8Array
1024
1028
  // REAPI carries compression in the RESOURCE NAME: