Skip to content

Intersections: finish making the debug capture opt-in, follow-up to #387 - #388

Closed
j-modernc-org wants to merge 1 commit into
tdewolff:masterfrom
j-modernc-org:fix-387-followups
Closed

j-modernc-org wants to merge 1 commit into
tdewolff:masterfrom
j-modernc-org:fix-387-followups

Conversation

@j-modernc-org

Copy link
Copy Markdown

Thanks for 0e0d9b2 — the two things that hurt an embedder are gone: rendering a real page
now writes nothing to /tmp and prints nothing to stdout. (The nil-next-node panic
becoming a LineTo outside debug mode is a bonus for us; a drawing call can no longer
take the process down there.)

Three small gaps it left. Each is a couple of lines, each has a test that fails without
its fix, and go test -race . is green with them.

1. The globals are still written unconditionally, so the race remains

bentleyOttmann assigns _ps, _qs, _op, _fillRule on every call, so the flag gates the
read in breakupCrossingSegments and not the write: two goroutines doing boolean path
operations still race. They are only ever read from the debug branch, so the assignment
moves under the same flag.

Without this, TestPathIntersectionConcurrent (two goroutines calling Path.And) reports:

WARNING: DATA RACE
  path_intersection.go:1842   <- _ps, _qs, _op, _fillRule = ps, qs, op, fillRule
  path_intersection.go:152    <- Path.And

2. w.Name() is still called on a nil file

The error check went in, but the log line stayed outside the else:

w, err := os.CreateTemp("", "canvas-testcase-*.gob")
if err != nil {
    log.Println("ERROR:", err)
} else {
    ...
}
log.Println("NOTE: new test case written to", w.Name()) // w is nil when err != nil

(*os.File).Name has no nil check, so a temporary directory that cannot be written panics
inside a drawing call. Only reachable with the flag on now, but it is the one thing the
error check was added to prevent. TestPathIntersectionTestCaseNoTempDir covers it.

3. The captured .gob files are empty

This is the one worth having, since the new doc comment asks people to send them in:

Please send us the test cases generated as temporary files.

As written they carry nothing. gob will not encode a value through an interface whose
concrete type has not been registered, so Encode([]any{_ps, _qs, _op, _fillRule}) fails
with

gob: type not registered for interface: canvas.Paths

and its error is dropped too — what lands on disk is the 12-byte type descriptor. One
machine here had 476 of these from before the fix, every one 12 bytes.

The capture now encodes a concrete pathIntersectionTestCase instead. That needs no
registration (Path already has MarshalBinary), and it reads back from inside the
package with

var tc pathIntersectionTestCase
err := gob.NewDecoder(f).Decode(&tc)

TestPathIntersectionTestCase writes one through the real code path — globals set,
TMPDIR pointed at a temp dir — and decodes it again, so the format cannot silently rot.

Also

The capture body moves into writePathIntersectionTestCase, so the hot loop carries one
call behind the flag rather than the whole block.

We still have the page content that triggers the branch, and can send you proper captures
for #382 now that the encode works — happy to attach a batch.

…dewolff#387

Three small gaps left by 0e0d9b2, each with a test that fails without its fix.

The globals are still assigned unconditionally, so the flag gates the read and
not the write and two goroutines doing boolean path operations still race (they
are only ever read from the debug branch, so the assignment can move under the
same flag). TestPathIntersectionConcurrent reports a data race at that line
under -race without this.

os.CreateTemp's error is checked now, but w.Name() is still called outside the
else, so a temporary directory that cannot be written dereferences a nil file
inside a drawing call. TestPathIntersectionTestCaseNoTempDir covers it.

And the captured .gob holds nothing: gob will not encode a value through an
interface whose concrete type is not registered, so Encode([]any{...}) fails --
its error dropped too -- and leaves a 12-byte type descriptor on disk. One
machine here had 476 such files, all 12 bytes. Since the doc comment asks people
to send these in, that is worth having work: the capture now encodes a concrete
pathIntersectionTestCase, which needs no registration and can be read back from
inside the package with gob.Decode. TestPathIntersectionTestCase writes one
through the real code path and decodes it again.

The whole capture moves into writePathIntersectionTestCase, so the hot loop
carries one call behind the flag rather than the body.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant