@vzn/vx-dte 0.0.650 → 0.0.652
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 +75 -31
- package/package.json +3 -3
- package/src/dispatch.ts +501 -26
- package/src/forecast.ts +1 -2
- package/src/github.ts +16 -5
- package/src/messages.ts +52 -31
- package/src/orchestrator.ts +551 -352
- package/src/planner.ts +1322 -172
- package/src/plugin.ts +48 -8
- package/src/seal.ts +11 -4
- package/src/wire-host.ts +98 -0
- package/src/wire-thread.ts +168 -0
- package/src/wire.ts +117 -68
- package/src/worker.ts +112 -62
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ before sealing, since no relay or socket compresses what is sealed. It
|
|
|
88
88
|
is padded with trailing spaces to its size class, a power of two from
|
|
89
89
|
256 bytes, then whole transport messages, so the relay learns no size
|
|
90
90
|
within a class. Inside, each send is numbered and acknowledged
|
|
91
|
-
end to end, and a quiet
|
|
91
|
+
end to end, and a quiet worker reports (Switchboard § Delivery, § Liveness).
|
|
92
92
|
|
|
93
93
|
## Try it
|
|
94
94
|
|
|
@@ -204,7 +204,8 @@ every 30 s, asks with each answer's ETag (an unchanged answer is free),
|
|
|
204
204
|
and asks nothing until a spent limit resets. A dispatch the limit refuses
|
|
205
205
|
is sent again at the reset, if that comes within 5 minutes; past that it
|
|
206
206
|
fails, naming when. A cancel the limit refuses is sent again at the
|
|
207
|
-
reset, if that comes within a minute
|
|
207
|
+
reset, if that comes within a minute, unless a worker given up as late
|
|
208
|
+
joined meanwhile: the run took it, so its workflow run goes on. A run that dies before it cancels
|
|
208
209
|
(a cancelled or timed-out job) leaves it to the job's last step, which
|
|
209
210
|
cancels each worker run the run listed in `RUNNER_TEMP`. Past both, a
|
|
210
211
|
worker that starts reads its run's attempt (`parent`) first, and once
|
|
@@ -456,9 +457,11 @@ use your own instead.
|
|
|
456
457
|
- A worker reports the cores and memory its machine has when it joins.
|
|
457
458
|
One the run started is held to the size it was started as, so
|
|
458
459
|
`local()` workers sharing a host split it rather than each take all of
|
|
459
|
-
it. Each frame it sends (a task
|
|
460
|
-
|
|
461
|
-
|
|
460
|
+
it. Each frame it sends (a task saved, its report, a fault) carries its
|
|
461
|
+
machine's load: cores busy over the last second and memory in use. A
|
|
462
|
+
heartbeat with nothing else to say, it sends its report: that load, the
|
|
463
|
+
tasks it runs and the last batch it read. A task of that batch or before
|
|
464
|
+
its report no longer names goes back to the queue at once. A task goes to it only when it fits now: while
|
|
462
465
|
one of its cores is free and the task's memory fits. What a worker has
|
|
463
466
|
in use is what its running tasks used on their last run (history's
|
|
464
467
|
measured CPU and peak RAM), or its live load plus what was sent since
|
|
@@ -522,6 +525,8 @@ use your own instead.
|
|
|
522
525
|
or of files a formatter changed) is sent in parts, none of it dropped.
|
|
523
526
|
An order too long for one frame (a task with thousands of deps) sends
|
|
524
527
|
its deps ahead of it the same way.
|
|
528
|
+
- A worker gets each dependency in full once. Later orders name it, so a
|
|
529
|
+
deep graph's orders stay small.
|
|
525
530
|
- A task runs with the orchestrator's environment under the worker's
|
|
526
531
|
root. `HOME`, `TMPDIR`, `USER` and the like are the worker's own, unless
|
|
527
532
|
the task's `env.define` sets them; `npm_execpath` is the workspace's
|
|
@@ -535,10 +540,10 @@ use your own instead.
|
|
|
535
540
|
leaves no file. One waits there up to 10 s; past that, with that worker
|
|
536
541
|
gone or of another platform, or with its uncached upstreams on two
|
|
537
542
|
workers, the upstream runs again where the dependant can go, and the
|
|
538
|
-
dependant follows it. A task whose upload no remote took
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
runs, its
|
|
543
|
+
dependant follows it. A task whose upload no remote took after three
|
|
544
|
+
tries (too big, a 429, a reset connection) is its worker's fault: that
|
|
545
|
+
worker takes no more tasks and the task runs on another; a second fault
|
|
546
|
+
fails the run. Before a task runs, its
|
|
542
547
|
worker removes the files under its outputs that an earlier task left,
|
|
543
548
|
except committed files, nested projects' files, and its dependencies'
|
|
544
549
|
outputs (an additive task builds on its upstream's tree). An upstream's
|
|
@@ -576,9 +581,14 @@ use your own instead.
|
|
|
576
581
|
`timeouts`).
|
|
577
582
|
- A worker that stops answering, stops saying it holds a task, or cannot
|
|
578
583
|
run one (a full disk) gets no more work, and its tasks run again on
|
|
579
|
-
another. A task two such workers
|
|
580
|
-
|
|
584
|
+
another. A task two such workers were sent fails, naming both; one whose
|
|
585
|
+
send failed does not count. With no worker of the run left
|
|
581
586
|
to take it and none starting, it fails at once.
|
|
587
|
+
- A task that failed and retries (its `retries`, or the one retry a
|
|
588
|
+
known-flaky task gets) runs on another worker than the attempt that
|
|
589
|
+
failed, as core asks (`ExecuteRequest.attempt`): the worker, or what
|
|
590
|
+
ran beside it, may be the flake. With no other worker of its platform
|
|
591
|
+
in the run, it runs there again rather than wait.
|
|
582
592
|
- A relay that answers nothing for 10 s, every try counted, fails every task not done
|
|
583
593
|
(`the relay is unreachable`); none runs here, which may be far smaller
|
|
584
594
|
than the workers. What finished is saved, so a rerun resumes from it.
|
|
@@ -587,8 +597,8 @@ use your own instead.
|
|
|
587
597
|
- The relay stores nothing, so a restart forgets the run. The orchestrator
|
|
588
598
|
joins again as its host and each worker joins again as itself, for two
|
|
589
599
|
heartbeats (4 s on the default); tasks keep running meanwhile, and what
|
|
590
|
-
either side sent goes again once both are back, taken once.
|
|
591
|
-
|
|
600
|
+
either side sent goes again once both are back, taken once. A worker
|
|
601
|
+
keeps what it runs, and one sent a task it runs already
|
|
592
602
|
does not run it again, so no work repeats. One not back by then
|
|
593
603
|
ends: a worker's tasks run elsewhere, an orchestrator's run fails.
|
|
594
604
|
Joining has the same 10 s in all, its socket and request together: a
|
|
@@ -611,22 +621,32 @@ use your own instead.
|
|
|
611
621
|
orchestrator's at its run's end is the final call that closes the run,
|
|
612
622
|
and a worker's when it is done. Either whose socket drops without it
|
|
613
623
|
dials again, 3 times within 10 s, and the relay keeps it, and its run,
|
|
614
|
-
for those 10 s; past them the run is over.
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
624
|
+
for those 10 s; past them the run is over. Either side hearing nothing
|
|
625
|
+
from the other for 3 heartbeats (6 s on the default) loses it, and the
|
|
626
|
+
rest follows from that: a worker that said nothing for half of it (3 s)
|
|
627
|
+
sends its report; the orchestrator never speaks on a timer, only orders
|
|
628
|
+
and the acks of what it heard; a send unacknowledged for 3 s goes again.
|
|
629
|
+
A worker that loses its orchestrator ends, and says so. Nobody probes, the relay answers
|
|
630
|
+
neither, and nobody pings. An orchestrator
|
|
621
631
|
with no worker yet asks the relay an empty frame instead; a frame the
|
|
622
632
|
relay leaves unanswered a heartbeat closes the socket, which is dialed
|
|
623
633
|
again.
|
|
634
|
+
- The orchestrator's socket, its acks and redials run on a thread
|
|
635
|
+
of their own, so sync work on the run's loop never holds them (one CI
|
|
636
|
+
run went quiet 12 s that way and lost every worker). A loop held a
|
|
637
|
+
second or more says so, with how long; one held 5 heartbeats (10 s)
|
|
638
|
+
ends the run, as a dead orchestrator would.
|
|
624
639
|
- Each send is numbered for its receiver and held until the receiver
|
|
625
640
|
acknowledges it, so one a socket or the relay dropped goes again after
|
|
626
|
-
a heartbeat, or at once on a new socket; the receiver takes
|
|
627
|
-
in order. An ack rides the next send back
|
|
628
|
-
|
|
629
|
-
|
|
641
|
+
a heartbeat and a half, or at once on a new socket; the receiver takes
|
|
642
|
+
each once, in order. An ack rides the next send back; the orchestrator's,
|
|
643
|
+
with no order to ride, go together after half a heartbeat, except a
|
|
644
|
+
quiet worker's beat, acked at once.
|
|
645
|
+
- A task sent runs at once: no message says it began. A command that
|
|
646
|
+
ended (`ran`, with when it began and ended by the worker's clock) rides
|
|
647
|
+
the worker's next frame, never alone; a task that saves first says it
|
|
648
|
+
in its `end` (begun, ran, ended). The run reads those times against the
|
|
649
|
+
clock of the frame that carried them.
|
|
630
650
|
- A run where no task began or ended for a minute warns what it waits
|
|
631
651
|
on, a line a task: which worker, and whether it runs or saves.
|
|
632
652
|
- A message the relay would not take is a warning naming it, its
|
|
@@ -651,9 +671,9 @@ An idle worker the rest of the run cannot keep busy leaves (`left`): one
|
|
|
651
671
|
beyond the peak of the cores the remaining tasks keep busy, so a lull
|
|
652
672
|
between two wide parts keeps it. One a remaining task needs for its size
|
|
653
673
|
stays, and so does one a later task needs when its size's `max` is
|
|
654
|
-
spent, since none could start in its place. A lost worker's
|
|
674
|
+
spent, since none could start in its place. A lost worker's tasks run again on
|
|
655
675
|
another, and the run starts one in its place. A worker is lost when its
|
|
656
|
-
socket closes, when it
|
|
676
|
+
socket closes, when it says nothing for 3 heartbeats (6 s), or at
|
|
657
677
|
once when its adapter sees its process or job end. Workers start at the first
|
|
658
678
|
task the cache does not answer, so a warm run starts none. Once one has
|
|
659
679
|
joined, its warm-up is what the next of its size takes at the least: work
|
|
@@ -670,10 +690,10 @@ run is over, one still starting is `not joined`), the tasks it
|
|
|
670
690
|
has done, failed or had aborted by a stop, and the ones it runs now; while the run goes, those that left
|
|
671
691
|
are one line (`3 workers left 9 done`). A worker the
|
|
672
692
|
run started is named by its size and the order it was started in; one
|
|
673
|
-
that joined on its own is `worker#N`. A task
|
|
674
|
-
|
|
693
|
+
that joined on its own is `worker#N`. A task waiting for a worker has no
|
|
694
|
+
row; the tasks line counts it `scheduled`
|
|
675
695
|
(`30 success · 4 running · 12 scheduled · 62 total`). Its row and clock
|
|
676
|
-
start when
|
|
696
|
+
start when it is sent: a worker runs what it is sent at once. A worker is `online` while the run
|
|
677
697
|
goes; it ends `left` when the run let it go as not needed, `lost` when
|
|
678
698
|
it crashed or went silent, and `stopped` when the run's end stopped it.
|
|
679
699
|
Once the run is over, each worker adds its warm-up (from asking the
|
|
@@ -733,6 +753,9 @@ machine together, and the share of their history the run's tasks used:
|
|
|
733
753
|
probe at 0.4s: 5 tasks left to run, 5 known misses
|
|
734
754
|
asked 1 at 0.5s for 5 tasks (5 measured), plan 12.0s
|
|
735
755
|
ideal 10.8s: each task as long as it took, placed perfectly
|
|
756
|
+
best 2 × small (4c/14G): 11.4s, the fewest of its sizes
|
|
757
|
+
more 3 × small (4c/14G): 10.8s, past its sizes' max
|
|
758
|
+
any 1 × (8c/28G): 10.9s, of any cores, the least core time
|
|
736
759
|
total cpu utilization 58%
|
|
737
760
|
```
|
|
738
761
|
|
|
@@ -763,6 +786,17 @@ real warm-up. That ideal end (`TaskExecutor.idealMs`) sits beside the
|
|
|
763
786
|
real time in the run summary and the footer's `ideal` line, and
|
|
764
787
|
`@vzn/vx-ci` prints it.
|
|
765
788
|
|
|
789
|
+
The same replay says which workers to declare next time
|
|
790
|
+
(`TaskExecutor.advice`), each from an empty run with the plan's own
|
|
791
|
+
rules (the fewest that end it within its margin of the soonest): `best`
|
|
792
|
+
is how many of each size; `more`, shown only when more than a size's
|
|
793
|
+
`max` would end the run sooner past that margin, is how many it would
|
|
794
|
+
take; `any` is one shape for each platform, of 2 to 64 cores (memory a
|
|
795
|
+
core as the platform's sizes have most), the one ending as soon for the
|
|
796
|
+
least core time. Raise a size's `max`, or change its `cpu` and
|
|
797
|
+
`memory`, by them. They are in the footer and the run summary, and
|
|
798
|
+
`@vzn/vx-ci` prints them under "Workers to declare".
|
|
799
|
+
|
|
766
800
|
## Environment
|
|
767
801
|
|
|
768
802
|
What `@vzn/vx-dte` reads from the CI job it runs in:
|
|
@@ -804,16 +838,26 @@ process.env.CF_ACCESS_CLIENT_ID, … }`); an unset value is left out, and
|
|
|
804
838
|
absent, planned as this machine's and not checked on join). With no adapter, the sizes' platforms are
|
|
805
839
|
the ones joining workers may have. Only work counts:
|
|
806
840
|
a task that missed the cache, or waits on a cached one that did, and
|
|
807
|
-
every uncached task, which runs every time; groups and hits need no worker.
|
|
841
|
+
every uncached task, which runs every time; groups and hits need no worker. The run plays
|
|
808
842
|
that work out on its workers and on N more (`src/planner.ts`), each new
|
|
809
843
|
one joining after its size's warm-up: this run's first of that size,
|
|
810
844
|
else an earlier run's, else any size's this run, else 20 s,
|
|
811
845
|
and takes the fewest N that end within 5% (5 s at least) of the best.
|
|
846
|
+
It plays only two warm-ups and the longest task ahead; past that, the
|
|
847
|
+
rest ends no sooner than its longest chain, nor than its work spread
|
|
848
|
+
on the cores it has, and the next plan, a warm-up on, sees further.
|
|
812
849
|
Sizes of different platforms come in turns, so of those N it drops
|
|
813
850
|
each the plan ends as soon without, its platform's work no more than
|
|
814
851
|
5 s later.
|
|
815
852
|
That margin is spent once: later plans keep its deadline until new
|
|
816
853
|
work is known. A task waiting on one that runs waits until it ends.
|
|
854
|
+
Then the run follows that plan: a start it wants later is asked when
|
|
855
|
+
due, and a worker idle with its planned work done goes. It plays again
|
|
856
|
+
only when reality leaves the plan: new work, work that leaves without
|
|
857
|
+
running, a worker that joins, leaves or warms up off plan, a task past
|
|
858
|
+
its time or ending sooner, one of no known time ending, a task starting
|
|
859
|
+
with its time, CPU or memory more than 5% off (1 s, 0.05 cores, 64 MiB
|
|
860
|
+
at least), or its window moving on.
|
|
817
861
|
What is left, running tasks too, takes as many times its history as
|
|
818
862
|
the run's finished tasks took theirs (once they had 20 s of it; never
|
|
819
863
|
less): a task past its forecast would otherwise be planned to end now,
|
|
@@ -863,7 +907,7 @@ HEAD^`), and every task after one (`src/forecast.ts`): a lockfile or
|
|
|
863
907
|
- `adapter` is the `DteAdapter` that starts workers, of the sizes in
|
|
864
908
|
`workers`: at least one with `max` above 0, or `dte()` is refused.
|
|
865
909
|
Absent, the run starts none and uses the workers that join `relay`.
|
|
866
|
-
- `relay` is the relay's URL, http(s)
|
|
910
|
+
- `relay` is the relay's base URL, http(s), without `/v1`, `?query` or `#fragment`; absent, the adapter's own.
|
|
867
911
|
- `token` answers the relay's bearer token (default `VX_DTE_TOKEN`). It
|
|
868
912
|
is asked again a minute before the token's `exp`, when it has one.
|
|
869
913
|
- `unmeasured`, `{ cpu, memory }` (default 2 cores, `'4GB'`), is what a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx-dte",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.652",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Distributed task execution for vx: one run spread over workers your own CI spawns, joined through a relay.",
|
|
6
6
|
"keywords": [
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"bun": ">=1.4.4"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@vzn/vx-dte-server": "0.0.
|
|
32
|
+
"@vzn/vx-dte-server": "0.0.652"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@vzn/vx": "^0.0.
|
|
35
|
+
"@vzn/vx": "^0.0.652"
|
|
36
36
|
},
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|