io-lifecycle-probe.mjs (4361B)
1 // Lifecycle building blocks with a real DOM read/provider and native pure work. 2 // This does NOT execute a v2 language program or bypass the production v2 gate. 3 export async function run(_control,backend){ 4 const assert=(value,message)=>{if(!value)throw Error(message);}; 5 const base=new URL('../',document.querySelector('script[type=module][src]').src); 6 const {ioResources:{lifecycle},ioHostWork:{HostWork},runtimeSession}=await import(new URL('dist/probe-api.js',base)); 7 const session=await runtimeSession.createRuntimeSession(backend,false),checks=[]; 8 const {tasks,resources}=lifecycle(1n,'owner-stopped','cycle',[null,null,null]),work=new HostWork(tasks); 9 const input=document.createElement('input');input.id='lifecycle-test-input';document.body.append(input); 10 const original=resources.publish(resources.beginAcquire(0),'reader',input); 11 const {task:read,handles}=resources.spawn(0,true,true,[original]),other=tasks.spawn(0,true,false); 12 let stale=false;try{resources.lookup(0,original);}catch(error){stale=error.message==='stale-handle';}assert(stale,'old alias survived transfer'); 13 let acknowledge,aborts=0,events=0,signal; 14 const cleanup=new Promise(resolve=>{acknowledge=resolve;}); 15 const listener=()=>{events++;};input.addEventListener('input',listener); 16 const release=async id=>{work.release(id,()=>resources.releaseScope(id,r=>r.backend.remove(),e=>e,'cleanup-failed'));await work.wait();const reply=work.take();assert(reply?.outcome.ok,'release failed');return tasks.settle(id,reply.outcome.value);}; 17 const finish=async id=>{tasks.bodyDone(id,{kind:'completed',value:null});assert(tasks.beginRelease(id),'release blocked');return release(id);}; 18 try{ 19 const fixture=(await(await fetch('/runtime-fixtures.json')).json())['pure-number']; 20 await session.load(new Uint8Array(fixture.bytes),fixture.hash); 21 work.start(read.key,async abort=>{ 22 signal=abort;const lease=resources.beginLease(read.key,handles[0],'reader','read'); 23 try{await new Promise(resolve=>abort.addEventListener('abort',()=>{aborts++;input.removeEventListener('input',listener);resolve();},{once:true})); 24 await cleanup;return 'late read value';}finally{resources.endLease(lease.token);} 25 }); 26 const forbidden=()=>{throw Error('pure work invoked IO hook');}; 27 work.start(other.key,()=>session.run('root',[],'number','pure',{before:forbidden,after:forbidden,read:forbidden,write:forbidden,running:forbidden})); 28 await work.wait();const reply=work.take();assert(reply.outcome.ok&&reply.outcome.value.value==='42','native sibling result');await finish(other.key); 29 assert(!signal.aborted&&tasks.state(read.key).hostCalls===1,'read was detached'); 30 input.dispatchEvent(new Event('input'));assert(events===1,'listener not live'); 31 checks.push('pending DOM read does not block sibling native pure evaluation'); 32 tasks.cancel(0,read,'cancel read');work.cancelStopped();work.cancelStopped(); 33 tasks.bodyDone(read.key,{kind:'completed',value:null}); 34 assert(aborts===1&&signal.aborted,'abort delivered more than once'); 35 input.dispatchEvent(new Event('input'));assert(events===1,'cancelled listener still active'); 36 assert(!tasks.beginRelease(read.key)&&input.isConnected,'cancellation request was treated as completion'); 37 const wait=work.wait();acknowledge();await wait; 38 assert(input.isConnected&&work.take()===null,'resource closed before scope release or late reply escaped'); 39 assert(resources.counts.leases===0,'read lease survived acknowledgement'); 40 assert(tasks.beginRelease(read.key),'acknowledged read did not release');await release(read.key); 41 assert(!input.isConnected,'owned DOM resource escaped physical release'); 42 tasks.claimJoin(0,read);assert(tasks.pollJoin(0).kind==='cancelled','cancelled join outcome');await finish(0); 43 assert(tasks.counts.scopes===0&&tasks.counts.tasks===0&&tasks.counts.hosts===0,'lifecycle retained history'); 44 assert(work.counts.pending===0&&work.counts.replies===0,'host work retained history'); 45 assert(resources.counts.resources===0&&resources.counts.leases===0&&resources.counts.admissions===0,'registry retained history'); 46 checks.push('transferred alias invalidation; leased DOM read; one abort; physical acknowledgement and resource release before cancelled join'); 47 }finally{acknowledge?.();input.removeEventListener('input',listener);input.remove();session.dispose();} 48 return {backend,checks}; 49 }