microsoft / Chakra-Samples

Repository for Chakra JavaScript engine related samples.
MIT License
216 stars 84 forks source link

JavaScriptProjectionEnqueueCallback #16

Closed SrinivasGourarum closed 8 years ago

SrinivasGourarum commented 8 years ago

I am getting an error when trying to use "JsSetProjectionEnqueueCallback". Please find the sample app @ https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!111&authkey=!ALhEoAFR-tPVGC4&ithint=file%2czip

I am trying to execute the following script: var uri = new Windows.Foundation.Uri("http://google.com"); var httpClient = new Windows.Web.Http.HttpClient();

httpClient.getStringAsync(uri).done(function () { // do something with the string content " Debug.writeln("finished"); }, onError); function onError(reason) { // error handling Debug.writeln( "onError"); }

Could you please help me with this?

liminzhu commented 8 years ago

Are you working on an UWP app? If that's the case, then you don't need to call JsSetProjectionEnqueueCallback. Setting up is done for you under the hood and JsProjectWinrtNamespace would just work. In the sample you sent, deleting the JsSetProjectionEnqueueCallback line would make everything work :smile:.

SrinivasGourarum commented 8 years ago

Yes we are working with UWP app.

  1. Sample app

By commenting the JsSetProjectionEnqueueCallback line, we are still getting "undefined" as the result. It is not hitting the the success or error code blocks. I used the script from https://msdn.microsoft.com/en-us/library/dn249552(v=vs.94).aspx in which enque callback api is used.

  1. Our application

In our application though without setting the projection enqueue callback we are getting an exception with the following message: "The application called an interface that was marshalled for a different thread."

We are forcing the runtime to always run on a dedicated thread. Does the HttpClient api needs to run on the main thread which could be the reason for the exception?

JsSetProjectionEnqueueCallback if used, there is no exception but still not reaching the success/error code blocks in the script.

SrinivasGourarum commented 8 years ago

a. Sample app

I missed looking in the output window by debugging Script. It is working. Sorry about that.

b. Our app

When I do not create a dedicated thread for the runtime (if script and ui run on the main thread) , it works. We will be needing multiple js threads to be executing parallely and the host needs to call on the thread with the appropriate runtime when needed.

Any thoughts on how to make this work?

SrinivasGourarum commented 8 years ago

I have created another app which reproduces the issue we are facing @ https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!112&authkey=!AOtCpU2O2jT_4NI&ithint=file%2czip

When the button "Execute" is clicked, the run time is created in another task and loads the file "script11.js". The application crashes with the message "The application called an interface that was marshalled for a different thread." The api "getStringAsync" is running only when the run time is created on the main thread.

liminzhu commented 8 years ago

Sorry for the late reply and for the wrong answer before. To correct myself, you do need to do JsSetProjectionEnqueueCallback if you are passing delegates around different threads. I'm not sure why the call fails but I'm investigating this with my team and I will get back to you on this.

SrinivasGourarum commented 8 years ago

A couple more questions on projection:

  1. We have a requirement where we want to be able to project class wise instead of the whole namespace. Is this possible? If not, is it something that is doable by tweaking ChakraCore when it becomes open source?
  2. Is it possible to show xaml controls by using projection? There was an exception stating the "type is not constructible" when I tried to create a button control.
liminzhu commented 8 years ago
  1. You can project winrt classes using JsInspectableToObject. This may be a good example.
  2. You should be able to manipulate xaml controls, though some of the types, such as Button, are not directly constructible in JavaScript. The current recommended way is to construct the object in xaml/c#, and then call JsInspectableToObject to project that object into JS, after which you can manipulate your xaml objects. This is a new feature for us, so if you encounter any problem/weirdness, please report it to us and we'll take a look.
SrinivasGourarum commented 8 years ago

Thanks Limin. I will try our the suggestions and get back in case of issues.

Just a gentle reminder. How about the original issue reported in this thread?

liminzhu commented 8 years ago

Sorry @SrinivasGourarum we were a little bit swamped with our open-sourcing effort. @yongqu is going to take a look at the original issue.

Yongqu commented 8 years ago

Sorry for the delay. Somehow I can't access the repro in the onedrive. Is it still there?

SrinivasGourarum commented 8 years ago

It is still there. Please try again. https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!112&authkey=!ADqlwPo_YRabc4M&ithint=file%2czip

Please let me know if you still face issues. Please let me know your email. I will mail it if needed.

Yongqu commented 8 years ago

Thank I can access it now. however the project is complaining that I don't have a phone connected. Is that a necessary step? thx Severity Code Description Project File Line Suppression State Error Error : DEP6100 : The following unexpected error occurred during bootstrapping stage 'Connecting to the device '30F105C9-681E-420b-A277-7C086EAD8A4E'.': DeviceException - Deployment failed because no Windows Phone was detected. Make sure a phone is connected and powered on. JsRT_UWP_Sample

SrinivasGourarum commented 8 years ago

A phone need not be connected. Please run it on "Local Machine" with"x86". When the window opens, click the "Execute" button.

SrinivasGourarum commented 8 years ago

Hi @Yongqu,

Is the issue reproducible at your end?

Regards, Srinivas.

Yongqu commented 8 years ago

I'm so sorry for the long delay. Was busy with some deadline earlier and one thing after the other the investigation got postponed too far out. There is this web page https://msdn.microsoft.com/en-us/library/dn169426.aspx#bkmk_CoreDispatcher describing how to send the delegate calls back to JavaScript thread. In c# code like this you probably don't need to call JsSetProjectionEnqueueCallback to do the manual thread switching and let the CoreDispatcher do the work. I hacked the code in onclick and it seems to work: var window = Windows.UI.Core.CoreWindow.GetForCurrentThread(); var m_dispatcher = window.Dispatcher; Task.Run(() => { m_dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, new Windows.UI.Core.DispatchedHandler(() => { string script2Execute1 = File.ReadAllText("script11.js"); string result1 = host.runScript(script2Execute); })); Please ignore the Visual Studio IDE's warning about async.await as we are trying to runasync already.

Alternatively you might be able to do the same thing in c#/c++ to implement the JsSetProjectionEnqueueCallback, basically MakeAndInitialize a coredispatcher and dispatch from there. Probably overkill in your scenario: http://stackoverflow.com/questions/28246848/what-is-dispatchedhandler-and-how-to-use-it-in-abi

Again I apologize for the long delay.

SrinivasGourarum commented 8 years ago

Thanks @Yongqu for the response. I think we are getting an error code while calling "JsRunScript" as "NoCurrentContext". You will see that when we debug in Script mode, the callbacks for either success or error are not hitting.

Could you please check?

Yongqu commented 8 years ago

I didn't see the error in my try. Can you try to call JsSetCurrentContext before calling JsRunScript?

SrinivasGourarum commented 8 years ago

I have uploaded the sample @ https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!117&authkey=!AFwMEBwFoUJHlog&ithint=file%2czip

It is indeed working with the way you suggested. However, we need to get this to work with JsSetProjectionEnqueueCallback. Please click on the button "Execute2" for this scenario. I am still getting the same error.

What is the IntPtr argument that the api JsSetProjectionEnqueueCallback is expecting? Please let me know if I am missing something.

SrinivasGourarum commented 8 years ago

The error code as "NoCurrentContext" was because I had intialized the context on a different thread. I have corrected the sample @ https://onedrive.live.com/redir?resid=9C4512A9CBBBEB98!118&authkey=!AB9FS0O619n4FL0&ithint=file%2czip

We are still not getting the expected output. Strangely, upon clicking the "Execute2" for the second time, it is working. What is the IntPtr argument that the api JsSetProjectionEnqueueCallback is expecting?

Yongqu commented 8 years ago

Yeah I fixed the NoCurrentContext issue. There are some other issues: JsSetProjectionEnqueueCallback needs to be called after projection were started, including JsProjectWinRTNamespace and JsObjectToInspectable. We should update the documentation. The callbackState is just a cookie for you to pass back in the JsProjectionEnqueueCallback (last parameter). We have some code assumption that is not true in c# await scenario. One of them is that we are assuming that JsSetProjectionQueueCallback is not needed in STA/ASTA, but here we do get called from a different thread while the main thread is STA. Curious why the coreDispatcher doesn't work for you?

SrinivasGourarum commented 8 years ago

We do need the scenario with JsSetProjectionQueueCallback because our Js file may contain a combination of host implemented as well as projected winrt apis. we have a dedicated thread for JS (a non UI thread) which apparently is unable to handle all projected winrt apis. So unless there is a callback natively, we cannot execute those apis on the UI thread.

Yongqu commented 8 years ago

Sorry your call order is correct (SetProjectionCallback first). I forgot that. Thanks for the feedback and I can see why now. I saw a couple of assumptions in the code that are not applicable in your scenario here. I will be investigating the solution but unfortunately I can't promise the timeline due to the windows release schedule constraints.

Yongqu commented 8 years ago

Forgot your question about the second execution: it works because sometimes the underline delegate native component (http winrt component here) could decide to call the delegate synchronously from the main thread if they have the result already. This is what happen in the sequential getStringAsync call just directly comes back in the main thread.

Yongqu commented 8 years ago

You can use this code to setup the engine. You need to run all the javascript related code in the UI thread, including setup the engine. Once this is done, you don't really need to call JsSetProjectionHost. private void Execute2_Click(object sender, RoutedEventArgs e) { var window = Windows.UI.Core.CoreWindow.GetForCurrentThread(); m_dispatcher = window.Dispatcher; Task.Run(() => { m_dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, new Windows.UI.Core.DispatchedHandler(() => { host = new ChakraHost.ChakraHost(); host.init(); string script2Execute = File.ReadAllText("script11.js"); string result1 = host.runScript(script2Execute); })); }); }

SrinivasGourarum commented 8 years ago

Thanks @Yongqu for the detailed explanation. Creating running the javascript on the UI thread will not be feasible as it will impact performance. So we are expecting the fix which involves working with JsSetProjectionQueueCallback.

Yongqu commented 8 years ago

The way WinRT projection works, all the JavaScript calls need to go back to the original thread the scriptengine/JSRT context was created. So if you don't want to run the code in UI thread, you need to make sure all the code back to that particular original thread. You will not be able to use coreDispatcher, and you might need to implement something similar to coreDispatcher that route all the calls, including those inside Task.Run() back to the original thread. The JsSetProjectionEnqueueCallback implementation would be using your dispatcher similar to the coreDispatcher code: myDispatcher.RunbackToOriginalThread(() => { jsCallback(jsContext); });

SrinivasGourarum commented 8 years ago

Our application always runs the JS on a dedicated thread which is a non UI thread. So all the javascript calls run on this same thread on which the engine/context is created. In the sample, we have used Task.Run() ( as the thread which is running the JS) to simulate our scenario where this issue is reproducible. The implementation for calling the jsCallback from inside the projection callback on the UI thread is already in place in the sample. We still see that this api (httpClient) is not working as it would when everything were to be running in a single thread. I hope I am able to drive home the point.

Yongqu commented 8 years ago

how do you send the Javascript code to run on your thread in your code which was simulated by Task.Run? I think you just need to implement the same thing in JsSetProjectionEnqueueCallback. JsSetProjectionEnqueueCallback(()=>{ myDispatcher.RunbackToOriginalThread(() => { jsCallback(jsContext); });}); where myDispatcher is however your implementation. Alternatively you can pack that jsCallback/jsContext to some structure and put it to a queue, and SetEvent/WaitForSingleObject will pick it up from the target thread and execute that.

SrinivasGourarum commented 8 years ago

Now, I understand what you have been saying. So when I get the callback for JsSetProjectionEnqueueCallback, I need to make sure that jscallback is executed on the same thread on which the engine was created. I have verified that the httpClient api is working.

However, I have tried running the following code in script and it does not seem to work.

var messageDialog = new Windows.UI.Popups.MessageDialog("message Dialog"); messageDialog.showAsync();

I have created another sample to which ensures that the jscallback is executed on the same thread which on which engine was created @ https://1drv.ms/u/s!Apjru8upEkWcdxRMzOlXk9Ix1jc

The dialog shows up if I happen to run all executions on UI thread (by commenting JsSetProjectionEnqueueCallback and clicking "Execute" button).

However, by running the js on different thread (clicking "Execute2" button), the dialog is not shown and the enqueue callback is not triggered.

Am I missing something here?

Yongqu commented 8 years ago

Glad one big issue is behind us :) The UI failure is likely threading issue again, though in the realm of actual WinRT component. I assume UI calls have to be run in UI thread, and I assume it'll do nothing if it's called from your own JS thread. Now you need to channel the call onto the UI thread. Maybe we'll need to have a separate JSRT context running in the UI thread to handle UI related work, and have a coreDispatcher dispatch UI related work over?

SrinivasGourarum commented 8 years ago

Thanks @Yongqu. We will definitely be able to create another context running on the UI thread in order to execute the apis which need UI thread.

You may close the issue if this is the expected behavior.