Taritsyn / BundleTransformer

Bundle Transformer - a modular extension for System.Web.Optimization (also known as the Microsoft ASP.NET Web Optimization Framework).
Apache License 2.0
130 stars 19 forks source link

Concurrent Less file translations with wrong result #53

Closed jcruizsebastian closed 2 years ago

jcruizsebastian commented 2 years ago

Hello Andrey, nice to meet you.

We are using BundleTransfomer on a web site which makes use of it in order to bundle Less resources. We are experimenting two diferent behaviours that we don't expect.

The first one, and probably the guilty of the second is a problem related with threads. We have at the very top of the Application_start in global.asax the registry of all our less bundles. Something like that:

        protected void Application_Start()
        {
            ViewEngines.Engines.Clear();

            ViewEngines.Engines.Add(new RazorViewEngine());

            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

            //Our helper that register the bundles
            BundleConfig.RegisterBundles(BundleTable.Bundles);

The problem happens when we start the application and there are various requests to the application, it seems not to be thread-safe and is accessing multiple times to the Process method of TransformerBase which do the trasformation process thanks to LessTranslator class. We don't know why it isn't a thread-safe process and if it is possible to use BundleTransformer in a thread-safe way without forking your development.

You can see in this log in which way is doing this, the number at the start is the threadId and the next info after the arrow is the less file that is processing:

123 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 126 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 138 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 54 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 119 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 61 -> "asset-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 61 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 119 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 54 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 61 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 54 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 119 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 138 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 138 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 123 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 123 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 126 -> "result-/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" 126 -> "asset-/PrefWeb/Content/kendo/web/kendo.rtl.less" 61 -> "result-/PrefWeb/Content/kendo/web/kendo.rtl.less"

You can appreciate how it's doing repeatedly (5 times) the process of translating the less file "/PrefWeb/Content/kendo/web/kendo.common-bootstrap.less" in threads with numbers 123, 126, 138, 54 and 119.

The second problem is the main problem for us, because in some situations that we can reproduce but with no sense for us, the result of the translation of a less file returns a empty value, I mean, it returns an empty CompiledContent in the CompilationResult returned by the Compile method of LessCompiler, it sounds like a problem with the lessjs because the JSON object that returns seems to have this compiledCode property, but it's empty.

You can see this log and empty response of lessjs compilation result.

Bundle-Transformer-github-report-problem

Do you know how it's possible?

Thanks in advance.

Regards, José Carlos Ruiz.

Taritsyn commented 2 years ago

Hello, José!

Have you tried the following setting at the end of RegisterBundles method?

BundleTable.EnableOptimizations = true;

Which JS engine do you use?

jcruizsebastian commented 2 years ago

Hello again Andrey.

Mmm, interesting, the answer is... NO, because yes, we have the BundleTable.EnableOptimizations = true; sentence, but we don't have it at the end of the method, we have it at before adding any resource. Is it important this detail? We will do tests with that sentence at the end tomorrow morning.

About the JS engine, we are using JavaScriptEngineSwitcher.V8.

Regards.

jcruizsebastian commented 2 years ago

Hello.

We have done the test just now with the same result. It seems unimportant to be at the end of the method, at least in our case.

Regards.

Taritsyn commented 2 years ago

Hello, José!

In version 1.12.44, I tried to fix the possible cause of these errors.

jcruizsebastian commented 2 years ago

Hello again Andrey.

It seems not to be solved.

We were investigating a little more and the concurrency problem (or something like that) happens in the JS engine, because the given result is empty for the "compiledCode" json property, and it should'nt be like that.

So... may be the ticket must be created in the JavaScriptEngineSwitcher but I'm not sure if the responsabilitiy is in that code. We have coded a workaround forking your code and adding a retry strategy, so, now if the given property "compiledCode" is empty we'll retry. It's so ugly but it's functional for us.

We will keep investigating the problem.

Happy new year.

Taritsyn commented 2 years ago

Hello, José!

We were investigating a little more and the concurrency problem (or something like that) happens in the JS engine, because the given result is empty for the "compiledCode" json property, and it should'nt be like that.

Microsoft ClearScript.V8 is a thread-safe engine. More like a lack of memory problem.

Maybe you are using the Bundle Transformer in some non-standard way? Under normal conditions, when generating a bundle, all assets are processed in single thread.

jcruizsebastian commented 2 years ago

Hello Andrey.

No, we are using BundleTransformer following your docs and when we make multiple requests to the application while we restart the site, different threads start to generate the bundles and not only one. You can do the test, only need to change the web.config while you are pressing multiple times to F5 in a web app with Bundle transformer and you could see how multiple threads are trying to generate bundles.

Thank you!

Taritsyn commented 2 years ago

… and when we make multiple requests to the application while we restart the site, different threads start to generate the bundles and not only one.

If this is case, then problem is at the level of the Microsoft ASP.NET Web Optimization Framework.

jcruizsebastian commented 2 years ago

If this is case, then problem is at the level of the Microsoft ASP.NET Web Optimization Framework.

It seems a problem at level you think, but... What about empty "compiledCode"? IT's true that a lot of request don't must do multiple bundle transformations in different threads, but... every result must have a valid content if the input is the same, and it is not the case.

I will investigate deeply, but seems to be something strange at JavaScriptEngineSwitcher.V8 or at Microsoft.ClearScript.V8.V8ScriptEngine level.

Thanks a lot.

Taritsyn commented 2 years ago

Hello, José!

I will investigate deeply, but seems to be something strange at JavaScriptEngineSwitcher.V8 or at Microsoft.ClearScript.V8.V8ScriptEngine level.

You can quickly test your hypothesis by simply replacing a JS engine with another one.

jcruizsebastian commented 2 years ago

Ok, thanks for the advice. I'm going to change the JS engine, give me a while.

Thank you.

Taritsyn commented 2 years ago

I recommend starting with the JavaScriptEngineSwitcher.Jint module.

jcruizsebastian commented 2 years ago

Hello again.

I switched to Jint and to ChakraCore with interesting conclusions.

We did 20 tests in total as you can see in the picture.

Or the problem is in both engines (ChakraCore an V8) or the problem is in between... Maybe an error is bening hidden in the JavaScriptEngineSwitcher...

image

I'm going to try to link JavaScriptEngineSwitcher.V8 and Core in order to debug.

Some news in a while.

Again thanks for your support and patience.

Regards.

Taritsyn commented 2 years ago
  • Jint is like seven times slower than V8 and Jint. At least for our case. It don't show any concurrency error maybe because its slowness.

You probably installed a JavaScriptEngineSwitcher.Jint version 3.1.0. Version 3.16.0 Preview is only two times slower than V8. Also the JavaScriptEngineSwitcher.NiL module has similar characteristics.

jcruizsebastian commented 2 years ago

Hello Andrey!

We did the tests linking JavaScriptEngineSwitcher.V8 and JavaScriptEngineSwitcher.Core and we were surprised when finally get the same result, an empty "compiledCode" property so... We started to review some changes we did in the past in a VirtualProvider and seems to be there the problem, because when we remove this virtialProvider the problem disappear

So, I'm going to close the ticket, everything here is working nice.

Thank you very much for your time. Regards.