De chat in leven houden als een telefoon slaapt
De agent van Mythex kan minuten aan één verzoek werken, en je kijkt misschien mee vanaf een telefoon, die vergrendelt, van app wisselt en van netwerk verandert terwijl hij werkt. Twee bugs maakten dat mis. In de ene behandelde de chat elk signaal “de pagina is weer zichtbaar” als bewijs dat zijn verbinding was weggevallen, en lusde hij tot hij bleef hangen op “Connecting…”. In de andere kwam een telefoon die de eerste reactie van een nieuwe chat had doorgeslapen terug met alleen de eerste paar stukjes ervan, tot je de pagina handmatig ververste. Beide zijn opgelost.
Hoe de chat een lopende beurt volgt
Terwijl de agent werkt, houdt de chat een stream open vanaf onze servers: elke stap, een gedachte, een toolaanroep, een stukje van het antwoord, komt binnen als een event. Als de stream wegvalt, maakt de chat opnieuw verbinding en speelt de server recente events opnieuw af uit een buffer die de laatste 120 seconden bewaart. Als de beurt eindigt, vraagt de chat de server ook om de opgeslagen geschiedenis en gelijkt wat hij toonde af met wat is opgeslagen. Die laatste stap vangt alles op wat de stream heeft gemist.
Om in rust een levende verbinding van een dode te onderscheiden, stuurt de server elke 15 seconden een klein “ping”-signaal.
Bug 1: wakker worden doodde een gezonde stream
Browsers laten een pagina weten wanneer ze weer zichtbaar wordt: na een tabbladwissel, na het ontgrendelen van het scherm. Wij gebruikten dat als aanleiding om de verbinding te controleren, en in de praktijk om de stream af te breken en opnieuw te verbinden, in de veronderstelling dat hij tijdens het verborgen zijn was gestorven.
Op telefoons en in ingesloten browsers vuren die events veel vaker dan streams wegvallen: bij elke tabbladwissel, elke ontgrendeling, en in sommige ingesloten browsers om de paar seconden. Elk daarvan brak een volkomen gezonde stream af. De chat maakte opnieuw verbinding, speelde opnieuw af, werd opnieuw afgebroken, en bleef uiteindelijk hangen op “Connecting…”. Daarna meldde hij dat de beurt was geëindigd terwijl de agent nog aan het werk was.
De oplossing: beoordeel de stream op of hij nog praat
De chat registreert nu wanneer de stream van elk project voor het laatst iets heeft ontvangen, zowel events als de pings van 15 seconden. Een “weer zichtbaar”-signaal breekt alleen een stream af die 25 seconden stil is geweest: langer dan het pinginterval plus een trage netwerkhop, zodat een levende stream nooit in aanmerking komt. Tests controleren dat een ping telt als teken van leven, en dat een stream die net heeft gepingd niet als dood wordt beschouwd.
De regel die we eruit hebben gehaald: de zichtbaarheid van een pagina zegt iets over de pagina, niet over de netwerkverbinding. Vraag het aan de verbinding.
Bug 2: een half antwoord nadat de telefoon wakker werd
Deze konden we altijd opnieuw reproduceren in de iOS-simulator:
- Start een nieuwe chat en stuur een verzoek dat even duurt.
- Laat de telefoon de build doorslapen.
- Kom terug nadat de beurt is afgelopen en de replay-buffer van twee minuten is verlopen.
De chat toonde de eerste paar blokken van het antwoord, gemarkeerd als klaar, en bleef zo staan tot je de pagina ververste.
Waarom
Een nieuwe chat heeft nog geen thread, de opgeslagen gespreksgeschiedenis, tot het eerste bericht die aanmaakt. Dat gebeurt in de verzendfunctie, nadat send de reconcile-stap al had vastgelegd, en die dacht nog dat er geen thread was. Dus aan het einde van de eerste beurt in een nieuwe chat zag reconcile geen thread en keerde hij stilletjes terug zonder iets op te halen.
Meestal viel dat niemand op, omdat de stream de hele beurt toch had geleverd. Maar een telefoon die de beurt had doorgeslapen en pas terugkwam nadat de replay-buffer van 120 seconden was verlopen, kon de ontbrekende events niet uit de stream halen, en de reconcile die het gat had moeten dichten was nooit gelopen.
De oplossing
- Send vertelt reconcile bij welke thread de beurt hoort, zodat de eerste beurt in een nieuwe chat net zo wordt afgestemd als elke andere. Als je inmiddels een andere chat hebt geopend voordat het antwoord terugkomt, wordt het resultaat weggegooid in plaats van op de verkeerde plek getoond.
- Een mislukte reconcile wordt opnieuw geprobeerd. Een telefoon die op een slechte verbinding wakker wordt, kan ontdekken dat de beurt voorbij is en het dan niet kunnen ophalen. Dat wordt nu onthouden, en de chat vraagt het opnieuw wanneer het tabblad weer getoond wordt of het netwerk terugkomt.
- “Worked for” is eerlijk. Elk antwoord toont hoe lang de agent heeft gewerkt. Als het tabblad het eindbericht van de server nooit heeft gezien, vulde het dat vroeger aan met zijn eigen klok, die liep vanaf Send tot de telefoon wakker werd, inclusief de tijd dat je weg was. Die duur wordt nu als schatting gemarkeerd, en vervangen door de opgeslagen duur zodra de geschiedenis is binnengekomen.
Wat we hieruit hebben geleerd
- Test op het apparaat dat mensen echt gebruiken, op de manier waarop ze het gebruiken. Beide bugs zaten in het gat tussen “een stream die werkt” en “een telefoon in een zak”.
- Een terugvaloptie die nooit draait, is geen terugvaloptie. De reconcile-stap bestond juist om dit geval op te vangen, en sloeg het stilletjes over.
- Probeer ook het herstel opnieuw. Het moment dat een telefoon wakker wordt, is vaak het moment dat zijn verbinding het slechtst is.
- Toon geen gok als feit. Een duur gemeten met de verkeerde klok is erger dan geen duur; markeer hem als schatting of vervang hem.
Hetzelfde werk heeft ook een paar dingen opgeruimd die je ziet terwijl een beurt loopt: shellstappen worden benoemd naar het commando dat het werk doet in plaats van cd, screenshots in de chat behouden de linkerbovenhoek van de pagina en passen in de kolom, en een nieuw project krijgt zijn gegenereerde naam zonder te verversen.
Als je je eigen app voor telefoons bouwt, behandelen onze gidsen over een mobielvriendelijke webapp bouwen en native versus progressive web apps de basis. Meer over de chat zelf staat in de chatdocumentatie.