@@ -12,7 +12,6 @@ import * as Queue from "effect/Queue";
1212import * as Ref from "effect/Ref" ;
1313import * as Stream from "effect/Stream" ;
1414import * as SubscriptionRef from "effect/SubscriptionRef" ;
15- import * as TestClock from "effect/testing/TestClock" ;
1615
1716import {
1817 AVAILABLE_CONNECTION_STATE ,
@@ -153,85 +152,6 @@ describe("environment shell synchronization", () => {
153152 } ) ,
154153 ) ;
155154
156- it . effect ( "keeps the failure visible while an already buffered shell batch drains" , ( ) =>
157- Effect . gen ( function * ( ) {
158- const items : OrchestrationShellStreamItem [ ] = [
159- { kind : "snapshot" , snapshot : LIVE_SHELL_SNAPSHOT } ,
160- ...Array . from ( { length : 300 } , ( _ , index ) => ( {
161- kind : "thread-upserted" as const ,
162- sequence : index + 2 ,
163- thread : { id : `thread-${ index } ` } as never ,
164- } ) ) ,
165- { kind : "synchronized" } ,
166- ] ;
167- let attempts = 0 ;
168- const client = {
169- [ ORCHESTRATION_WS_METHODS . subscribeShell ] : ( ) =>
170- attempts ++ === 0
171- ? Stream . concat ( Stream . fromArray ( items ) , Stream . fail ( new Error ( "stream failed" ) ) )
172- : Stream . concat (
173- Stream . succeed < OrchestrationShellStreamItem > ( { kind : "synchronized" } ) ,
174- Stream . never ,
175- ) ,
176- } as unknown as WsRpcProtocolClient ;
177- const supervisorState = yield * SubscriptionRef . make ( AVAILABLE_CONNECTION_STATE ) ;
178- const activeSession = yield * SubscriptionRef . make < Option . Option < RpcSession . RpcSession > > (
179- Option . some ( session ( client ) ) ,
180- ) ;
181- const supervisor = EnvironmentSupervisor . EnvironmentSupervisor . of ( {
182- target : TARGET ,
183- state : supervisorState ,
184- session : activeSession ,
185- prepared : yield * SubscriptionRef . make ( Option . some ( PREPARED ) ) ,
186- connect : Effect . void ,
187- disconnect : Effect . void ,
188- retryNow : Effect . void ,
189- } satisfies EnvironmentSupervisor . EnvironmentSupervisor [ "Service" ] ) ;
190- const cache = Persistence . EnvironmentCacheStore . of ( {
191- loadShell : ( ) => Effect . succeed ( Option . none ( ) ) ,
192- saveShell : ( ) => Effect . void ,
193- loadThread : ( ) => Effect . succeed ( Option . none ( ) ) ,
194- saveThread : ( ) => Effect . void ,
195- removeThread : ( ) => Effect . void ,
196- loadServerConfig : ( ) => Effect . succeed ( Option . none ( ) ) ,
197- saveServerConfig : ( ) => Effect . void ,
198- loadVcsRefs : ( ) => Effect . succeed ( Option . none ( ) ) ,
199- saveVcsRefs : ( ) => Effect . void ,
200- removeVcsRefs : ( ) => Effect . void ,
201- clearVcsRefs : ( ) => Effect . void ,
202- clear : ( ) => Effect . void ,
203- } ) ;
204- // Cold cache with no HTTP snapshot available → falls back to the
205- // socket-embedded snapshot.
206- const snapshotLoader = ShellSnapshotLoader . of ( {
207- load : ( ) => Effect . succeed ( Option . none ( ) ) ,
208- } ) ;
209- const shellState = yield * makeEnvironmentShellState ( ) . pipe (
210- Effect . provideService ( EnvironmentSupervisor . EnvironmentSupervisor , supervisor ) ,
211- Effect . provideService ( Persistence . EnvironmentCacheStore , cache ) ,
212- Effect . provideService ( ShellSnapshotLoader , snapshotLoader ) ,
213- ) ;
214-
215- const drained = yield * SubscriptionRef . changes ( shellState ) . pipe (
216- Stream . filter ( ( value ) => Option . getOrNull ( value . snapshot ) ?. snapshotSequence === 301 ) ,
217- Stream . runHead ,
218- Effect . map ( Option . getOrThrow ) ,
219- ) ;
220- expect ( drained . error ) . toEqual ( Option . some ( "Could not synchronize environment data." ) ) ;
221- expect ( drained . status ) . toBe ( "cached" ) ;
222- expect ( Option . getOrThrow ( drained . snapshot ) . threads ) . toHaveLength ( 300 ) ;
223- yield * TestClock . adjust ( "250 millis" ) ;
224- const recovered = yield * SubscriptionRef . changes ( shellState ) . pipe (
225- Stream . filter ( ( value ) => value . status === "live" ) ,
226- Stream . runHead ,
227- Effect . map ( Option . getOrThrow ) ,
228- ) ;
229- expect ( attempts ) . toBe ( 2 ) ;
230- expect ( recovered . error ) . toEqual ( Option . none ( ) ) ;
231- expect ( Option . getOrThrow ( recovered . snapshot ) . snapshotSequence ) . toBe ( 301 ) ;
232- } ) ,
233- ) ;
234-
235155 it . live . each ( [
236156 { bufferSize : Infinity , expectedSequences : [ 51 ] } ,
237157 // RpcClient defaults to a 16-event buffer, which splits larger server chunks.
@@ -531,177 +451,4 @@ describe("environment shell synchronization", () => {
531451 expect ( yield * Ref . get ( loaderCalls ) ) . toBe ( 2 ) ;
532452 } ) ,
533453 ) ;
534-
535- it . effect ( "folds a backlogged shell event flood into a bounded number of publications" , ( ) =>
536- Effect . gen ( function * ( ) {
537- const events = yield * Queue . unbounded < OrchestrationShellStreamItem > ( ) ;
538- const client = {
539- [ ORCHESTRATION_WS_METHODS . subscribeShell ] : ( ) => Stream . fromQueue ( events ) ,
540- } as unknown as WsRpcProtocolClient ;
541- const supervisorState = yield * SubscriptionRef . make ( AVAILABLE_CONNECTION_STATE ) ;
542- const activeSession = yield * SubscriptionRef . make < Option . Option < RpcSession . RpcSession > > (
543- Option . some ( session ( client ) ) ,
544- ) ;
545- const supervisor = EnvironmentSupervisor . EnvironmentSupervisor . of ( {
546- target : TARGET ,
547- state : supervisorState ,
548- session : activeSession ,
549- prepared : yield * SubscriptionRef . make ( Option . some ( PREPARED ) ) ,
550- connect : Effect . void ,
551- disconnect : Effect . void ,
552- retryNow : Effect . void ,
553- } satisfies EnvironmentSupervisor . EnvironmentSupervisor [ "Service" ] ) ;
554- const cache = Persistence . EnvironmentCacheStore . of ( {
555- loadShell : ( ) => Effect . succeed ( Option . none ( ) ) ,
556- saveShell : ( ) => Effect . void ,
557- loadThread : ( ) => Effect . succeed ( Option . none ( ) ) ,
558- saveThread : ( ) => Effect . void ,
559- removeThread : ( ) => Effect . void ,
560- loadServerConfig : ( ) => Effect . succeed ( Option . none ( ) ) ,
561- saveServerConfig : ( ) => Effect . void ,
562- loadVcsRefs : ( ) => Effect . succeed ( Option . none ( ) ) ,
563- saveVcsRefs : ( ) => Effect . void ,
564- removeVcsRefs : ( ) => Effect . void ,
565- clearVcsRefs : ( ) => Effect . void ,
566- clear : ( ) => Effect . void ,
567- } ) ;
568- const snapshotLoader = ShellSnapshotLoader . of ( {
569- load : ( ) => Effect . succeed ( Option . none ( ) ) ,
570- } ) ;
571- const shellState = yield * makeEnvironmentShellState ( ) . pipe (
572- Effect . provideService ( EnvironmentSupervisor . EnvironmentSupervisor , supervisor ) ,
573- Effect . provideService ( Persistence . EnvironmentCacheStore , cache ) ,
574- Effect . provideService ( ShellSnapshotLoader , snapshotLoader ) ,
575- ) ;
576- const publications = yield * Ref . make ( 0 ) ;
577- yield * SubscriptionRef . changes ( shellState ) . pipe (
578- Stream . runForEach ( ( ) => Ref . update ( publications , ( count ) => count + 1 ) ) ,
579- Effect . forkScoped ,
580- ) ;
581-
582- yield * Queue . offer ( events , { kind : "snapshot" , snapshot : LIVE_SHELL_SNAPSHOT } ) ;
583- yield * SubscriptionRef . changes ( shellState ) . pipe (
584- Stream . filter ( ( state ) => Option . isSome ( state . snapshot ) ) ,
585- Stream . runHead ,
586- ) ;
587- const before = yield * Ref . get ( publications ) ;
588-
589- // 400 upserts across 25 threads, offered as one backlog batch.
590- const flood = Array . from ( { length : 400 } , ( _ , index ) : OrchestrationShellStreamItem => ( {
591- kind : "thread-upserted" ,
592- sequence : index + 2 ,
593- thread : { id : `thread-${ index % 25 } ` } as never ,
594- } ) ) ;
595- yield * Queue . offerAll ( events , flood ) ;
596- const settled = yield * SubscriptionRef . changes ( shellState ) . pipe (
597- Stream . filter (
598- ( state ) => Option . isSome ( state . snapshot ) && state . snapshot . value . snapshotSequence === 401 ,
599- ) ,
600- Stream . runHead ,
601- ) ;
602-
603- // Every upsert applied: all 25 threads landed at the final sequence.
604- const snapshot = Option . getOrThrow ( Option . getOrThrow ( settled ) . snapshot ) ;
605- expect ( snapshot . threads ) . toHaveLength ( 25 ) ;
606- // The backlog folded into batches instead of one publication per event.
607- for ( let attempt = 0 ; attempt < 10 ; attempt += 1 ) {
608- yield * Effect . yieldNow ;
609- }
610- expect ( ( yield * Ref . get ( publications ) ) - before ) . toBeLessThan ( 50 ) ;
611- } ) ,
612- ) ;
613- it . effect (
614- "yields between shell slices and discards the remaining backlog after a session switch" ,
615- ( ) =>
616- Effect . gen ( function * ( ) {
617- const events = yield * Queue . unbounded < OrchestrationShellStreamItem > ( ) ;
618- const client = {
619- [ ORCHESTRATION_WS_METHODS . subscribeShell ] : ( ) => Stream . fromQueue ( events ) ,
620- } as unknown as WsRpcProtocolClient ;
621- const supervisorState = yield * SubscriptionRef . make ( AVAILABLE_CONNECTION_STATE ) ;
622- const activeSession = yield * SubscriptionRef . make < Option . Option < RpcSession . RpcSession > > (
623- Option . some ( session ( client ) ) ,
624- ) ;
625- const supervisor = EnvironmentSupervisor . EnvironmentSupervisor . of ( {
626- target : TARGET ,
627- state : supervisorState ,
628- session : activeSession ,
629- prepared : yield * SubscriptionRef . make ( Option . some ( PREPARED ) ) ,
630- connect : Effect . void ,
631- disconnect : Effect . void ,
632- retryNow : Effect . void ,
633- } satisfies EnvironmentSupervisor . EnvironmentSupervisor [ "Service" ] ) ;
634- const cache = Persistence . EnvironmentCacheStore . of ( {
635- loadShell : ( ) => Effect . succeed ( Option . none ( ) ) ,
636- saveShell : ( ) => Effect . void ,
637- loadThread : ( ) => Effect . succeed ( Option . none ( ) ) ,
638- saveThread : ( ) => Effect . void ,
639- removeThread : ( ) => Effect . void ,
640- loadServerConfig : ( ) => Effect . succeed ( Option . none ( ) ) ,
641- saveServerConfig : ( ) => Effect . void ,
642- loadVcsRefs : ( ) => Effect . succeed ( Option . none ( ) ) ,
643- saveVcsRefs : ( ) => Effect . void ,
644- removeVcsRefs : ( ) => Effect . void ,
645- clearVcsRefs : ( ) => Effect . void ,
646- clear : ( ) => Effect . void ,
647- } ) ;
648- const snapshotLoader = ShellSnapshotLoader . of ( {
649- load : ( ) => Effect . succeed ( Option . none ( ) ) ,
650- } ) ;
651- const shellState = yield * makeEnvironmentShellState ( ) . pipe (
652- Effect . provideService ( EnvironmentSupervisor . EnvironmentSupervisor , supervisor ) ,
653- Effect . provideService ( Persistence . EnvironmentCacheStore , cache ) ,
654- Effect . provideService ( ShellSnapshotLoader , snapshotLoader ) ,
655- ) ;
656- yield * Queue . offer ( events , { kind : "snapshot" , snapshot : LIVE_SHELL_SNAPSHOT } ) ;
657- yield * SubscriptionRef . changes ( shellState ) . pipe (
658- Stream . filter ( ( state ) => Option . isSome ( state . snapshot ) ) ,
659- Stream . runHead ,
660- ) ;
661- const replacement = session ( {
662- [ ORCHESTRATION_WS_METHODS . subscribeShell ] : ( ) =>
663- Stream . make ( {
664- kind : "snapshot" as const ,
665- snapshot : { ...LIVE_SHELL_SNAPSHOT , snapshotSequence : 1000 } ,
666- } ) . pipe ( Stream . concat ( Stream . never ) ) ,
667- } as unknown as WsRpcProtocolClient ) ;
668- const appliedSequences : number [ ] = [ ] ;
669- let switched = false ;
670- yield * SubscriptionRef . changes ( shellState ) . pipe (
671- Stream . runForEach ( ( state ) =>
672- Effect . gen ( function * ( ) {
673- if ( Option . isNone ( state . snapshot ) ) return ;
674- const sequence = state . snapshot . value . snapshotSequence ;
675- if ( sequence <= 1 || sequence >= 1000 ) return ;
676- appliedSequences . push ( sequence ) ;
677- if ( ! switched ) {
678- switched = true ;
679- yield * SubscriptionRef . set ( activeSession , Option . some ( replacement ) ) ;
680- }
681- } ) ,
682- ) ,
683- Effect . forkScoped ,
684- ) ;
685-
686- // 400 upserts across 25 threads, offered as one backlog batch.
687- const flood = Array . from ( { length : 400 } , ( _ , index ) : OrchestrationShellStreamItem => ( {
688- kind : "thread-upserted" ,
689- sequence : index + 2 ,
690- thread : { id : `thread-${ index % 25 } ` } as never ,
691- } ) ) ;
692- yield * Queue . offerAll ( events , flood ) ;
693- const settled = yield * SubscriptionRef . changes ( shellState ) . pipe (
694- Stream . filter (
695- ( state ) =>
696- Option . isSome ( state . snapshot ) && state . snapshot . value . snapshotSequence === 1000 ,
697- ) ,
698- Stream . runHead ,
699- ) ;
700-
701- const snapshot = Option . getOrThrow ( Option . getOrThrow ( settled ) . snapshot ) ;
702- expect ( snapshot . threads ) . toEqual ( LIVE_SHELL_SNAPSHOT . threads ) ;
703- expect ( appliedSequences . length ) . toBeGreaterThan ( 0 ) ;
704- expect ( Math . max ( ...appliedSequences ) ) . toBeLessThanOrEqual ( 129 ) ;
705- } ) ,
706- ) ;
707454} ) ;
0 commit comments