Skip to content

WebSocket Memory Leak on Android and iOS #16732

Description

@SteffenC

Memory usage through 1:33 minute
screen shot 2017-11-07 at 14 19 05

Is this a bug report?

Yes.

Have you read the Contributing Guidelines?

Yes.

Environment

Environment:
OS: macOS High Sierra 10.13.1
Node: 8.5.0
Yarn: 1.0.2
npm: 5.3.0
Watchman: 4.9.0
Xcode: Xcode 9.1 Build version 9B55
Android Studio: 3.0 AI-171.4408382
Packages: (wanted => installed)
react: 16.0.0-beta.5 => 16.0.0-beta.5
react-native: 0.49.1 => 0.49.1

Steps to Reproduce

  1. react-native init test
  2. Change content of app.js to the provided example
  3. Add provided "Speedtester.js" file in src/Speedtester.js
  4. run project, and notice the memory usage keep increasing.

Expected Behavior

I expected the memory usage to increase during the speedtest and then decrease again afterwards.

Actual Behavior

The memory usage increases during the test but never decreases.

Reproducible Demo

App.js

importReact,{Component}from'react';import{StyleSheet,Text,View}from'react-native';importSpeedtesterfrom'./src/Speedtester';exportdefaultclassAppextendsComponent{constructor(){super();this.state={rate: 0};}componentDidMount(){this.runTest();}runTest(){constsptest=newSpeedtester();sptest.runDownloadTest(10000,(rate)=>{this.setState({ rate });},()=>{setTimeout(()=>{this.runTest()},10000)});}render(){return(<Viewstyle={{flex: 1,justifyContent: 'center'}}><Textstyle={styles.rate}>{this.state.rate}</Text></View>);}}conststyles=StyleSheet.create({rate: {fontSize: 20,textAlign: 'center',margin: 10,},});

Speedtester.js

