@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 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 peer probed (Switchboard § Delivery, § Liveness).
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. A run that dies before it cancels
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 begun, a command ended, a task saved,
460
- its heartbeat) carries its machine's load: cores busy over the last
461
- second and memory in use. A task goes to it only when it fits now: while
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 (too big, a 429) is kept the same way: its output comes to the orchestrator with
539
- its result, its files stay on the worker, and a task on the
540
- orchestrator's machine that needs them fails saying so. Before a task
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 had begun fails, naming both; one an
580
- worker had not begun yet does not count. With no worker of the run left
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. Leases
591
- wait while either is away, and a worker sent a task it runs already
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. A peer quiet on a socket that never
615
- closed is lost within two heartbeats (4 s on the default): only a
616
- peer heard from for none of a heartbeat and a half is probed (an
617
- empty numbered send), which it acks at once; either side hearing
618
- nothing from the other for 1.9 heartbeats loses it, a worker by
619
- ending. A quiet pair trades one probe and its ack each heartbeat and a
620
- half, the relay answers neither, and nobody pings. An orchestrator
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 each once,
627
- in order. An ack rides the next send back, and those with none to ride
628
- go together in one frame per half heartbeat: no message of its own on
629
- a busy run.
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 begun tasks run again on
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 stops answering the orchestrator's probes, or at
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 sent to a worker but not yet
674
- begun there has no row; the tasks line counts it `scheduled`
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 the worker begins it. A worker is `online` while the run
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. At each miss, finish and join the run plays
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); absent, the adapter's own.
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.650",
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.650"
32
+ "@vzn/vx-dte-server": "0.0.652"
33
33
  },
34
34
  "peerDependencies": {
35
- "@vzn/vx": "^0.0.650"
35
+ "@vzn/vx": "^0.0.652"
36
36
  },
37
37
  "repository": {
38
38
  "type": "git",