Last modified: 2014-08-28 23:33:36 UTC

Wikimedia Bugzilla is closed!

Wikimedia migrated from Bugzilla to Phabricator. Bug reports are handled in Wikimedia Phabricator.
This static website is read-only and for historical purposes. It is not possible to log in and except for displaying bug reports and their history, links might be broken. See T31784, the corresponding Phabricator task for complete and up-to-date bug report information.
Bug 29784 - ResourceLoader: JsMinPlus is too memory hungry (memory leaks?)
ResourceLoader: JsMinPlus is too memory hungry (memory leaks?)
Status: REOPENED
Product: MediaWiki
Classification: Unclassified
ResourceLoader (Other open bugs)
1.22.0
All All
: Normal normal with 1 vote (vote)
: ---
Assigned To: Nobody - You can work on this!
: performance
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2011-07-09 08:02 UTC by Niklas Laxström
Modified: 2014-08-28 23:33 UTC (History)
14 users (show)

See Also:
Web browser: ---
Mobile Platform: ---
Assignee Huggle Beta Tester: ---


Attachments

Description Niklas Laxström 2011-07-09 08:02:29 UTC
Always the same error message:
PHP Fatal error:  Allowed memory size of 146800640 bytes exhausted (tried to allocate 629392 bytes) in /www/w/includes/libs/jsminplus.php on line 1680
Comment 1 Brion Vibber 2011-07-11 21:11:43 UTC
As a workaround for now, disable the parsing checks by setting $wgResourceLoaderValidateJS = false;


Looks like the culprit is the bundled jquery-ui in Translate extension; I can get it to fail very reliably on that file setting the overall memory limit to 53M or below when parsing it standalone in a maint script (jsparse.php added in r91908):


brion@stormcloud:/var/www/trunk$ php maintenance/jsparse.php --memory-limit=54M extensions/Translate/js/jquery-ui-1.7.2.custom.min.js 
extensions/Translate/js/jquery-ui-1.7.2.custom.min.js OK

brion@stormcloud:/var/www/trunk$ php maintenance/jsparse.php --memory-limit=53M extensions/Translate/js/jquery-ui-1.7.2.custom.min.js 
PHP Fatal error:  Allowed memory size of 55574528 bytes exhausted (tried to allocate 86 bytes) in /var/www/trunk/includes/libs/jsminplus.php on line 1162
PHP Stack trace:
PHP   1. {main}() /var/www/trunk/maintenance/jsparse.php:0
PHP   2. require_once() /var/www/trunk/maintenance/jsparse.php:72
PHP   3. JSParseHelper->execute() /var/www/trunk/maintenance/doMaintenance.php:105
PHP   4. JSParser->parse() /var/www/trunk/maintenance/jsparse.php:55
PHP   5. JSParser->Script() /var/www/trunk/includes/libs/jsminplus.php:616
PHP   6. JSParser->Statements() /var/www/trunk/includes/libs/jsminplus.php:625
PHP   7. JSParser->Statement() /var/www/trunk/includes/libs/jsminplus.php:639
PHP   8. JSParser->Expression() /var/www/trunk/includes/libs/jsminplus.php:956
PHP   9. JSParser->FunctionDefinition() /var/www/trunk/includes/libs/jsminplus.php:1209
PHP  10. JSParser->Script() /var/www/trunk/includes/libs/jsminplus.php:1002
PHP  11. JSParser->Statements() /var/www/trunk/includes/libs/jsminplus.php:625
PHP  12. JSParser->Statement() /var/www/trunk/includes/libs/jsminplus.php:639
PHP  13. JSParser->Expression() /var/www/trunk/includes/libs/jsminplus.php:956
PHP  14. JSParser->Expression() /var/www/trunk/includes/libs/jsminplus.php:1314
PHP  15. JSParser->FunctionDefinition() /var/www/trunk/includes/libs/jsminplus.php:1209
PHP  16. JSParser->Script() /var/www/trunk/includes/libs/jsminplus.php:1002
PHP  17. JSParser->Statements() /var/www/trunk/includes/libs/jsminplus.php:625
PHP  18. JSParser->Statement() /var/www/trunk/includes/libs/jsminplus.php:639
PHP  19. JSParser->ParenExpression() /var/www/trunk/includes/libs/jsminplus.php:679
PHP  20. JSParser->Expression() /var/www/trunk/includes/libs/jsminplus.php:1432

If there's enough another stuff going on, hitting a larger limit when processing that is not inconceivable.