/* eslint-disable */importReact,{Component}from'react';import{View,Text}from'react-native';classSpeedtester{constructor(){if(!String.prototype.startsWith){String.prototype.startsWith=function(searchString,position){returnthis.substr(position||0,searchString.length)===searchString;};}}runLatencyTest(onprogress,end){vartarget="wss://speedtest.fullrate.dk/backend/ws";Promise.resolve().then(function(){returnlatencytest({target: target,}).then(function(samples){for(vari=0;i<samples.length;i++){onprogress(samples[i])}});}).then(end);}runDownloadTest(duration,onprogress,end){vartarget="wss://speedtest.fullrate.dk/backend/ws";Promise.resolve().then(function(){returnspeedtest({target: target,session: session_download,duration: duration||10000,finish: end,progressinterval: 800,onprogress: function(speed,elapsed,total,time){if(speed>30){varrate=speed.toFixed(0);}else{varrate=speed.toFixed(1);}if(time){vareta=time;}else{vareta=0;}onprogress(rate,eta);}});});}runUploadTest(duration,onprogress,end){vartarget="wss://speedtest.fullrate.dk/backend/ws";consttmpWs=newWebSocket(target);tmpWs.binaryType='blob';tmpWs.onopen=function(event){send=true;constsize=(1*1024*1024);tmpWs.send('DOWNLOAD '+size);tmpWs.onmessage=function(event){constuploadbuf=event.data;tmpWs.close();varrate=0;vareta=0;Promise.resolve().then(function(){returnspeedtest({target: target,session: session_upload,duration: 10000,finish: end,uploadbuf: uploadbuf,onprogress: function(speed,elapsed,total,time){if(speed>30){rate=speed.toFixed(0);}else{rate=speed.toFixed(1);}if(time){eta=time;}else{eta=0;}onprogress(rate,eta);}});});}}}stopTest(){if(_cleanup){_cleanup();}}}/*** SPEED TEST FUNCS*/functionnow(){return(newDate).getTime();}functionuint8tostr(data){returnString.fromCharCode.apply(null,newUint8Array(data));}// Returns a function that will cancel the sessionfunctionsession(target,queuelen,onreceive,handler){varsendtimes=[];varreplies=0;varnextinc=0;varsize=handler.minsize;varmintime=100;varexit=false;functionfill_queue(){if(exit)return;while(sendtimes.length<queuelen){sendtimes.push(now());ws.send(handler.req(size));}}varws=newWebSocket(target);ws.binaryType=handler.resulttype;ws.onopen=function(){if(exit)return;fill_queue();}ws.onclose=function(){if(exit)return;exit=true;onreceive("closed");}ws.onerror=function(e){if(exit)return;exit=true;onreceive(e);};ws.onmessage=function(e){if(exit)return;varv=handler.rep(e.data);if(v===false){exit=true;onreceive("Unexpected data returned");return;}onreceive(undefined,v);vartd=now()-sendtimes.shift();if(td<mintime&&nextinc<=replies&&size<handler.maxsize){size=size*2;nextinc=replies+queuelen;if(size>handler.maxsize)size=handler.maxsize;}fill_queue();replies++;};returnfunction(){exit=true;returnnewPromise(function(resolve){ws.onclose=ws.onerror=function(){resolve();};ws.close();});};}functionsession_download(target,queuelen,uploadbuf,onreceive){returnsession(target,queuelen,onreceive,{resulttype: 'blob',minsize: 10000,maxsize: 1000000,req: functionreq_download(size){return'DOWNLOAD '+size;},rep: functionrep_download(data){vars=data.size||data.byteLength;returns;},});}//var uploadbuf = new Blob([new Uint8Array(1 * 1024 * 1024)]);functionsession_upload(target,queuelen,uploadbuf,onreceive){returnsession(target,queuelen,onreceive,{resulttype: 'arraybuffer',minsize: 10000,maxsize: 1000000,req: functionreq_upload(size){vars=uploadbuf.slice(size);returns;},rep: functionrep_upload(data){varstr=uint8tostr(data);if(!str.startsWith('OK '))returnfalse;vars=parseInt(str.substr(3),10);returns;},});}vartimeRemaining;varelapsed;vardelta;var_cleanup;functionspeedtest(opts){vartarget=opts.target;if(!target)returnPromise.reject("No target URL");varsession=opts.session;if(!session)returnPromise.reject("No session");varduration=opts.duration||10000;varprogressinterval=opts.progress_interval||400;varqueuelen=opts.queuelen||10;varconcurrency=opts.concurrency||5;vartotal=0;varbegintime=now();varsamples=[[0,0]];varsmoothtime=opts.smoothtime||3000;varfirst;timeRemaining=duration;returnnewPromise(function(resolve,reject){varcancels=[];for(vari=0;i<concurrency;i++){cancels.push(session(target,queuelen,opts.uploadbuf,function(err,amount){if(err){cleanup();}total+=amount;}));}varinterval=setInterval(function(){elapsed=now()-begintime;while(samples.length>0&&samples[0][0]<(elapsed-smoothtime))samples.shift();samples.push([elapsed,total]);varfirst=samples[0];delta=samples.length<=1||elapsed===first[0] ? 0 : (total-first[1])/(elapsed-first[0]);timeRemaining=timeRemaining-progressinterval;varspeed=8*delta/1000;if(opts.onprogress)opts.onprogress(speed,elapsed,total,timeRemaining);},progressinterval);vartimeout=setTimeout(function(){cleanup();opts.finish();},duration);if(opts.onprogress)opts.onprogress(0,0,0,0);functioncleanup(){if(opts.onprogress)opts.onprogress(8*delta/1000,elapsed,total,0);clearInterval(interval);clearTimeout(timeout);returnPromise.all(cancels.map(function(c){returnc();}));}_cleanup=cleanup;});}functionlatencytest(opts){constprobes=opts.probes||20;vartarget=opts.target;if(!target)returnPromise.reject("No target URL");returnnewPromise(function(resolve,reject){constresults=[];constws=newWebSocket(target);ws.binaryType='arraybuffer';functionsendping(){ws.send('PING '+now());}ws.onopen=function(){sendping();};ws.onclose=function(){resolve(results);};ws.onerror=function(){reject("An error occured");// TODO?};ws.onmessage=function(e){varmsg=uint8tostr(e.data);// if (msg.indexOf('PONG ') !== -1) reject("Invalid response from server"); appearantly this breaks on android?varts=parseInt(msg.substr(5),10);vardelta=now()-ts;if(delta===0)delta=1;results.push(delta);if(results.length<probes){sendping();}else{ws.close();}};});}exportdefaultSpeedtester;

Memory usage shown in Instruments
instruments-code

Call tree showing memory usage
instruments

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions