mozilla / pluotsorbet

[ARCHIVED] PluotSorbet is a J2ME-compatible virtual machine written in JavaScript.
GNU General Public License v2.0
238 stars 46 forks source link

Allow all MIDlets in a suite to run automatically, even when not the active Firefox OS app #1543

Closed TimAbraldes closed 9 years ago

TimAbraldes commented 9 years ago

We currently treat background and foreground MIDlets differently, allowing the former to run automatically but delaying the start of the latter until the j2me.js Firefox OS app has become active. We do this for a couple of reasons: 1) We don't want to consume more resources than necessary when in the background and 2) Notifications seem to be weirdly affected by whether the foreground MIDlet has been launched or not.

It seems like we can probably fix 2. As for 1, I tested with a complex app and found the following results:

BG MIDlet running, FG MIDlet prevented from running ~ 25MB
BG MIDlet running, FG MIDlet allowed to run ~ 29MB

So, a 16% increase in memory usage. That seems worthwhile for an app that the user finds useful enough that he/she allows it to run in the background in the first place, considering that the benefit of making this change is that the app will launch much-more-instantly.

We should be sure to pause/resume the FG MIDlet according to whether the Firefox OS app is active.

marco-c commented 9 years ago

Yesterday I tested pausing the FG MIDlet, but it didn't have any effect, the FG MIDlet was still responding and drawing when events occured. I'm going to test again today.

The memory consumption results seem too low compared to the usual results, how did you measure them?

TimAbraldes commented 9 years ago

Yesterday I tested pausing the FG MIDlet, but it didn't have any effect, the FG MIDlet was still responding and drawing when events occured.

Indeed. Calling pauseApp on individual MIDlets is intended to notify them that they will be paused. We'll have to actually do the work of freezing their threads after they respond to the pause notification. I think that should be possible by messing with the thread scheduler.

The memory consumption results seem too low compared to the usual results, how did you measure them?

I used the B2G script:

[path/to/b2g]/tools/get_about_memory.py --minimize

Admittedly, I'm not certain that the FG MIDlet completely started up during that testing. I'll post back when I have updated numbers.

marco-c commented 9 years ago

Indeed. Calling pauseApp on individual MIDlets is intended to notify them that they will be paused. We'll have to actually do the work of freezing their threads after they respond to the pause notification. I think that should be possible by messing with the thread scheduler.

Note that this is not required by pauseApp according to the documentation. The specification isn't strict on how pauseApp works, on old devices it used to stop all threads of a MIDlet, in newer devices it only asks the MIDlet to release resources and doesn't do anything else. I suspect the S40 is one of the devices where pauseApp is just a suggestion to the MIDlet, I'll confirm this with the Nokia simulator. We can still decide not to follow the S40 behavior, but we should double-check if it causes problems.

I think a better solution would be to call destroyApp, this way the BG MIDlets will call launchIEMIDlet when an event occurs.

marco-c commented 9 years ago

I used the B2G script:

We should measure with for i inseq 0 1000; do adb shell b2g-procrank | grep APPNAME; done as well.

marco-c commented 9 years ago

I've just tested on the Nokia simulator, pauseApp is never called.

TimAbraldes commented 9 years ago

I'm actually advocating an approach where launchIEMIDlet never gets called.

For the sake of conversation, let's name the following situations:

A) User has not launched app. BG MIDlet is running. B) User has launched app at some point but it is not currently the active app.

The approach I'm advocating involves two parts.

  1. Instead of targeting the BG MIDlet class for launch, target the correct FG MIDlet class. This means that the FG MIDlet will be the one specified in webapp manifests and that j2me.js will always launch the BG MIDlet when an FG MIDlet is launching.

    Pros: Warm startup time (startup time from situation A) will be as fast as startup time from situation B. Our MIDlet launching code will be more reliable because we are only responsible for launching the BG MIDlet (which we can always discover from information in the JAD) instead of launching the FG MIDlet (which we have to guess). This will work for more existing MIDlet suites than our current approach.

    Cons: In situation A, and only situation A, we will have higher memory consumption and cpu utilization, and "time" will move forward for the app (this could be important for apps that start up with a "please enter information within this time limit", or perhaps for games). If the 16% number is accurate, I think that the memory tradeoff is acceptable. We can address the other two issues with the approach outline below. NOTE: All of these issues are already present in situation B, so the approach outlined below merits investigation even without targeting the FG MIDlet.

  2. Pause all FG MIDlets when the app is not active. This involves sending a PAUSE_ALL_EVENT which causes all MIDlets to have their pauseApp functions called. We then receive notification via MIDletProxyList.notifySuspendAll0 that all MIDlets have completed preparing for pause, at which time we freeze all the threads of the FG MIDlets so that they don't consume cpu while the app is not active.

    Pros: In situation B, lower cpu utilization (and potentially lower memory utilization, depending on whether apps free resources in their pauseApp functions). This will also apply to situation A if we implement the approach above of always launching the correct FG MIDlet.

    Cons: Some extra work needed to pause/resume threads when the app becomes active/inactive.

