Bug 230192 - Decoding audio data and creating a source node causes safari to crash
Summary: Decoding audio data and creating a source node causes safari to crash
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: Web Audio (show other bugs)
Version: Safari 14
Hardware: All All
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-09-11 11:52 PDT by Jason
Modified: 2021-09-13 04:03 PDT (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jason 2021-09-11 11:52:21 PDT
We have an application that plays songs as users learn to play the piano.  We noticed that the app would crash after users would switch between songs a couple of dozen times (depends on which songs).
We isolated the issue to calling context.decodeAudioData and assigning that decoded audio data to a new context.createBufferSource. 

There appears to be a memory leak which causes the app to start to slow down, then causes the browser to crash & restart with "webpage was reloaded because a problem occurred." on iOS, or message "This webpage was reloaded because it was using significant memory" on desktop.

Verified issue occurs after ~12 iterations on iPhone 11 max pro running iOS 14.3.
Verified issue occurs after ~12 iterations on iPad 5th Gen running iOS 14.7.1.
Verified issue occurs after ~40 iterations on 2018 MacBook Pro running Big Sur 11.3.1.

Code demonstrating the issue
https://codepen.io/jasonmcaffee/pen/KKqmxEX

Relevant code
async function load(){
  const context = new AudioContext();
  const url = "https://dalnn20hi8hmy.cloudfront.net/r1/00/00/59/4i/slice_15716_resourcefile_47285_20200901_22h33m22s_mdt.mp3";
  const arrayBuffer = await fetchAudioData({url, context});

  //decoding the array buffer and creating a source node causes ios 14.7.1 Safari to crash after ~10 iterations on an iPad 5th gen. also crashes ios 14.3 on iPhone 11 pro max.
  while(!stop){
    const audioBuffer = await decodeAudioData(context, arrayBuffer.slice(0)); //use slice(0) to avoid detached array buffer error.
    const source = createAudioSourceFromAudioBuffer(audioBuffer, context);
    print(`${i++} done creating buffer source.`);
    await sleep(100);
  }
  
}

async function sleep(ms: number){
  return new Promise((resolve)=>{
    setTimeout(resolve, ms);
  });
}


async function fetchAudioData({url, context, signal}: {url: string; context: AudioContext, signal?: AbortSignal}){
  const response = await fetch(url, {cache: "force-cache", signal});
  const arrayBuffer = await response.arrayBuffer();
  return arrayBuffer;
}  

async function decodeAudioData(context: AudioContext, arrayBuffer: ArrayBuffer): Promise<AudioBuffer>{
  return new Promise((resolve, reject) => context.decodeAudioData(arrayBuffer, resolve, reject));
}

function createAudioSourceFromAudioBuffer(audioBuffer: AudioBuffer, context: AudioContext | OfflineAudioContext): AudioBufferSourceNode {
  const source = context.createBufferSource();
  source.buffer = audioBuffer;
  return source;
}
Comment 1 Radar WebKit Bug Importer 2021-09-13 04:03:15 PDT
<rdar://problem/83048575>