Page MenuHomePhabricator

When chunked uploading very large files, upload progress bar gets stuck with errors in the console about "too much recursion" / "call stack size exceeded" (but upload succeeds?)
Closed, ResolvedPublic

Description

Trying to uload a 1.1 GB video file I am getting this error message in the Firefox console:

mediawiki.jqueryMsg: mwe-upwiz-uploading: too much recursion

And the upload stucks.

Trying the same using Chrome it works well.

Event Timeline

Raymond created this task.Mar 22 2016, 2:22 PM
Restricted Application added a project: Multimedia. · View Herald TranscriptMar 22 2016, 2:22 PM
Restricted Application added subscribers: Steinsplitter, Aklapper. · View Herald Transcript

I can't reproduce using Firefox 46.0b2 (32-bit) on Windows 7 (64-bit). Does it always happen? Does it happen for non-video files? Can I get a copy of that file (have you managed to upload it with Chrome?), so that I could try with it too?

Restricted Application added a subscriber: Matanya. · View Herald TranscriptMar 22 2016, 7:56 PM

@matmarex Uploaded via Chrome was successfull: https://commons.wikimedia.org/wiki/File:Kalkberg_360%C2%B0-Rundumblick_HQ.webm

Upload failed with FF 45.0.1 / Win 8.1 (64 bit)

I am using the UploadWizard very rarely since I am uploading my images from Lightroom directly.

I tried that file and it seems to be uploading just fine. I don't really feel like waiting till the whole file goes through (my connections has pretty low upload speed), your error was right when you started and not later, right?

I don't really see how UploadWizard could do something very recursive that would end up in jquerymsg code. If it still happens for you, can you copy-paste the full console output, including any error backtrace? (That is, if you see something like , click the triangle icon to expand it first.)

I tried it again today after a restart of Firefox. Same problem after uploading ~ half of the video. See Screenshot

Complete console output:

indexOf()
 self-hosted:77
.inArray()
 load.php:5
.buildFragment()
 load.php:82
.domManip()
 load.php:86
.append()
 load.php:83
.text/<()
 load.php:83
jQuery.access()
 load.php:57
.text()
 load.php:83
getFailableParserFn/<()
 load.php:93
mw.jqueryMsg.getPlugin/<()
 load.php:94
mw.UploadWizardUploadInterface.prototype.setStatus()
 load.php:137
mw.UploadWizardUploadInterface.prototype.showTransportProgress()
 load.php:137
MWUploadWizardUploadInterface/<()
 load.php:136
jQuery.event.dispatch()
 load.php:65
jQuery.event.add/elemData.handle()
 load.php:60
jQuery.event.trigger()
 load.php:64
.trigger/<()
 load.php:75
.each()
 load.php:5
jQuery.prototype.each()
 load.php:2
.trigger()
 load.php:75
mw.UploadWizardUpload.prototype.setTransportProgress()
 load.php:94
mw.ApiUploadFormDataHandler.prototype.start/</<()
 load.php:378
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/promise.then/</</<()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
jQuery.Callbacks/self.fireWith()
 load.php:46
.Deferred/</deferred[tuple[0]]()
 load.php:47
jQuery.Callbacks/fire()
 load.php:45
 load.php:5:701
matmarex closed this task as Resolved.Apr 7 2016, 9:13 AM

Change a8a5966bf6aa76cb137b4b7cda4f3aa2635be991 has shaken up the problematic code here, and as a side effect is has less direct recursion, which will hopefully make Firefox happier. @Raymond, please reopen if you run into this again.

matmarex reopened this task as Open.Apr 15 2016, 1:54 PM

On further though, I don't think this is fixed. I ran into a very similar problem when trying to reproduce T132300. For me, the progress bar stopped updating after uploading 27 chunks (135 MB) of the 1.6 GB file, using Opera (like Chrome); I also didn't get anything interesting in the console. However, the file continued to be uploaded, only the progress notifications were broken.

@Raymond, if you see this again, can you check if the file upload is continuing (e.g. by looking at the "Networking" tab of Windows Task Manager) and if so, try waiting for it to complete?

@matmarex Sure I will ckeck with my next upload of a bigger file.

matmarex renamed this task from mediawiki.jqueryMsg: mwe-upwiz-uploading: too much recursion to When chunked uploading very large files, upload progress bar gets stuck with errors in the console about "too much recursion" / "call stack size exceeded" (but upload succeeds?).Apr 15 2016, 2:54 PM
matmarex claimed this task.
matmarex triaged this task as High priority.Apr 15 2016, 3:42 PM

