Skip to content

fix: clone custom font bytes per gofpdf provider to avoid race#573

Open
johnfercher-ai[bot] wants to merge 2 commits into
masterfrom
fix/concurrent-custom-font-race
Open

fix: clone custom font bytes per gofpdf provider to avoid race#573
johnfercher-ai[bot] wants to merge 2 commits into
masterfrom
fix/concurrent-custom-font-race

Conversation

@johnfercher-ai
Copy link
Copy Markdown

Summary

Closes #550.

Under concurrent generation mode, maroto creates one gofpdf provider per worker. When a custom UTF-8 font is configured, every provider was receiving the same []byte slice from cfg.CustomFonts. During PDF finalization gofpdf mutates that backing array inside utf8FontFile.generateChecksum / GenerateCutFont, which race-detects across workers. Cloning the font bytes per-provider in the gofpdf builder gives each provider its own backing array and eliminates the race.

The fix is a one-liner in internal/providers/gofpdf/builder.go (bytes.Clone(font.GetBytes())). Two regression tests guard it:

  • internal/providers/gofpdf/builder_internal_test.go — unit-level check that Build never mutates the caller's font bytes.
  • maroto_test.go — end-to-end test that reproduces the issue's scenario (concurrent mode + custom font, many rows) and asserts Generate() succeeds cleanly.

Test plan

  • go test -race -count=1 -run TestBuilder_Build ./internal/providers/gofpdf/... — passes.
  • go test -race -count=1 -run TestMaroto_Generate . — passes (includes the "when concurrent mode is active with a custom font, should not race" subtest).
  • go test -count=1 ./... — entire suite is green.

Under concurrent generation mode each worker builds its own gofpdf
provider, but they were sharing the same underlying byte slice from the
configured custom fonts. gofpdf mutates this slice inside putfonts /
GenerateCutFont (specifically generateChecksum), which races when two
workers are finalising documents at the same time.

Clone the font bytes when calling AddUTF8FontFromBytes so every provider
owns an independent backing array, eliminating the write/write and
write/read data races reported by the Go race detector.

Closes #550.
The previous commit introduced internal/providers/gofpdf/builder_internal_test.go
without a package declaration or imports, which broke the gofpdf test
build. Restore the file so it compiles as an external (gofpdf_test)
package test and keeps guarding against the regression where Build
would mutate the caller's custom-font byte slice.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.28%. Comparing base (aa2645f) to head (47c9ae4).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #573   +/-   ##
=======================================
  Coverage   96.28%   96.28%           
=======================================
  Files          65       65           
  Lines        2443     2443           
=======================================
  Hits         2352     2352           
  Misses         60       60           
  Partials       31       31           
Flag Coverage Δ
unittests 96.28% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Race condition - pararel mode + custom font

1 participant