Indeed. Calling pauseApp on individual MIDlets is intended to notify them that they will be paused. We'll have to actually do the work of freezing their threads after they respond to the pause notification. I think that should be possible by messing with the thread scheduler.

Note that this is not required by pauseApp according to the documentation. The specification isn't strict on how pauseApp works, on old devices it used to stop all threads of a MIDlet, in newer devices it only asks the MIDlet to release resources and doesn't do anything else. I suspect the S40 is one of the devices where pauseApp is just a suggestion to the MIDlet, I'll confirm this with the Nokia simulator. We can still decide not to follow the S40 behavior, but we should double-check if it causes problems.

I think we need to separate the concept of the pauseApp function (which is implemented by individual MIDlets) from what the framework does after calling that function. pauseApp is always "just a suggestion to the MIDlet" - the MIDlet isn't responsible for freezing its own threads, just for preparing itself in case it gets paused. AIUI, there's no way on an S40 to switch between active apps, so it makes sense that there's no situation in which a MIDlet would have its pauseApp function called. In Firefox OS, our j2me.js app can be active or inactive. So far we've chosen to allow our j2me.js apps to just continue running even if they are not the active Firefox OS app. That might be an acceptable tradeoff. If it's not, I advocate for using the pausing behavior described above.

I think a better solution would be to call destroyApp, this way the BG MIDlets will call launchIEMIDlet when an event occurs.

There's no equivalent to the PAUSE_ALL_EVENT for destroying all the MIDlets in a suite. If we want to destroy all the FG MIDlets when going inactive, we will have to write a bunch of Java code that mimics what happens when processing a PAUSE_ALL_EVENT but that destroys MIDlets instead. Additionally, and more importantly, destroying apps when going inactive means (I believe) that we'll lose all app state every time the user switches away from our app. When switching back to her/his favorite j2me.js app, the user will find that her/his game has restarted (or recent draft message has been lost, or timer has restarted, etc.)

marco-c commented 9 years ago

I'm actually advocating an approach where launchIEMIDlet never gets called.

This means we will never get useful notifications for some MIDlets (unless we find another way to get them).

AIUI, there's no way on an S40 to switch between active apps, so it makes sense that there's no situation in which a MIDlet would have its pauseApp function called. In Firefox OS, our j2me.js app can be active or inactive. So far we've chosen to allow our j2me.js apps to just continue running even if they are not the active Firefox OS app.

Yes, what I'm concerned about is that there might be problems since many MIDlets have been written for the Asha and don't have a concept of having all threads stopped. So, stopping all threads in the FG MIDlet could cause problems if the BG MIDlet is somehow communicating with the FG MIDlet.

(and potentially lower memory utilization, depending on whether apps free resources in their pauseApp functions)

Many MIDlets we're targeting only have a stub implementation for pauseApp, since it's not actually used in the platform they're targeting. destroyApp, instead, should actually release memory.

There's no equivalent to the PAUSE_ALL_EVENT for destroying all the MIDlets in a suite. If we want to destroy all the FG MIDlets when going inactive, we will have to write a bunch of Java code that mimics what happens when processing a PAUSE_ALL_EVENT but that destroys MIDlets instead.

We always have a single FG MIDlet, so we can simply send the DESTROY event to the foreground Isolate.

Additionally, and more importantly, destroying apps when going inactive means (I believe) that we'll lose all app state every time the user switches away from our app. When switching back to her/his favorite j2me.js app, the user will find that her/his game has restarted (or recent draft message has been lost, or timer has restarted, etc.)

Yes, I think we'd lose the app state. However, we'd be able to support a few other things that at the moment we can't support: sharing content from other apps and always receiving useful notifications for some MIDlets. In some kinds of apps, I think getting useful notifications is better than not losing the app's state, so we could also make this configurable. It'd probably also be better for memory consumption, because we'd effectively release the memory used by the FG MIDlet.

So, it's a tradeoff, destroyApp would allow useful notifications and sharing content but would make MIDlets lose their state. Pausing threads without destroying the MIDlet would not make MIDlet lose their state. If we can get good notifications while pausing threads, then the pausing threads approach would become much more palatable to me.

TimAbraldes commented 9 years ago

As of #1593 we always launch the FG MIDlet, even when we're not the active Firefox OS app. That's enough for this issue. Let's file separate issues for the other items discussed here (pausing MIDlets when not the active Firefox OS app, destroying the FG MIDlet when necessary to re-launch it with args)