Hanging TuesPechkin after initial conversion
Asked Answered
B

3

7

I am attempting to create a Web API that can convert a styled HTML file into a PDF.

I am using TuesPechkin and have installed my application into IIS (as a 32-bit app: I have modified the application pool to run in 32bit mode).

IIS 8.5 is running on Windows Server 2012 R2.

PDFConversion class in C#:

using System.Drawing.Printing;
using System.IO;
using TuesPechkin;

namespace PDFApi
{
    public class PDFcreator
    {
        public void convert(string path, string uri)
        {
            IConverter converter = new StandardConverter(
                new RemotingToolset<PdfToolset>(
                    new Win64EmbeddedDeployment(
                    new TempFolderDelpoyment())));

            var document = new HtmlToPdfDocument
            {
                GlobalSettings =
                {
                    ProduceOutline = true,
                    DocumentTitle = "Converted Form",
                    PaperSize = PaperKind.A4,
                    Margins =
                    {
                        All = 1.375,
                        Unit = Unit.Centimeters
                    }
                },
                Objects =
                {
                    new ObjectSettings { RawData = File.ReadAllBytes(uri) }
                }
            };

            byte[] pdfBuf = converter.Convert(document);

            FileStream fs = new FileStream(path, FileMode.Create);
            fs.Write(pdfBuf, 0, pdfBuf.Length);
            fs.Close();
        }
    }
}

The Controller is as follows:

    [Route("converthtml")]
    [HttpPost]
    [MIMEContentFilter]
    public async Task<HttpResponseMessage> ConvertHtml()
    {
        string temppath = System.IO.Path.GetTempPath();

        var streamProvider = new MultipartFormDataStreamProvider(temppath);
        await Request.Content.ReadAsMultipartAsync(streamProvider);


        string filepath = streamProvider.FileData.Select(entry => entry.LocalFileName.Replace(temppath + "\\", "")).First<string>();
        string pdfpath = System.IO.Path.GetTempFileName();
        pdfpath = pdfpath.Substring(0, pdfpath.LastIndexOf('.')) + ".pdf";

        new PDFcreator().convert(pdfpath, filepath);

        var stream = new FileStream(pdfpath, FileMode.Open);
        var result = new HttpResponseMessage(HttpStatusCode.OK);
        result.Content = new StreamContent(stream);
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
        return result;
    }

Here's where it gets a little odd: Experimenting in Fiddler, sending a file once will return the PDF immediately. However, all subsequent POSTs will leave Fiddler hanging. Examining Task Manager shows the CPU and Memory for this task to jump up to 13.5% and ~96MB respectively.

The Temp folder (where the files are stored), on a successful run, will have three files in it: the original uploaded file (stored with a GUID-like name), the file generated via wkHtmlToPdf (in the form "wktemp-"), and the generated pdf (as tempXXXX.pdf). In the case of it hanging, only the first file can be found, indicating that the problem is somewhere in wkHtmlToPdf itself.

However, the real kicker is when the process is manually killed in Task Manager, the API completes successfully, returns the pdf, fully created!

If IIS is reset, the process returns to the original state; a new attempt will work without issue.

Obviously, resetting IIS after every call is hardly viable, nor is manually killing the process each time...

Is this a bug / are there any solutions to this issue?

Bikaner answered 20/1, 2015 at 3:55 Comment(2)
Looks like you're using StandardConverter instead of ThreadSafeConverter.Shipentine
Yes, this solved my issue, thank-you! I also moved the converter out of the function and made it static.Bikaner
O
3

Very important-

@toshkata-tonev answer helped me, but when a lot of sessions used this function our server crushed because over CPU!

The point is that the process should be shared by all sessions as static shared function.

So this was the solution for me:

Implementation: Create a static class in your application:

public static class TuesPechkinInitializerService {
    private static string staticDeploymentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wkhtmltopdf");

public static void CreateWkhtmltopdfPath()
    {
        if (Directory.Exists(staticDeploymentPath) == false)
        {
            Directory.CreateDirectory(staticDeploymentPath);
        }
    }

    public static IConverter converter = 
        new ThreadSafeConverter(
            new RemotingToolset<PdfToolset>(
                new Win64EmbeddedDeployment(
                    new StaticDeployment(staticDeploymentPath)
                )
            )
        );
}

In GLOBAL.asax, I initialize that class on project start:

TuesPechkinInitializerService.CreateWkhtmltopdfPath();

