io-session-probe.mjs (4254B)
1 // Native tree sibling cancellation through cloneable events, with a real DOM 2 // provider. No raw handles leave the worker/session boundary. 3 export async function run(_control,backend){ 4 const base=new URL('../',document.querySelector('script[type=module][src]').src),fixture=(await(await fetch('/runtime-fixtures.json')).json())['v2-session-streams']; 5 const assert=(ok,message)=>{if(!ok)throw Error(message);}; 6 async function exercise(start,turn,stop){ 7 let report=await start();const events=[]; 8 for(let i=0;i<30&&!events.some(e=>e.kind==='write');i++){events.push(...report.events);if(!events.some(e=>e.kind==='write'))report=await turn({run:report.run,kind:'poll'});} 9 const read=events.find(e=>e.kind==='read'),write=events.find(e=>e.kind==='write');assert(read&&write,'pending read blocked sibling write'); 10 const input=document.createElement('input');document.body.append(input);let hits=0; 11 const listener=()=>{hits++;};input.addEventListener('input',listener); 12 try{ 13 if(stop){ 14 report=await turn({run:report.run,kind:'stop'}); 15 assert(!report.done&&report.events.filter(e=>e.kind==='cancel').length===2,'stop did not cancel both providers'); 16 input.removeEventListener('input',listener);input.remove(); 17 report=await turn({run:report.run,kind:'reply',reply:{...read,kind:'cancelled'}}); 18 assert(!report.done,'stop forgot pending output acknowledgement'); 19 report=await turn({run:report.run,kind:'reply',reply:{...write,kind:'cancelled'}}); 20 for(let i=0;i<30&&!report.done;i++)report=await turn({run:report.run,kind:'poll'}); 21 assert(report.done&&report.outcome.kind==='cancelled','structured session stop did not settle'); 22 assert(Object.values(report.counts).every(group=>Object.values(group).every(value=>value===0)),'stop retained lifecycle state'); 23 return; 24 } 25 let rejected=false;try{await turn({run:'999',kind:'stop'});}catch{rejected=true;}assert(rejected,'wrong run accepted'); 26 report=await turn({run:report.run,kind:'reply',reply:{...read,run:'999',bytes:[1],eof:false}});assert(report.accepted===false,'wrong reply consumed wait'); 27 assert(write.bytes.length===1&&write.bytes[0]===65,'write payload mismatch'); 28 report=await turn({run:report.run,kind:'reply',reply:{run:write.run,id:write.id,kind:'write',count:1}}); 29 let cancel=report.events.find(e=>e.kind==='cancel'); 30 for(let i=0;i<30&&!cancel;i++){report=await turn({run:report.run,kind:'poll'});cancel=report.events.find(e=>e.kind==='cancel');} 31 assert(cancel?.id===read.id,'missing read cancellation');assert(!report.done&&report.counts.events.pending===1,'abort delivery settled provider'); 32 input.removeEventListener('input',listener);input.dispatchEvent(new Event('input'));assert(hits===0,'listener survived cancellation'); 33 report=await turn({run:report.run,kind:'poll'});assert(!report.done&&input.isConnected,'unacknowledged cleanup retired run'); 34 input.remove();report=await turn({run:report.run,kind:'reply',reply:{...cancel,kind:'cancelled'}}); 35 for(let i=0;i<30&&!report.done;i++)report=await turn({run:report.run,kind:'poll'}); 36 assert(report.done&&report.outcome.kind==='completed'&&report.outcome.value==='42','tree run result mismatch'); 37 assert(Object.values(report.counts).every(group=>Object.values(group).every(value=>value===0)),'historical transport/lifecycle state retained'); 38 }finally{input.removeEventListener('input',listener);input.remove();} 39 } 40 for(const stop of [false,true]){ 41 const {CompilerWorker}=await import(new URL('runtime/haskell/client.mjs',base)),client=new CompilerWorker(); 42 try{await client.request({type:'init',compiler:false});await client.request({type:'load',bytes:new Uint8Array(fixture.bytes).buffer,hash:fixture.hash}); 43 await exercise(async()=>JSON.parse((await client.request({type:'io-session-start',name:'root',arguments:[]})).text),async command=>JSON.parse((await client.request({type:'io-session-turn',...command})).text),stop); 44 }finally{client.dispose();} 45 } 46 return {backend,checks:['native tree read permits sibling write','wrong-run stop and reply reject','tree cancellation waits for DOM cleanup acknowledgement','complete42; no retained task/resource/event records','external structured stop awaits both provider acknowledgements']}; 47 }