diff --git a/lib/renderer/pdf.go b/lib/renderer/pdf.go index ef2d2d2..6b63eca 100644 --- a/lib/renderer/pdf.go +++ b/lib/renderer/pdf.go @@ -4,16 +4,16 @@ import ( "context" "encoding/json" "fmt" + "io" "text/template" "time" "github.com/Zomato/espresso/lib/browser_manager" log "github.com/Zomato/espresso/lib/logger" "github.com/Zomato/espresso/lib/templatestore" - "github.com/go-rod/rod" ) -func GetHtmlPdf(ctx context.Context, params *GetHtmlPdfInput, storeAdapter *templatestore.StorageAdapter) (*rod.StreamReader, error) { +func GetHtmlPdf(ctx context.Context, params *GetHtmlPdfInput, storeAdapter *templatestore.StorageAdapter) ([]byte, error) { startTime := time.Now() if params == nil { @@ -130,10 +130,24 @@ func GetHtmlPdf(ctx context.Context, params *GetHtmlPdfInput, storeAdapter *temp return nil, fmt.Errorf("unable to generate pdf: %v", err) } + duration = time.Since(startTime) + log.Logger.Info(ctx, "reading pdf stream at", map[string]any{"duration": duration}) + + // Read the stream fully BEFORE releasing the tab to prevent memory leak + pdfBytes, err := io.ReadAll(pdfStream) + if err != nil { + return nil, fmt.Errorf("unable to read pdf stream: %v", err) + } + + // Close the stream while the page is still alive to properly cleanup Chrome's IO handle + if closeErr := pdfStream.Close(); closeErr != nil { + log.Logger.Error(ctx, "failed to close pdf stream", closeErr, nil) + } + duration = time.Since(startTime) log.Logger.Info(ctx, "pdf generated at", map[string]any{"duration": duration}) - return pdfStream, nil + return pdfBytes, nil } func getMetaInfo(data map[string]interface{}) map[string]interface{} { diff --git a/lib/renderer/renderer_test.go b/lib/renderer/renderer_test.go index 5753da6..81c0e49 100644 --- a/lib/renderer/renderer_test.go +++ b/lib/renderer/renderer_test.go @@ -78,20 +78,18 @@ func TestGetHtmlPdf(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - pdf, err := GetHtmlPdf(ctx, tt.input, nil) + pdfBytes, err := GetHtmlPdf(ctx, tt.input, nil) if tt.wantErr { assert.Error(t, err) return } assert.NoError(t, err) - assert.NotNil(t, pdf) - defer pdf.Close() + assert.NotNil(t, pdfBytes) + assert.True(t, len(pdfBytes) > 0, "PDF should not be empty") - // Read first few bytes to verify it's a PDF - buf := make([]byte, 4) - _, err = pdf.Read(buf) - assert.NoError(t, err) - assert.Equal(t, []byte("%PDF"), buf) + // Verify first few bytes to confirm it's a PDF + assert.True(t, len(pdfBytes) >= 4, "PDF should be at least 4 bytes") + assert.Equal(t, []byte("%PDF"), pdfBytes[:4]) }) } } diff --git a/service/go.mod b/service/go.mod index 5fec02b..c37e3b9 100644 --- a/service/go.mod +++ b/service/go.mod @@ -2,8 +2,10 @@ module github.com/Zomato/espresso/service go 1.23.0 +replace github.com/Zomato/espresso/lib => ../lib + require ( - github.com/Zomato/espresso/lib v0.0.0-20250422055733-4aa4a50d2b3b + github.com/Zomato/espresso/lib v0.0.0-20250523093533-6d517dcb5c35 github.com/go-rod/rod v0.116.2 github.com/rs/zerolog v1.34.0 github.com/spf13/viper v1.19.0 diff --git a/service/go.sum b/service/go.sum index 13e2eed..85f64b2 100644 --- a/service/go.sum +++ b/service/go.sum @@ -1,7 +1,5 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/Zomato/espresso/lib v0.0.0-20250422055733-4aa4a50d2b3b h1:1g6Zj9y6IiE+POc+sfJ3gRGariQvB5qcT6pCc84NF7U= -github.com/Zomato/espresso/lib v0.0.0-20250422055733-4aa4a50d2b3b/go.mod h1:kLwZ8Pdcx3k5UepIeNTCTKT0+/T78gYLmXFO9O+Ew54= github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs= diff --git a/service/internal/service/generateDoc/generatePdf.go b/service/internal/service/generateDoc/generatePdf.go index 1d2acd3..cfdc50c 100644 --- a/service/internal/service/generateDoc/generatePdf.go +++ b/service/internal/service/generateDoc/generatePdf.go @@ -85,14 +85,13 @@ func GeneratePDF(ctx context.Context, req *PDFDto, templateStoreAdapter *templat IsSinglePage: pdfParams.IsSinglePage, } - pdf, err := renderer.GetHtmlPdf(ctx, &pdfProps, templateStoreAdapter) + pdfBytes, err := renderer.GetHtmlPdf(ctx, &pdfProps, templateStoreAdapter) if err != nil { return fmt.Errorf("failed to generate pdf: %v", err) } - defer pdf.Close() duration := time.Since(startTime) - svcUtils.Logger.Info(ctx, "pdf stream received :: ", map[string]any{"duration": duration}) + svcUtils.Logger.Info(ctx, "pdf bytes received :: ", map[string]any{"duration": duration}) duration = time.Since(startTime) @@ -103,14 +102,15 @@ func GeneratePDF(ctx context.Context, req *PDFDto, templateStoreAdapter *templat return fmt.Errorf("failed to load signing credentials: %v", credErr) } - signedPDF, err := signer.SignPdfStream(ctx, pdf, credentials.Certificate, credentials.PrivateKey) + pdfReader := bytes.NewReader(pdfBytes) + signedPDF, err := signer.SignPdfStream(ctx, pdfReader, credentials.Certificate, credentials.PrivateKey) if err != nil { return fmt.Errorf("failed to sign pdf using SignPdfStream: %v", err) } pdfReader = bytes.NewReader(signedPDF) } else { - pdfReader = pdf + pdfReader = bytes.NewReader(pdfBytes) } svcUtils.Logger.Info(ctx, "starting upload :: ", map[string]any{"duration": duration})