jxcore / jxcore-cordova

JXcore / Node.JS plugin for Apache Cordova / PhoneGap
Other
233 stars 68 forks source link

restarting jxcore-cordova #133

Open mohlsen opened 8 years ago

mohlsen commented 8 years ago

Do to issues where iOS pulls ports when apps are placed into the background, it would be a desirable feature to be able to restart the jxcore process when it is running under cordova. We are having problems resuming network requests (outbound from jxcore) after the app resumes.

If jxcore-cordova could be stopped / started / restarted, it seems like apps using could catch the iOS events indicating that it is entering / leaving the background, and then shutdown jxcore. Alternatively or in addition, when the app is resumed, it could start / restart it.

Is seems like right now from the docs and code, jxcore starts up automatically, and calling loadmainfile(...) is the only real way to start your app. Could loadmainfile(...) be called again to restart the app?

We using Thali in our app, they have an issue to investigate this (https://github.com/thaliproject/Thali_CordovaPlugin/issues/408), but our app also makes network requests outside of Thali.

obastemur commented 8 years ago

@mohlsen We had used jxcore multi tasking for that purpose on our app for App store. Once JS land receives an event that the app is going background, we pull the plug of task and everything with it.

The right approach here can be that JXcore is giving an option to free all the open sockets / ports per request.

We wanted to implement that while ago but couldn't prioritize yet.

mohlsen commented 8 years ago

ok. we'll take a look at https://github.com/jxcore/wifi-shoplist-app to see if that will work around it for us. thanks

yaronyg commented 8 years ago

I was talking with @obastemur and I thought I would clarify what is going on. The issue is that when an iOS app is moved to suspended mode the OS apparently kills all the app's ports in a fairly odd way. Rather than just closing the ports it leaves them in a ghost state where they appear to the app to be open but in fact are not.

So, for example, imagine that you have a server listening on port X. The app goes into suspended state and port X's connection is killed by the OS. But the app isn't told about this since it apparently happens after the app was suspended. In our case when the app comes back and runs JXcore the server we had listening on port X thinks its still listening on port X since it was never notified otherwise.

In the simplest case we could just call close on the server and then try to grab port X again. But there are at least two serious problems here.

The first problem is that another app could have grabbed port X in the meantime. There is a background state in iOS where apps can apparently keep ports for some time before they are suspended. So the point is that we can never rely on hard coded ports.

The second problem is more dire, it turns out that LibUV really doesn't like this situation and in practice this means that at best we might never be able to recover the memory associated with the ports that were active when the app was suspended and at worst the whole app can crash.

For apps like wifi-shoplist that only work in the foreground the solution is that as soon the pause event fires just close down everything. You can see wifi-shoplist initiate this behavior here.

Things get more complicated though for apps that want to run in the background. At a minimum apps that run on Android and iOS need to understand the system's very different behavior and not go killing everything when Android goes into the background. In iOS in theory one could ignore the pause event and instead write native code to try and register the methods discussed here and use that to keep the app alive for awhile. The trick though is to make sure that one pays attention to the last warnings and closes down everything and cleans up in time. This is critical because as explained here there is no warning before going into suspended state and at that point it's too late to prevent memory leaks/crashes.

Zeipt commented 8 years ago

So, there are no any chances to notify app about soon killing by the OS? I use background plugin and watch for free mem periodically, and when it goes under my soft limit I use that plugin to force the app to go foreground. This helps to save app and its sockets for hours! But it would be more easy, if there would be any more determined notify from OS.. (My app is for Android) And second question is are there any chances to have ability to force GC from within app? Because I see that mem always ups, and even my trick cant help against killing after some hours, and I see that only some other apps in system or system itself inits GC or just get back mem for themselfs..

yaronyg commented 8 years ago

For whatever it's worth we are planning on using the standard onPause notification to tell us when to clean things up. We then set everything up again onResume. Keep in mind that iOS really doesn't have anything like Android's background or service mode. Once your app is out of foreground it's mostly dead (there are some exceptions but they are convoluted).

obastemur commented 8 years ago

@Zeipt this is iOS only. As for GC, you might use jxcore.tasks and unload thread instead of killing the process.

Zeipt commented 8 years ago

@yaronyg yes, I know abt iOS limits, so I just understood that there cant be anything really secure and private on this OS, So I just decided to not make my app available for iOS. @obastemur no, I think you disunderstood my question. I dont need to kill my app. I want to call GC from app and free mem, may be this will help my app to work longer.. As in native nodejs, with --expose-gc

obastemur commented 8 years ago

@Zeipt you may try jxcore.tasks.forceGC it doesn't require --expose-gc

BTW; (ping / close / recreate ) approach below works for iOS. So @yaronyg you may not have to force kill the sockets etc. as soon as you receive pause on iOS

var server;
var http = require('http');
var request = require('request');

function MyServer() {
  server = http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write('<p style="color:#006699">Visit JS/jxcore/app.js to update the code</p><hr/>');
    res.end('<div>' + base_html.replace(/\n/g, "<br/>") + '</div>');
  }).listen(3000);
  console.log('Server running at (port:3000) ' + arrIP[1]);
}

