okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
cba5ff8
main
1"""Unread TCP streams cannot retain unbounded activity or block model completion.""" 2 3import json 4import threading 5import unittest 6 7from harness import Albedo ,Provider ,exclusive ,text 8from stream_support import StreamProbe ,unread_stream 9 10 11# exclusive: enables daemon inspection at startup and measures global subscribers 12@exclusive 13class StreamPressureTest (unittest .TestCase ): 14def setUp (self ): 15self .provider = Provider (lambda _ :text ("model completed under pressure" )) 16self .addCleanup (self .provider .close ) 17self .app = Albedo ( 18self .provider , 19prepare = lambda app :app .daemon .env .update (ALBEDO_INSPECT = "1" ), 20 ) 21self .app .__enter__ () 22self .addCleanup (self .app .__exit__ ,None ,None ,None ) 23self .probe = StreamProbe (self .app ) 24 25def test_stalled_agents_feed_is_bounded_while_responsive_feed_and_model_complete ( 26self , 27 ): 28sessions = [self .app .session ()for _ in range (8 )] 29stalled = unread_stream (self .app ,"/sessions?scope=all" ) 30self .addCleanup (stalled .close ) 31observed = [] 32ready = threading .Event () 33completed = threading .Event () 34 35def consume (): 36with self .app .api ( 37"/sessions?scope=all" ,headers = {"Accept" :"text/event-stream" } 38 )as response : 39for line in response : 40if line .startswith (b"data: " ): 41events = json .loads (line [6 :])["events" ] 42observed .extend (events ) 43ready .set () 44if any ( 45event ["type" ]== "activity" 46and any ( 47row ["text" ]== "model completed under pressure" 48for row in event ["data" ]["activity" ]["lines" ] 49 ) 50for event in events 51 ): 52completed .set () 53return 54 55threading .Thread (target = consume ,daemon = True ).start () 56self .assertTrue (ready .wait (5 ),"responsive feed never subscribed" ) 57ids = "[" + "," .join (f"<< { json . dumps ( session ) } >>" for session in sessions )+ "]" 58measured = self .probe .call ("pressure_json" ,f"[ { ids } ]" ) 59self .assertTrue (measured ["pressure" ],measured ) 60self .assertEqual (measured ["blocked" ],1 ,measured ) 61self .app .prompt ( 62sessions [0 ],"complete while another subscriber is stalled" 63 ).close () 64self .app .idle (sessions [0 ]) 65self .assertTrue (completed .wait (5 ),"responsive feed lost model completion" ) 66self .assertLessEqual (measured ["count" ],256 ,measured ) 67self .assertLessEqual (measured ["bytes" ],1048576 ,measured ) 68self .assertLessEqual (measured ["mailbox_bytes" ],1024 ,measured ) 69self .assertLessEqual (measured ["mailbox" ],3 ,measured ) 70pressure_events = [ 71event 72for event in observed 73if event ["type" ]== "activity" 74and event ["data" ]["activity" ]["lines" ] 75and event ["data" ]["activity" ]["lines" ][0 ]["text" ].startswith ("pressure:" ) 76 ] 77self .assertEqual (len (pressure_events ),measured ["published" ]) 78for session in sessions : 79self .assertEqual ( 80 [ 81int (event ["data" ]["activity" ]["lines" ][0 ]["text" ].split (":" ,3 )[2 ]) 82for event in pressure_events 83if event ["data" ]["session_id" ]== session 84 ], 85list (range (1 ,measured ["published" ]// len (sessions )+ 1 )), 86 ) 87self .assertFalse (any (event ["type" ]== "overflow" for event in observed )) 88with self .app .api (f"/sessions/ { sessions [ 0 ] } ?tail=0" )as response : 89snapshot = json .load (response ) 90self .assertEqual (snapshot ["status" ]["phase" ],"idle" ) 91stalled .close () 92self .assertEqual ( 93self .probe .call ("removed_json" ), {"subscribers" :0 ,"queues" :0 } 94 ) 95 96def test_idle_agents_disconnect_removes_subscription_without_publication (self ): 97connection = unread_stream (self .app ,"/sessions?scope=all" ) 98self .assertEqual (self .probe .call ("subscriptions_json" )["subscribers" ],1 ) 99connection .close () 100self .assertEqual ( 101self .probe .call ("removed_json" ), {"subscribers" :0 ,"queues" :0 } 102 ) 103 104def test_agent_count_byte_and_oversize_limits_emit_only_overflow_then_close (self ): 105session = self .app .session () 106for name ,count ,size in ( 107 ("count" ,257 ,16 ), 108 ("bytes" ,128 ,16384 ), 109 ("oversize" ,1 ,1048577 ), 110 ): 111with ( 112self .subTest (limit = name ), 113self .app .api ( 114"/sessions?scope=all" ,headers = {"Accept" :"text/event-stream" } 115 )as response , 116 ): 117initial = next (line for line in response if line .startswith (b"data: " )) 118self .assertEqual (json .loads (initial [6 :])["events" ][0 ]["type" ],"ready" ) 119measured = self .probe .call ( 120"burst_json" ,f"[[<< { json . dumps ( session ) } >>], { count } , { size } ]" 121 ) 122batches = [ 123json .loads (line [6 :]) 124for line in response 125if line .startswith (b"data: " ) 126 ] 127self .assertEqual ( 128batches , [{"events" : [{"type" :"overflow" ,"data" : {}}]}] 129 ) 130self .assertEqual (measured ["overflow" ],1 ,measured ) 131self .assertLessEqual (measured ["count" ],256 ,measured ) 132self .assertLessEqual (measured ["bytes" ],1048576 ,measured ) 133if name == "count" : 134self .assertEqual (measured ["count" ],256 ,measured ) 135elif name == "bytes" : 136self .assertLess (measured ["count" ],256 ,measured ) 137else : 138self .assertEqual (measured ["bytes" ],0 ,measured ) 139self .assertEqual ( 140self .probe .call ("removed_json" ), {"subscribers" :0 ,"queues" :0 } 141 ) 142self .app .prompt (session ,"model continues after overflow" ).close () 143self .app .idle (session ) 144with self .app .api (f"/sessions/ { session } ?tail=0" )as response : 145snapshot = json .load (response ) 146self .assertEqual (snapshot ["status" ]["phase" ],"idle" ) 147 148def test_stalled_session_has_one_wake_and_reconnect_resets_durable_history (self ): 149session = self .app .session () 150before = self .app .stream_page (session ) 151connection = unread_stream (self .app ,f"/sessions/ { session } " ) 152self .addCleanup (connection .close ) 153self .provider .chunk_size = 4096 154self .provider .script = lambda _ :text ("session pressure " * 250000 ,delay = 0.002 ) 155self .app .prompt (session ,"durable prompt under session pressure" ).close () 156measured = self .probe .call ( 157"session_pressure_json" ,f"[<< { json . dumps ( session ) } >>]" 158 ) 159self .assertEqual (measured ["blocked" ],1 ,measured ) 160self .assertLessEqual (measured ["wakes" ],1 ,measured ) 161self .app .idle (session ) 162recovered = self .app .stream_page (session ,before ,tail = 2 ) 163self .assertEqual (recovered ["generation" ],before ["generation" ]) 164self .assertEqual (recovered ["events" ][0 ]["type" ],"reset" ) 165snapshot = recovered ["snapshot" ] 166self .assertIsNotNone (snapshot ["history" ]["older" ]) 167earliest = min (entry ["position" ]for entry in snapshot ["history" ]["items" ]) 168with self .app .api ( 169f"/sessions/ { session } /history?before= { earliest } &limit=2" 170 )as response : 171earlier = json .load (response ) 172self .assertIn ( 173"durable prompt under session pressure" , 174 [ 175part ["text" ] 176for entry in earlier ["items" ] 177if entry ["kind" ]== "user" 178for part in entry ["content" ] 179if part ["kind" ]== "text" 180 ], 181 ) 182self .assertLessEqual (len (json .dumps (recovered ).encode ()),1048576 ) 183 184def test_idle_session_disconnect_removes_watcher_without_publication (self ): 185session = self .app .session () 186connection = unread_stream (self .app ,f"/sessions/ { session } " ) 187connection .close () 188measured = self .probe .call ( 189"session_removed_json" ,f"[<< { json . dumps ( session ) } >>]" 190 ) 191self .assertEqual (measured ["watchers" ],0 ,measured )