And to use it:

HtmlToPdfDocument pdfDocument = new HtmlToPdfDocument
                {
                    GlobalSettings = new GlobalSettings(),
                    Objects =
                    {
                        new ObjectSettings
                        { 
                            ProduceLocalLinks = true,
                            ProduceForms = true,
                            HtmlText = htmlContent
                        }                    
                    }
                };

                byte[] pdfDocumentData = TuesPechkinInitializerService.converter.Convert(pdfDocument);

Thanks to:

https://github.com/tuespetre/TuesPechkin/issues/152

Outhe answered 17/6, 2020 at 11:40 Comment(1)
I should have answered this question myself 5 years ago when @tuespetre's comment pointed out exactly what my issue was. This was indeed what I had done wrong: I needed a static instance of ThreadSafeConverter Thankyou for posting this answer: I can now close this question!Bikaner
M
15

EDIT: Very important - this answer had a lot of votes, but is not complete. Check the marked solution

            var tempFolderDeployment = new TempFolderDeployment();
            var win32EmbeddedDeployment = new Win32EmbeddedDeployment(tempFolderDeployment);
            var remotingToolset = new RemotingToolset<PdfToolset>(win32EmbeddedDeployment);

            var converter =
                 new ThreadSafeConverter(remotingToolset);


            byte[] pdfBuf = converter.Convert(document);
            remotingToolset.Unload();

Unload remotingToolset will prevent hanging RemotingToolset.Unload();

Mofette answered 13/10, 2015 at 15:56 Comment(5)
This resolved for me (using 64 bit deploy). The converter should have an option to pass this down. But acceptable. Thanks!Rubino
This worked for me as well using WinAnyCPUEmbeddedDeployment.Ulloa
This is my best answer ever, product of days of debugging and pure luck. And still it's not marked as an answered. So sad....Mofette
Devs Make sure to unload after converting.Fitzgerald
This code really helped me so thanks a lot. Just to note that inside the brackets it should be tempFolderDeployment, win32EmbeddedDeployment and remotingToolset instead of TempFolderDeployment, Win32EmbeddedDeployment and RemotingToolsetStarlin
O
3

Very important-

@toshkata-tonev answer helped me, but when a lot of sessions used this function our server crushed because over CPU!

The point is that the process should be shared by all sessions as static shared function.

So this was the solution for me:

Implementation: Create a static class in your application:

public static class TuesPechkinInitializerService {
    private static string staticDeploymentPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wkhtmltopdf");

public static void CreateWkhtmltopdfPath()
    {
        if (Directory.Exists(staticDeploymentPath) == false)
        {
            Directory.CreateDirectory(staticDeploymentPath);
        }
    }

    public static IConverter converter = 
        new ThreadSafeConverter(
            new RemotingToolset<PdfToolset>(
                new Win64EmbeddedDeployment(
                    new StaticDeployment(staticDeploymentPath)
                )
            )
        );
}

In GLOBAL.asax, I initialize that class on project start:

TuesPechkinInitializerService.CreateWkhtmltopdfPath();

And to use it:

HtmlToPdfDocument pdfDocument = new HtmlToPdfDocument
                {
                    GlobalSettings = new GlobalSettings(),
                    Objects =
                    {
                        new ObjectSettings
                        { 
                            ProduceLocalLinks = true,
                            ProduceForms = true,
                            HtmlText = htmlContent
                        }                    
                    }
                };

                byte[] pdfDocumentData = TuesPechkinInitializerService.converter.Convert(pdfDocument);

Thanks to:

https://github.com/tuespetre/TuesPechkin/issues/152

Outhe answered 17/6, 2020 at 11:40 Comment(1)
I should have answered this question myself 5 years ago when @tuespetre's comment pointed out exactly what my issue was. This was indeed what I had done wrong: I needed a static instance of ThreadSafeConverter Thankyou for posting this answer: I can now close this question!Bikaner
S
0

It seems you are using WkHtmlToPdf 64 bits and you've installed the 32 bits one.

You should change :

        IConverter converter = new StandardConverter(
            new RemotingToolset<PdfToolset>(
                new Win64EmbeddedDeployment(
                new TempFolderDelpoyment())));

to

        IConverter converter = new StandardConverter(
            new RemotingToolset<PdfToolset>(
                new Win32EmbeddedDeployment(
                new TempFolderDelpoyment())));
Spare answered 24/3, 2015 at 13:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.