Since these should be static files anyway, they're not essential to be parsing anyway; will probably switch it over to only apply this by default on wiki-sourced JS modules, which usually won't be giant bulk libraries in one piece like this. :)
Comment 2 Niklas Laxström 2011-07-11 21:17:44 UTC
Hmm interesting. But that file should be in use only in MW < 1.16.
Comment 3 Brion Vibber 2011-07-11 21:30:39 UTC
Indeed; further it's loaded via OutputPage::addScriptFile not ResourceLoader at all...

Must be dying on something else -- probably the core jquery.js which is also largeish (but not large enough to die with my default memory limits at present)
Comment 4 Brion Vibber 2011-07-11 21:40:37 UTC
Ok, r91914 adds finer-grained $wgResourceLoaderValidateStaticJS (defaults off), so on a default setup we'll now only run the validation on modules backed by wiki pages, which are the most error-prone. (Gadgets, user & site JS pages)

If any reqs are still dying with this setting, you might have some giant user/site/gadget JS pages.  :)
Comment 5 Andre Klapper 2012-11-19 14:57:23 UTC
(In reply to comment #2)
> Hmm interesting. But that file should be in use only in MW < 1.16.

Does that make this report obsolete / WONTFIX?
Comment 6 Andre Klapper 2013-05-16 16:06:29 UTC
(In reply to comment #2)
> Hmm interesting. But that file should be in use only in MW < 1.16.

Niklas / Brion: Does that make this report obsolete / WONTFIX nowadays?
Comment 7 Nemo 2013-05-16 16:29:17 UTC
AFAICS, the problem was that even later versions had the problem even though they shouldn't. That said, per comment 4 prio is probably lower.

(In reply to comment #1)
> As a workaround for now, disable the parsing checks by setting
> $wgResourceLoaderValidateJS = false;

translatewiki.net still has this.
Comment 8 Ori Livneh 2013-05-29 19:34:20 UTC
http://en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-Twinkle.js&action=raw&oldid=557261853 appears to have tripped up OOMs, too.

I think there's a bad leak in jsminplus.php, and my hunch is that it has something to do with keeping references to substring copies of the input buffer.

To investigate this, I added the following line to the bottom of jsminplus.php:

    JSMinPlus::minify( file_get_contents( $argv[1] ) );

This allows it to be invoked against some file from the command line.
I then downloaded the version of Twinkle that was causing crashes:

    curl "https://en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-Twinkle.js&action=raw&oldid=557261853" -o twinkle-557261853.js

I then ran:

    $ /usr/bin/time -v php --no-php-ini jsminplus.php twinkle-557261853.js 2>&1 | grep Maximum
	Maximum resident set size (kbytes): 215552

That's 210.5 megabytes. Yikes.
This is with PHP 5.3.10-1ubuntu3.6+wmf1 with Suhosin-Patch. I ran the same command on one of the Apaches and got a similar result.

On #wikimedia-operations, Azatoth was trying to reproduce these measurements, but memory consumption on his machine was much lower: around 35 megabytes. He is running PHP 5.4.4-14+deb7u1. I upgrade a test machine to 5.4.15-1~precise+1 to see if perhaps the underlying cause was a memory leak that had been fixed in PHP. With that version, memory usage went down to 127 megabytes -- much lower, but still very high. I don't know why Azatoth was getting such different results.

I ran jsminplus.php under XDebug (this was with 5.3.10-1ubuntu3.6+wmf1) to produce a function trace and a cachegrind file. Both are too large to attach here. I ran tracefile-analyser.php (available in the contrib/ subfolder of XDebug's trunk) to produce the following reports:

Showing the 25 most costly calls sorted by 'memory-inclusive'.

                                      Inclusive        Own
function                      #calls  time     memory  time     memory
----------------------------------------------------------------------
JSTokenizer->getInput          165360  114.4447 12172424424  75.0972 13841944
substr                         167744  39.9809 12158771784  39.9809 12158771784
preg_match                     272096  63.2338 37906160  63.2338 37906160
rtrim                           3925  0.9227 35834032  0.9227 35834032
JSMinPlus->parseTree           56734  97.3480 20106208  2.7083 -34902944
func_get_args                  16804  3.8140 17558600  3.8140 17558600
JSNode->addNode                59938  14.1259 16062936  14.1259 16062936
JSParser->Variables             1017  170.5382 15314304  3.1037  3815424
JSTokenizer->get               116567  390.5212  9476264  170.0294 -12200722168
JSTokenizer->match             39648  139.7419  7702976  19.8020  2129472
JSParser->reduce                8715  40.7495  6143944  21.1107  -137456
array_push                     49937  11.4957  6061784  11.4957  6061784
JSParser->nest                    35  21.1327  4364896  0.0329        0
JSNode->__set                  23239  5.3959  3797512  5.3959  3797512
JSTokenizer->peek              22443  70.8936  3206192  10.5485        0
JSTokenizer->mustMatch         20217  105.4872  2770336  13.4861 -1633224
JSTokenizer->isDone             5546  33.2490  2109248  2.4946   446064
array_splice                    8554  2.0875  2043416  2.0875  2043416
JSParser->ParenExpression        663  56.9034  1954440  0.6503        0
{main}                             1  823.7359  1948864  0.0220      208
JSParser->Statements            1183  823.5497  1429432  0.0037     1688
JSParser->Statement             6302  823.5046  1425056  0.0030     1800
JSParser->Expression           11273  823.4974  1422832  0.0190    11928
JSMinPlus::minify                  1  823.5846  1406040  0.0005      672
JSParser->FunctionDefinition     365  823.3641  1405944  0.0069  -103608

------------------------------------------------------------------------------



Showing the 25 most costly calls sorted by 'memory-own'.

                                      Inclusive        Own
function                      #calls  time     memory  time     memory
----------------------------------------------------------------------
substr                         167744  39.9809 12158771784  39.9809 12158771784
preg_match                     272096  63.2338 37906160  63.2338 37906160
rtrim                           3925  0.9227 35834032  0.9227 35834032
func_get_args                  16804  3.8140 17558600  3.8140 17558600
JSNode->addNode                59938  14.1259 16062936  14.1259 16062936
JSTokenizer->getInput          165360  114.4447 12172424424  75.0972 13841944
array_push                     49937  11.4957  6061784  11.4957  6061784
JSParser->Variables             1017  170.5382 15314304  3.1037  3815424
JSNode->__set                  23239  5.3959  3797512  5.3959  3797512
JSTokenizer->match             39648  139.7419  7702976  19.8020  2129472
array_splice                    8554  2.0875  2043416  2.0875  2043416
JSTokenizer->unget             34685  8.1175   587496  8.1175   587496
file_get_contents                  1  0.0162   538608  0.0162   538608
JSTokenizer->isDone             5546  33.2490  2109248  2.4946   446064
JSNode->__get                  156169  38.4977   124376  38.4977   124376
JSMinPlus->isValidIdentifier     964  1.1048    78888  0.6843    78888
array_map                          1  0.0002    15840  0.0002    15840
JSParser->Expression           11273  823.4974  1422832  0.0190    11928
JSMinPlus->isWordChar            148  0.0970    11904  0.0636    11904
define                            97  0.0193     4008  0.0193     4008
JSParser->__construct              1  0.0012     2520  0.0003     2240
JSParser->Statement             6302  823.5046  1425056  0.0030     1800
JSParser->Statements            1183  823.5497  1429432  0.0037     1688
JSTokenizer->init                  1  0.0002      856  0.0002      856
JSMinPlus->__construct             1  0.0015     3208  0.0004      688

------------------------------------------------------------------------------



Showing the 25 most costly calls sorted by 'time-own'.

                                      Inclusive        Own
function                      #calls  time     memory  time     memory
----------------------------------------------------------------------
JSTokenizer->get               116567  390.5212  9476264  170.0294 -12200722168
JSTokenizer->getInput          165360  114.4447 12172424424  75.0972 13841944
preg_match                     272096  63.2338 37906160  63.2338 37906160
JSNode->__construct            67494  98.8944 -2112880  56.9707 -28682112
substr                         167744  39.9809 12158771784  39.9809 12158771784
JSNode->__get                  156169  38.4977   124376  38.4977   124376
strlen                         126407  28.8690        0  28.8690        0
JSTokenizer->currentToken      108812  25.2751       80  25.2751       80
JSParser->reduce                8715  40.7495  6143944  21.1107  -137456
JSTokenizer->match             39648  139.7419  7702976  19.8020  2129472
func_num_args                  67494  15.2409        0  15.2409        0
JSNode->addNode                59938  14.1259 16062936  14.1259 16062936
JSTokenizer->mustMatch         20217  105.4872  2770336  13.4861 -1633224
array_push                     49937  11.4957  6061784  11.4957  6061784
JSTokenizer->peek              22443  70.8936  3206192  10.5485        0
substr_count                   37358  8.5390        0  8.5390        0
JSTokenizer->unget             34685  8.1175   587496  8.1175   587496
in_array                       31880  7.3246        0  7.3246        0
count                          31500  7.2972        0  7.2972        0
array_pop                      31298  7.1604 -6043640  7.1604 -6043640
JSNode->__set                  23239  5.3959  3797512  5.3959  3797512
end                            16918  3.9226        0  3.9226        0
func_get_args                  16804  3.8140 17558600  3.8140 17558600
JSParser->Variables             1017  170.5382 15314304  3.1037  3815424
JSMinPlus->parseTree           56734  97.3480 20106208  2.7083 -34902944
Comment 9 Ori Livneh 2013-05-29 19:57:38 UTC
Cachegrind file:
http://kubo.wmflabs.org/cachegrind.out.3012.gz

Xdebug function trace (trace_format=1):
http://kubo.wmflabs.org/trace.105056044.xt.gz

Two other details I forgot to mention:
* There is no newer version of jsminplus.php, and I did not find a bug tracker for the project.
* Minifying the previous revision of Twinkle consumed less memory, but not dramatically so (I forgot to write it down and am too lazy to rerun the check). So it's probably not the case that some unusually aberrant syntax in the diff between those two revisions caused memory usage to balloon (which was my initial suspicion).
Comment 10 Brad Jorsch 2013-05-30 17:33:50 UTC
(In reply to comment #8)
> To investigate this, I added the following line to the bottom of
> jsminplus.php:
> 
>     JSMinPlus::minify( file_get_contents( $argv[1] ) );

Note this is actually going to be using less memory than what ResourceLoader actually does. JSMinPlus::minify passes itself to the JSParser constructor, which causes it to trim the node tree for the body of each function. ResourceLoader passes null, so this isn't done.

We could easily enough get that advantage in RL by passing JSParser an instance of this:

  class DummyMinifier {
      function parseTree() {
          return null;
      }
  }

If we really want to cut the memory usage, though, we could add a mode to do the same sort of pruning even more aggressively, maybe on every statement instead of only on every function body.

> On #wikimedia-operations, Azatoth was trying to reproduce these measurements,
> but memory consumption on his machine was much lower: around 35 megabytes. He
> is running PHP 5.4.4-14+deb7u1. I upgrade a test machine to
> 5.4.15-1~precise+1
> to see if perhaps the underlying cause was a memory leak that had been fixed
> in
> PHP. With that version, memory usage went down to 127 megabytes -- much
> lower,
> but still very high. I don't know why Azatoth was getting such different
> results.

Me neither. On my local machine, with Debian's 5.4.15-1, I get around 42M.

> I think there's a bad leak in jsminplus.php, and my hunch is that it has
> something to do with keeping references to substring copies of the input
> buffer.

It looks like the problem is just that jsminplus builds up a huge tree of JSNode objects (i.e. 86487 of them). Dumping this tree doesn't show copies of the input buffer.

However, I do see that it makes temporary copies of large substrings of the input buffer very often, because it treats "no comment" the same as "comment longer than $chunksize" for some unknown reason. This can be reduced by adding the following check just before line 1864:

            // If it's not possibly a comment (or regex), exit the loop
            if ($input[0] !== '/')
                break;

Even though these temporary copies are soon freed, they still cause memory usage spikes. Although even after making this change, XDebug seems to be reporting that PHP is still allocating 128K memory buffers associated with calls to JSTokenizer::get for no obvious reason.
Comment 11 Krinkle 2013-10-21 06:45:07 UTC
Can this be closed? We no longer use JSMinPlus. Looks like it still exist in the repository though, should it be removed?
Comment 12 Kevin Israel (PleaseStand) 2013-10-21 06:54:58 UTC
(In reply to comment #11)
> Can this be closed? We no longer use JSMinPlus. Looks like it still exist in
> the repository though, should it be removed?

MaxSem was going to remove it in Gerrit change #74283, also thinking it was unused. It is actually still used for syntax checking of user/site JS, though not for minification.
Comment 13 Krinkle 2014-08-28 23:33:36 UTC
Bumping priority (since this is still affecting production if you have a large gadget or user script).

Mentioning JsMinPlus in title for easier finding via search.

Note You need to log in before you can comment on or make changes to this bug.


Navigation
Links