Skip to content

Long Time operation Fail after 2 minutes #5228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
matheusavi opened this issue Jul 30, 2018 · 13 comments
Closed

Long Time operation Fail after 2 minutes #5228

matheusavi opened this issue Jul 30, 2018 · 13 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-spa

Comments

@matheusavi
Copy link

Microsoft.AspNetCore.All Version 2.1.2
Microsoft.AspNetCore.NodeServices Version 2.1.1
Microsoft.NETCore.App Version 2.1.2
TargetFramework netcoreapp2.1

I'm doing something like this (generate a PDF file from HTML in node services, and them return the file to the user):
https://code.msdn.microsoft.com/How-to-export-HTML-to-PDF-c5afd0ce

I'm using the following plugin on node file:
https://github.com/westy92/html-pdf-chrome

No problem generating some small report's, but when the operation take longer than 2 minutes, the request fail and I got the following exception:

System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.IO.IOException: The server returned an invalid or unrecognized response.
   at System.Net.Http.HttpConnection.FillAsync()
   at System.Net.Http.HttpConnection.ReadNextResponseHeaderLineAsync(Boolean foldedHeadersAllowed)
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at Microsoft.AspNetCore.NodeServices.HostingModels.HttpNodeInstance.InvokeExportAsync[T](NodeInvocationInfo invocationInfo, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance.InvokeExportAsync[T](CancellationToken cancellationToken, String moduleName, String exportNameOrNull, Object[] args)
   at Microsoft.AspNetCore.NodeServices.NodeServicesImpl.InvokeExportWithPossibleRetryAsync[T](String moduleName, String exportedFunctionName, Object[] args, Boolean allowRetry, CancellationToken cancellationToken)
   at TestPdf.Controllers.HomeController.About(INodeServices nodeServices) in C:\Users\matheus.avi\Documents\Visual Studio 2017\Projects\TestPdf\TestPdf\Controllers\HomeController.cs:line 59
   at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

But the operation still's running and success is logged in the console.
Also if I configure it to generate a local File, the file is generated.

Startup.cs

services.AddNodeServices(options =>
{
    options.InvocationTimeoutMilliseconds = 600000;
    options.NodeInstanceOutputLogger = _logger;
    options.WatchFileExtensions = new string[] { };
});

pdf2.js

module.exports = function (callback, html, header, footer) {
    const htmlPdf = require('html-pdf-chrome');
    const options = {
        port: 9222,
        printOptions: {
            displayHeaderFooter: true,
            headerTemplate: header,
            footerTemplate: footer,
            marginTop: 1.1,
            marginBottom: 1,
            marginLeft: 0,
            marginRight: 0,
        },
        timeout: 600000
    };
    console.log('Create pdf');
    htmlPdf.create(html, options)
        .then(function (pdf) {
            console.log('pdf created');
            callback(null, JSON.parse(JSON.stringify(pdf.toBuffer())).data);
        })
        .catch(function (e) {
            console.log('Error generating pdf');
            console.log(e);
            callback(e, null);
        });
}; 

HomeController.cs

 public async Task<IActionResult> About([FromServices] INodeServices nodeServices)
{
	HttpClient hc = new HttpClient();
	hc.Timeout = TimeSpan.FromMinutes(10);

	var htmlContent = await hc.GetStringAsync($"http://{Request.Host}/report.html");
	var headerContent = await hc.GetStringAsync($"http://{Request.Host}/header.html");
	var footerContent = await hc.GetStringAsync($"http://{Request.Host}/footer.html");

	var result = await nodeServices.InvokeAsync<byte[]>("./pdf2", htmlContent, headerContent, footerContent);

	HttpContext.Response.ContentType = "application/pdf";

	HttpContext.Response.Headers.Add("x-filename", "report.pdf");
	HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "x-filename");
	HttpContext.Response.Body.Write(result, 0, result.Length);
	return new ContentResult();

}
@matheusavi
Copy link
Author

I tried to change the node timeout but had no effect.

Using this in Startup.cs resolves the problem:

options.UseSocketHosting();

But I had to build it manually, since the last version isn't available in Nuget.

Will this package be maintened?

@InvincibleDRT
Copy link

@spyker0 did you figure out any other way to fix this....Im also having same issue and im generating pdfs with puppeteer in node

@matheusavi
Copy link
Author

This is the only way I find it.
Actually we are doing a java API using Jasper.
I really didnt find a C# or JS free plugin that supplies our needs.

@aspnet-hello aspnet-hello transferred this issue from aspnet/JavaScriptServices Dec 17, 2018
@aspnet-hello aspnet-hello added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-spa labels Dec 17, 2018
@Jaffacakes82
Copy link

