3

I have come across what seems to be a very, very strange issue in Mobile Safari.

We use the Flux dispatcher from Facebook, which has some very simple dispatching logic, roughly as follows (everything is synchronous):

Dispatcher.dispatch = function (payload) {
  if (this.isDispatching) {
    throw new Error("Cannot dispatch while dispatching")
  }

  this.isDispatching = true

  try {
    this.notifyAllObservers(payload)
  } finally {
    this.isDispatching = false
  }

}

This means that if one of the observers triggers another dispatch, you go into the Error("Cannot dispatch...") branch.

The problem is, we're ending up with this error, not because an observer is dispatching, but because a touchend event seems to pop up in the call stack out of nowhere if you hammer a particular button very fast.

By putting a debugger statement in the guard clause, above, we get the stack trace that caused the "double-dispatch", and it seems plain wrong.

The stack trace looks roughly like this (annotated and boring bits removed):

_settlePromise      - from an XHR
Dispatcher.dispatch - we dispatch a "searchResults" action
recalculate         - the UI needs to recalculate some layout (this includes an element.offsetLeft access)
touchendHandler     - where the hell did this come from??
Dispatcher.dispatch - BANG! (we haven't been able to set Dispatcher.isDispatching to false)

Reading the call stack from root to leaf, you can see every call in the code reflected on the next layer of the stack, so in _settlePromise we can see the call to Dispatcher.dispatch(...), and in Dispatcher.dispatch we can see ui.recalulate().

This suddenly is not true once we get to the recalculate layer. recalculate reads the offsetLeft of some UI element which causes a forced synchronous layout. The next call in the stack is a touch event handler, which I just can't understand. Surely any UI events should happen on the next tick?

To me, this seems like a browser problem. Maybe I don't understand the full intricacies of the event loop, but it seems odd that an unrelated call could suddenly appear in the stack. This only happens on Mobile Safari as far as we can tell.

Any thoughts? Does this call stack make sense to anyone?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tom
  • 1,043
  • 8
  • 16
  • 1
    You might want to have a look at [Is javascript guaranteed to be single-threaded?](http://stackoverflow.com/q/2734025/1048572) – Bergi Jan 19 '16 at 11:08
  • Just to be clear, this behaviour is only on Mobile Safari? – Dominic Jan 19 '16 at 12:01
  • Dominic, we are unable to reproduce on any other browser. But that's not proof that it can't happen. It could be connected to the time taken to perform the synchronous layout. – Tom Jan 19 '16 at 12:07
  • Bergi, just skimmed that post and it seems to be relevant. I'll give it a proper read in a sec, thanks. – Tom Jan 19 '16 at 12:07
  • I have the exact same problem, touch event handlers just below a xhr.send or a img.setAttribute in the call stack. Have you find anything about this? – keneco Mar 31 '16 at 15:47
  • I'm also experiencing the problem, this time in angular's $digest cycle. After xhr call during the calculations in cookie handler, call stack suddenly jumps to touchend handler and triggers another $apply, which results in error. I can't reproduce that locally, though i see that in a huge pile of logs. Around 1% of traffic experiences that problem at least once, and it happens ONLY on iOS devices (versions 6 to 10 preview, data covers a few months). – Frizi Jul 11 '16 at 09:06

0 Answers0