Open Bug 1702447 Opened 11 days ago Updated 11 days ago

URL.createObjectURL is easy to use incorrectly and results in easy OOM

Categories

(Core :: DOM: File, defect)

defect

Tracking

()

People

(Reporter: padenot, Unassigned, NeedInfo)

Details

Attachments

(1 file)

222 bytes, application/octet-stream
Details
Attached file testcase.html

STR: Run this (also attached)

<script>
    // 500kB buffer
    var buffer = new ArrayBuffer(500 * 1024);
    console.time("a")
    var a = URL.createObjectURL(new Blob(new Uint8Array(buffer), ["audio/mpeg"]));
    console.timeEnd("a")
</script>

Expected:

  • Some memory is allocated, it's fast

Actual:

  • This allocates and maps about 300MB of memory, and takes about 1s to run on my really fast linux box. On a file/buffer that is more than a few megabytes, it OOMs, and this machines has 64GB of memory.

Profile of the issue:

This is also problematic in Chrome fwiw.

Flags: needinfo?(bugs)

The bulk of the time spent seems to be in SerializeInputStreamAsPipeInternal.

This seem to work correctly in Safari.

Component: DOM: Core & HTML → DOM: File

Do you have a profile with parent process too, and IPC profiling might be useful too.

Flags: needinfo?(bugs) → needinfo?(amarchesini)
Flags: needinfo?(bugs)

I discussed this a bit on #dom on matrix, but this is actually being caused by a footgun. The Blob constructor takes an array of blob segments as a first argument, whereas this example passes in a Uint8Array. WebIDL converts each element of the array individually into a string to be added to the stream, so the behaviour actually ends up being:

["0", "0", "0", ...500k times, "0"]

The overhead in this situation, then, becomes due to the overhead of processing all 500k stream segments independently. WebKit appears to eagerly concatenate sequences of strings together into the same blob part at construction time, meaning that they end up storing a sequence of 500k "0"s, whereas we convert each single-byte string into a stream independently. The largely-inflated overhead is then coming just from every stream being relatively expensive compared to a single byte string.

It might be worth considering producing a warning of some kind if a Uint8Array is directly passed to new Blob as the first argument, depending on how difficult that is to do, and we could consider concatenating adjacent string/ArrayBuffer segments together like WebKit is in order to reduce overhead for very large blob segment sequences.

Summary: URL.createObjectURL is inefficient → URL.createObjectURL is easy to use incorrectly and results in easy OOM
You need to log in before you can comment on or make changes to this bug.