kriskowal / q

A promise library for JavaScript
MIT License
14.93k stars 1.2k forks source link

Safari drops ticks for backgrounded tasks iOS 14 #849

Open idoodler opened 4 years ago

idoodler commented 4 years ago

We are developing an Cordova based iOS App. Everything works fine on iOS 13, but we experience issues on iOS 14.

Whenever the App is sent to background and retrieved again the app is unresponsive as we use Q.js for our ViewManager.

When debugging this via the Safari Web Inspector I noticed that var def = Q.defer(); def.promise.done(function(value) { alert(value); }); def.resolve("Done") works before but not after sending the app to the background. However native Javascript Promises work as expected new Promise(function(resolve, reject) { resolve("Done"); }).then(function(value) { alert(value) });

When inspecting the def variable using def.promise.inspect() the state is resolved and I can see the value, however none of the functions (resolve, reject, finally) are called, neither in the done, nor in the then function.

Anyone experience similar issues?

Edit: We are using Q#1.5.1

benjamingr commented 4 years ago

Try overriding Q.nextTick (set requestTick to Promise.resolve().then(fn) and see if it helps?

idoodler commented 4 years ago

@benjamingr No, I opened the Safari Webinspector and executed Q.nextTick = function(fn) { Promise.resolve().then(fn) } which did not change anything.

kriskowal commented 4 years ago

There were reports of Safari dropping timer events ages ago. I don’t know whether they went away, but your description of the problem seems to indicate a regression. Apart from looking for a more reliable primitive for timers as @benjamingr hints at, there is not a lot Q can do, and changing the underlying nextTick implementation requires very time consuming testing.

To wit, you can see some of that rigor over on https://github.com/kriskowal/asap, which I’ve not updated in ages.

It may be worthwhile to file an issue against Safari.

idoodler commented 4 years ago

I just tried to reproduce this issue with a fresh Cordova project, but I couldn't. The Promise stays alive during the application close and open. Any tips on how to debug this?

idoodler commented 4 years ago

This issue is still present on iOS 14 Beta 3.

idoodler commented 4 years ago

This issue is still present on iOS 14 Beta 4

idoodler commented 4 years ago

After revisiting this issue I decided to add Q.nextTick = function(fn) { Promise.resolve().then(fn) } in our Application code (not just adding it via the Webinspector). It looks like as if this solves the issue. Does this indicate some errors in our Promise handling?

mtamburro commented 4 years ago

The issue is still found on iOS 14 Beta 5 (and does not appear to affect iOS 13). Here's what I understand so far:

  1. The code path affected is the one for modern browsers where setImmediate is not defined
  2. In this path, channel.port2 posts a message, and channel.port1.onmessage responds
  3. This works, until the app goes in to the background

Here's what happens in the background:

  1. channel.port2 posts a message
  2. channel.port1 misses that message <-- I'll submit this to Apple as a difference in behavior in WKWebView on iOS 13 vs. 14
  3. this results in the flushing variable is never toggled from false to true... and no further promises are resolved
kriskowal commented 4 years ago

It might be helpful to reproduce and fix the problem in ASAP where we have CI that covers a broad spectrum of browsers. It would need some refreshing.

If we can address the issue in ASAP, it would give us some confidence of a solution that could be back-ported to Q. Alternately, we would need to back-port the testing setup in ASAP to give us the confidence we would need.

msmtamburro commented 4 years ago

FYI, I opened FB8533670 with Apple regarding a change in onmessage in WKWebView on iPadOS 14 as the App leaves the foreground. My related Apple Developer Forum post is here, for tracking.

Ended up making a temporary patch to q.js:

When the posted message is missed, the timeout calls flush().

msmtamburro commented 4 years ago

FYI, I opened FB8533670 with Apple regarding a change in onmessage in WKWebView on iPadOS 14 as the App leaves the foreground. My related Apple Developer Forum post is here, for tracking.

Ended up making a temporary patch to q.js:

  • creating a backup setTimeout right before posting a message
  • clearing that timeout in flush()

When the posted message is missed, the timeout calls flush().

Apple Responded, and says this new behavior is expected: "Promptly suspending when entering the background is correct behavior according to the norm of iOS."

kriskowal commented 4 years ago

Maybe you can push back on Apple here. Of course it’s expected to suspend in the background. It’s also expected for the message to be delivered when returning to the foreground, unless the page is reloaded entirely. Clearly, this is a case where some timers survive stasis.

msmtamburro commented 4 years ago

Thanks, Kris--I actually sent this yesterday:

I do want to point out that this difference in behavior means that a message posted as the app begins to background will get “lost” rather than “handled” when the app returns to the foreground.

We'll see if we get a response.