test(build): deflake graph scheduler test with synchronization - #314
reyreavman wants to merge 2 commits into
Conversation
Verification
Review focus
Follow-up
|
|
Reviewed this after spending a full investigation on exactly the failure it fixes: 1. The masking is the expensive half, and it is still unfixedThe description names it ("every occurrence costs a full investigation before it can even be attributed") but nothing in the diff addresses it, so the next plain-test failure in
All on CLI v2.28.1, so this is not a CLI-version artifact. Worth knowing separately: CI runs CLI 2.20.1 against library 2.28.1 — every The only fix that needs no workflow change is to stop having plain tests in a Ginkgo package: convert the 17 If that is too large for this PR, please split it out and link it here rather than leaving the trap armed. 2.
|
TestDoImagesInParallel_DependentImageDoesNotWaitForUnrelatedSlowImage asserted that a chain of three 10ms images finishes before an unrelated image delayed by 150ms. That margin is a race, not a property: measured under 8x concurrency the chain stretches from 35ms to 84ms, and on a loaded CI runner it can exceed the budget and invert the order. A vanilla test failing this way is invisible. Under ginkgo -p, which task test:unit uses, the stdout of vanilla go tests is swallowed, so the suite reports every spec green and SUCCESS while the binary exits non-zero, leaving only "There were failures detected in the following suites: build ./pkg/build" and exit code 201. Hold "slow" on a gate released only once "c" has been recorded, so the ordering holds regardless of machine load. Should a wave/level barrier ever be reintroduced, "c" is no longer reachable while "slow" is pending, the gate is never released, and the test fails on the context deadline instead of depending on which sleep happened to win. Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
TestDoImagesInParallel_AnnotatesEachImageWithARealWorkerID asserted that both workers were used by giving 4 images a 20ms delay each and checking two distinct worker IDs were seen. runWorkers spawns worker goroutines without any handshake, so nothing stops worker 0 from draining the queue before worker 1 is scheduled: the sleeps only masked it by yielding. Removing them under GOMAXPROCS=1 fails 38 of 50 runs with a single worker taking all four images. Hold every image on a barrier that opens once two are in the phase at once. Two images built concurrently is the property "both workers were used", so it can no longer pass on one worker: the barrier is never reached and the test fails on the context deadline. Pass a per-test home dir to werf.Init. With an empty homeDirOption it falls back to ~/.werf and creates the host locker there, which under ginkgo -p is 15 processes sharing one path, and on a self-hosted runner is shared with every concurrent job. Say in the comments that the gate and the barrier are the detectors and that the ordering assertions are now true by construction, so the next reader does not keep the assertions and drop the synchronization. Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
25ee561 to
9ff491c
Compare
Verification
Review focus
Follow-up
Not addressed, deliberately: a
|
Summary
The
unitCI job could fail with nothing failing in its log:Build Suite - 148/148 specs ... SUCCESS!, no--- FAILanywhere, thenThere were failures detected in the following suites: build ./pkg/buildand exit code 201. Two plain tests inpkg/buildasserted scheduling properties through racing sleep budgets and could lose those races on a loaded runner, and ginkgo-p— whichtask test:unituses — swallows the stdout of plain go tests, so a loss left no trace. No werf behavior changes.What
TestDoImagesInParallel_DependentImageDoesNotWaitForUnrelatedSlowImageenforces its ordering by synchronization:slowis held on a gate released only oncechas been recorded, so the observed order is[a, b, c, slow]regardless of how loaded the machine is. It previously budgeted three sequential 10ms images against one 150ms image and required the chain to win.TestDoImagesInParallel_AnnotatesEachImageWithARealWorkerIDenforces "both workers were used" by holding every image on a barrier that opens once two are in the phase at once, replacing four 20ms sleeps and arequire.Len(seenWorkers, 2)that nothing guaranteed.runWorkersspawns worker goroutines without a handshake, so worker 0 could drain the whole queue before worker 1 was scheduled. VERIFIED: with the sleeps removed underGOMAXPROCS=1, the old form fails 38 of 50 runs with"map[0:true]" should have 2 item(s), but has 1.image "slow" gave up waiting for its gateandimage "w1" waited for 2 images to build concurrently, never reached.werf.Init. With an emptyhomeDirOptionit falls back to~/.werfand creates the host locker and run-timestamp files there, which under ginkgo-pis 15 processes on one path and on a self-hosted runner is shared with every concurrent job.TestDoImagesInParallel_AssignsBuildOrderIndexByRealDequeueNotStaticTopologyis deliberately unchanged: bothslowandaare queued innewGraphSchedulerbefore any worker starts, so indices 0/1 are theirs by construction under any load.pkg/build/graph_scheduler.goandpkg/build/conveyor.goare untouched, so nothing about real build scheduling differs.Why
Both assertions were races dressed as properties. Sleeps are wall-clock and complete on time under any load, while the work they were being raced against pays scheduling and dispatch overhead that grows with contention: the a→b→c chain completes in ~35ms idle and ~84ms under 8-way concurrency, over half its 150ms budget, and the
unitjob runs 15 ginkgo processes on a shared runner. The worker-ID test was the same shape, and its sleeps were the only reason it ever saw two workers.Leaving them costs more than a retry. Because ginkgo
-pdiscards plain test output, a failure surfaces as a green suite with a bare exit code 201 naming no test, so every occurrence costs a full investigation before it can even be attributed.Widening the gaps instead — a 1s delay on
slow, say — was rejected: it is the same racing budget with a larger constant, it only moves the threshold at which load inverts the result, and it makes the suite slower for the privilege.