@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 +6 -5
- package/package.json +2 -2
- package/src/cache.ts +12 -12
- package/src/executor.ts +11 -7
- package/src/wire.ts +6 -2
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.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
163
|
-
* digest first (the CAS address must be known
|
|
164
|
-
* then the upload from a second read of the
|
|
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
|
-
|
|
168
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|