Here's a reduced version of the code in UW causing this issue: https://jsfiddle.net/78f0qm7c/3/ (the real thing is in mw.FormDataTransport.prototype.uploadChunk).

I'm not sure if this could be considered a jQuery bug, or if they'd say it's a case of "Doctor, it hurts when I do this. Then don't do that." if I reported it. Nesting 1000 promises is not a usual use case, and our code could be rewritten not to do it (it'll take some refactoring though).

(This appears to be fixed in jQuery 3.0 alpha, because promises are now resolved/rejected/notified asynchronously, so there's no deep recursion. It takes a couple second real-time for the notification to arrive with a long promise chain, though.)

Krinkle removed a subscriber: Krinkle.Apr 16 2016, 12:34 AM
Krinkle added a subscriber: Krinkle.

The jsfiddle looks like a grey area too me. Not sure upstream would support that as a valid use case. But if that's what UploadWizard is doing in internally, seems like we should change that.

Why does the promise depth relate to duration or size of the upload? Does it really add additional tasks to execute, or is it using/abusing promises for some other purpose?

Why does the promise depth relate to duration or size of the upload? Does it really add additional tasks to execute, or is it using/abusing promises for some other purpose?

It's legit. Large files are uploaded in "chunks" of 5 MB each, so that any transient connection errors or malicious server pixies only require us to re-send the 5 MB chunk, and not the whole huge file. Each chunk is sent in a separate API request, which has a separate promise, and which is started from the 'then' callback of the previous chunk. Look at mw.FormDataTransport.prototype.uploadChunk() to see the actual code.

But if that's what UploadWizard is doing in internally, seems like we should change that.

No doubt it can be rewritten to avoid this, and I'll be doing this regardless of what we think of the jQuery behavior.

@matmarex I tested today a ~ 630 MB .webm file. Still the same error on console (too much recursion) but this time I investigaed the networking tab of Firefox 45.

  1. Some dozen POST requests for the chunks
  2. The last POST request shows in the answer section of the header:

upload:Object

  • result: "Success"
  • stage: "assembling"
  • filekey: "13z7i7pwv7dg.fguf8v.293.webm"

imageinfo:Object

  • html: "<p>Eine Datei dieses Namens ist bereits vorhanden. Bitte prüfe <strong><a href="/w/index.php?title=File:20160426083207!chunkedupload_e2f186dfeff0.webm&amp;action=edit&amp;redlink=1" class="new" title="File:20160426083207!chunkedupload e2f186dfeff0.webm (page does not exist)">File:20160426083207!chunkedupload e2f186dfeff0.webm</a></strong>, sofern du dir nicht sicher bist, ob du sie ändern möchtest.</p><div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/w/index.php?title=Special:Upload&amp;wpDestFile=20160426083207!chunkedupload_e2f186dfeff0.webm" class="new" title="File:20160426083207!chunkedupload e2f186dfeff0.webm">File:20160426083207!chunkedupload e2f186dfeff0.webm</a> <div class="thumbcaption"></div></div></div>"
  • canonicaltitle: "File:20160426083207!chunkedupload e2f186dfeff0.webm"
  • url: "http://commons.wikimedia.org/wiki/Special:UploadStash/file/13z7i7pwv7dg.fguf8v.293.webm"
  • descritionurl: "http://commons.wikimedia.org/wiki/Special:UploadStash/file/13z7i7pwv7dg.fguf8v.293.webm"
  • sha1: "0835e773af3a4c8830dc5f03b5adda131fd22627"

(the German error message in the html line is https://translatewiki.net/wiki/MediaWiki:Fileexists/en )

I gave the process ~ 1 hour but nothing more happens. Upload is unfinished :-(

Krinkle removed a subscriber: Krinkle.May 9 2016, 4:20 PM
matmarex lowered the priority of this task from High to Normal.Jun 15 2016, 5:25 PM

Change 297347 had a related patch set uploaded (by Bartosz Dziewoński):
mw.FormDataTransport: Work around call stack limits for chunked uploads

https://gerrit.wikimedia.org/r/297347

Change 297347 merged by jenkins-bot:
mw.FormDataTransport: Work around call stack limits for chunked uploads

https://gerrit.wikimedia.org/r/297347

matmarex closed this task as Resolved.Jul 5 2016, 7:43 PM
matmarex removed a project: Patch-For-Review.

This should no longer be a problem after 1.28.0-wmf.9 deployment to Commons (which should happen tomorrow evening: https://wikitech.wikimedia.org/wiki/Deployments#deploycal-item-20160706T1900). @Raymond, it would be great if you could at some point verify that it has been resolved.

@matmarex Uploading a 740 MB .webm file works well today. Thanks you for fixing it :-)