MyServer();

setInterval(function(){

var request = require('request');
request('http://' + arrIP[1] + ":3000/", function (error, response, body) {
  if (!error && response.statusCode == 200) {
    console.log(body) // Show the HTML for the Google homepage. 
  } else {
      console.log("ERRORRR!!!", error, response, body);
    server.close();
    MyServer();
  }
})

}, 2000);

I believe this can be automated internally for all the sockets without adding extra request etc. layer.

yaronyg commented 8 years ago

@obastemur now I'm confused. I thought you said that if we didn't fully close the server socket before we were put to sleep then when we woke up we would, at best, lose memory and at worst crash the app. The code above doesn't have any knowledge that we are going into the background and wouldn't close the sockets before we did. Also I thought there was a race condition where, while we are in the background, someone else could grab port 3000 and when we come back that app might itself still be in background but not suspended state and so we wouldn't be able to get the port ourselves?

obastemur commented 8 years ago

I thought you said that if we didn't fully close the server socket before we were put to sleep then when we woke up we would, at best, lose memory and at worst crash the app.

I'm still behind that with a small difference. Sample I've shared doesn't leave any client socket behind (it calls end)

As for the race condition, there is no such a thing for iOS. Once the other app is on background (so your app can be on foreground) you can reallocate and use the port (as the sample does)

It's indeed different comparing to what we have talked over skype but as for iOS 9.2, above sample works without any issue.

Zeipt commented 8 years ago

@obastemur great! Thanx! It works!

yaronyg commented 8 years ago

Sorry for being thicker than usual but this is really important and will cause changes to our plans so I want to make sure I'm following along correctly.

Question 1 - Race Condition time 0 - App A goes into the foreground and starts a server on port 3000 time 1 - App A goes into the background and is then suspended time 2 - App B comes into the foreground and starts a server on port 3000, mind you App A still had this port when it was suspended.

Will App B's attempt to grab port 3000 work?

Assuming it does work then

time 3 - App B goes into the background but is not suspended, so it is still listening on port 3000 time 4 - App A comes into the foreground, App B hasn't been suspended yet and now App A tries to grab port 3000, what happens?

Question 2 - Memory leaks and crashes

Time 0 - App A grabs port 3000 and has incoming connections to it Time 1 - App A goes into background but keeps open port 3000 and connections Time 2 - App A is suspended Time 3 - App A is back in the foreground

I take it the existing connections will be dead and the port will be lost. But you are now saying that there won't be a memory leak or crash? We just need to realize we came out of background and restart the server? The difference from before being that we can now do this after we come back from suspended rather than before?

obastemur commented 8 years ago

Will App B's attempt to grab port 3000 work?

Yes.

time 3 - App B goes into the background but is not suspended, so it is still listening on port 3000 time 4 - App A comes into the foreground, App B hasn't been suspended yet and now App A tries to grab port 3000, what happens?

I guess this may happen on any OS for any networking app?

I take it the existing connections will be dead and the port will be lost. But you are now saying that there won't be a memory leak or crash?

When there are active connections, the behavior is undefined. So we might expect everything. The scenario I'd shared above was based on no open and dependent!! connections and I'm closing the one I have.

We just need to realize we came out of background and restart the server? The difference from before being that we can now do this after we come back from suspended rather than before?

As I tried to explain, this all depends to the connections left open.

yaronyg commented 8 years ago

O.k. so it sounds like if I have a server that is listening on a port and if that server has no active incoming connections then it's o.k. to leave it listening while it is suspended and that no memory will be lost and no crashes will ensue. Instead we just have to close the server when we come back into the foreground and restart it?

obastemur commented 8 years ago

Instead we just have to close the server when we come back into the foreground and restart it?

Much better than dumping the whole thread.

yaronyg commented 8 years ago

Ahh and now we get to the crux of my confusion. I never thought we were supposed to dump the whole thread. I thought we only needed to close down all the servers before we went into suspend on iOS. I didn't want to dump the thread because if I remember correctly each thread requires a full spidermonkey context so we would have one full context for the parent thread (that we aren't using) and then another full context for the child thread (that we are using).

Although I still suspect that every once in awhile we will need to drop all of JXcore and restart it en mass so we can clean up everything. But we'll have to handle that at the native layer.