Skip to content

Commit

Permalink
fix: close goroutine pool to prevent goroutine leaks after multiple g…
Browse files Browse the repository at this point in the history
…enerate calls (#499)

* fix: close goroutine pool to prevent goroutine leaks after multiple generate calls

* test: validate pdf creation

---------

Co-authored-by: Fernando Coelho Saraiva <66834579+Fernando-hub527@users.noreply.github.com>
  • Loading branch information
FelipeCooper and Fernando-hub527 authored Dec 10, 2024
1 parent da3191b commit 53eb473
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 12 deletions.
17 changes: 5 additions & 12 deletions maroto.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package maroto
import (
"errors"

"github.com/f-amaral/go-async/pool"
"github.com/johnfercher/maroto/v2/pkg/consts/generation"

"github.com/johnfercher/maroto/v2/internal/cache"
Expand All @@ -15,8 +16,6 @@ import (

"github.com/johnfercher/go-tree/node"

"github.com/f-amaral/go-async/async"
"github.com/f-amaral/go-async/pool"
"github.com/johnfercher/maroto/v2/pkg/components/col"
"github.com/johnfercher/maroto/v2/pkg/components/page"
"github.com/johnfercher/maroto/v2/pkg/components/row"
Expand All @@ -38,9 +37,6 @@ type Maroto struct {
headerHeight float64
footerHeight float64
currentHeight float64

// Processing
pool async.Processor[[]core.Page, []byte]
}

// GetCurrentConfig is responsible for returning the current settings from the file
Expand Down Expand Up @@ -68,12 +64,6 @@ func New(cfgs ...*entity.Config) core.Maroto {
config: cfg,
}

if cfg.GenerationMode == generation.Concurrent {
p := pool.NewPool[[]core.Page, []byte](cfg.ChunkWorkers, m.processPage,
pool.WithSortingOutput[[]core.Page, []byte]())
m.pool = p
}

return m
}

Expand Down Expand Up @@ -289,6 +279,9 @@ func (m *Maroto) generate() (core.Document, error) {
}

func (m *Maroto) generateConcurrently() (core.Document, error) {
p := pool.NewPool[[]core.Page, []byte](m.config.ChunkWorkers, m.processPage,
pool.WithSortingOutput[[]core.Page, []byte]())
defer p.Close()
chunks := len(m.pages) / m.config.ChunkWorkers
if chunks == 0 {
chunks = 1
Expand All @@ -304,7 +297,7 @@ func (m *Maroto) generateConcurrently() (core.Document, error) {
pageGroups = append(pageGroups, m.pages[i:end])
}

processed := m.pool.Process(pageGroups)
processed := p.Process(pageGroups)
if processed.HasError {
return nil, errors.New("an error has occurred while trying to generate PDFs concurrently")
}
Expand Down
27 changes: 27 additions & 0 deletions maroto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package maroto_test

import (
"fmt"
"runtime"
"testing"
"time"

"github.com/johnfercher/maroto/v2/pkg/components/code"
"github.com/johnfercher/maroto/v2/pkg/components/text"
Expand Down Expand Up @@ -369,6 +371,31 @@ func TestMaroto_Generate(t *testing.T) {
// Assert
test.New(t).Assert(sut.GetStructure()).Equals("maroto_concurrent.json")
})
t.Run("goroutines do not leak after multiple generate calls on concurrent mode", func(t *testing.T) {
// Arrange
cfg := config.NewBuilder().
WithConcurrentMode(10).
Build()

sut := maroto.New(cfg)

// Act
for i := 0; i < 30; i++ {
sut.AddRow(10, col.New(12))
}
initialGoroutines := runtime.NumGoroutine()
_, err1 := sut.Generate()
_, err2 := sut.Generate()
_, err3 := sut.Generate()
time.Sleep(100 * time.Millisecond)
finalGoroutines := runtime.NumGoroutine()

// Assert
assert.Nil(t, err1)
assert.Nil(t, err2)
assert.Nil(t, err3)
assert.Equal(t, initialGoroutines, finalGoroutines)
})
t.Run("page number", func(t *testing.T) {
// Arrange
cfg := config.NewBuilder().
Expand Down

0 comments on commit 53eb473

Please sign in to comment.