browser-client.mjs (1999B)
1 // Single-flight Worker ownership. Termination rejects the active request and 2 // discards the entire runtime; no attempt to interrupt/reuse a poisoned RTS. 3 export class CompilerWorker { 4 constructor(){ 5 this.worker=new Worker(new URL('./worker.mjs',import.meta.url),{type:'module'}); 6 this.lifetime=new AbortController();this.signal=this.lifetime.signal; 7 this.next=0;this.pending=null;this.disposed=false; 8 this.worker.onmessage=({data})=>{ 9 const pending=this.pending;if(!pending||pending.id!==data.id)return; 10 clearTimeout(pending.timer);this.pending=null; 11 if(data.ok){ 12 data.text=new TextDecoder('utf-8',{fatal:true}).decode(data.bytes); 13 data.roundtripMs=performance.now()-pending.start; 14 pending.resolve(data); 15 }else{ 16 pending.reject(Error(data.error)); 17 if(data.fatal)this.dispose('Fatal runtime error'); 18 } 19 }; 20 this.worker.onerror=event=>{event.preventDefault();this.dispose(event.message||'Worker failed');}; 21 } 22 request(message,timeout=null){ 23 if(this.disposed)return Promise.reject(Error('Disposed Worker')); 24 if(this.pending)return Promise.reject(Error('Worker is busy')); 25 if(timeout!==null&&timeout!==Infinity&&(!Number.isFinite(timeout)||timeout<=0))return Promise.reject(Error('Invalid request deadline')); 26 const id=++this.next,start=performance.now(); 27 return new Promise((resolve,reject)=>{ 28 const timer=timeout===null||timeout===Infinity?undefined:setTimeout(()=>this.dispose('Worker deadline exceeded'),timeout); 29 this.pending={id,resolve,reject,timer,start}; 30 try{this.worker.postMessage({...message,id});} 31 catch(error){this.dispose('Request transport failed: '+error.message);} 32 }); 33 } 34 dispose(reason='Worker terminated'){ 35 if(this.disposed)return; 36 this.disposed=true;this.worker.terminate();this.lifetime.abort(Error(reason)); 37 if(this.pending){clearTimeout(this.pending.timer);this.pending.reject(Error(reason));this.pending=null;} 38 } 39 }