Jaffacakes82 commented Mar 12, 2019

options.UseSocketHosting();

What namespace is UseSocketHosting in? Is this part of JavaScriptServices or something else?

EDIT: Found it, although using that just throws a method not found exception for me.

This issue is being caused by the Node HTTP server closing the connection after 2 minutes (which it does by default) before the PDF generation operation has completed.

This issue mentions making this configurable but was archived shortly after. Is it possible to configure this timeout currently using the NodeServices API or some kind of override in the JavaScript modules that get invoked?

@SteveSandersonMS I assume this still isn't possible? Are you still accepting PRs for this?

@SteveSandersonMS
Copy link
Member

SteveSandersonMS commented Mar 22, 2019

What namespace is UseSocketHosting in?

I wouldn't recommend using it. It's not something we've ever shipped as a supported package, nor are we planning to do so since we found that the default HTTP-based transport performs equally well.

Are you still accepting PRs for this?

Since you can call some function of your own inside Node that changes the server.timeout value, it doesn't seem like any other change is needed.

So, I'll close this, but please let us know if you think I'm mistaken.

@Jaffacakes82
Copy link

Jaffacakes82 commented Mar 26, 2019

@SteveSandersonMS sorry, perhaps I'm being naive but in order to set the timeout would it not have to be done in this file?

I don't fully understand how the NodeServices APIs work but surely my custom JavaScript module just gets imported dynamically, by that point, it's too late to set server timeout?

@SteveSandersonMS
Copy link
Member

No, you can make the call from anywhere. Just do it before any other call that you want the new timeout to be effective on.

@Jaffacakes82
Copy link

@SteveSandersonMS still not following - surely this would mean I need to have access to the HTTP server object in my module, which I don't because the module isn't doing anything to do with handling HTTP requests. Do you have an example by any chance?

@SteveSandersonMS
Copy link
Member

All I'm saying is that, if you want to change Node's server.timeout, then you can go to whatever JS code you are invoking via NodeServices and add a line that modifies server.timeout as per Node docs.

@Jaffacakes82
Copy link

@SteveSandersonMS apologies to keep coming back to this, but I still don't think this is possible.

Consider the following module called app.js that does nothing but sleep for three minutes:

module.exports = function (callback, data) {
    setTimeout(main(callback, data), 180000);
}

const main = (callback, data) => {
    if (data) {
        callback(null, data);
    }

    callback("No data!", null);
}

Invoked by the following C#:

this.nodeServices.InvokeAsync<string>("app", "random data string").Result;

This will throw the following exception because the Node HTTP server being spun up internally by INodeServices is timing out after two minutes (as is the default):
System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.IO.IOException: The server returned an invalid or unrecognized response.

From what I can see, to prevent this happening you would need to set the server.timeout of the Node HTTP server created on line 118 of this file which is absolutely not possible from within the module defined above.

I really am hoping I'm being stupid, as I need a solution to this, but I can't get my head around your comments about setting this myself in my module.

Thanks in advance!

@SteveSandersonMS
Copy link
Member

OK, I finally understand your scenario. Thanks for the clarification! I now see that the underlying issue is that your code doesn't have access to the server object, and therefore has no way to modify server.timeout.

If this is something you're interested in adding a PR to address, I think we'd certainly be open to it. My suggestion would be:

  • Use the existing InvocationTimeoutMilliseconds option value. Developers can set this when configuring the service, e.g. services.AddNodeServices(options => { options.InvocationTimeoutMilliseconds = 10000 };.
  • OutOfProcessNodeInstance receives this as a constructor parameter here.
  • You can modify the PrepareNodeProcessStartInfo logic so that it also passes the invocationTimeoutMilliseconds value as a command-line argument to the launched Node process
  • Then, in HttpNodeInstanceEntryPoint.ts, see how it already parses incoming command-line args, and get your new timeout param value if supplied (remember to convert to a numeric value using parseInt or similar). Be sure to tolerate no value being passed for that argument. If some value was passed, then you can assign it to server.timeout.

Reopening since this is definitely a fair feature request!

@Jaffacakes82
Copy link

Yay, thanks!

Sorry for my poor previous explanations. Just to let you know, there's a potential duplicate here that I raised: #8447

You may want to close one or the other and I can raise a PR referencing whichever issue remains open.

Thanks.

@SteveSandersonMS
Copy link
Member

OK, your other issue is less noisy, so I'll close this one in favour of that. But I'll paste my PR suggestion to the other one. Thanks!

@ghost ghost locked as resolved and limited conversation to collaborators Dec 3, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates feature-spa
Projects
None yet
Development

No branches or pull requests

5 participants