Saltar al contenido
Development

Recuperé un fix guardado en el stash, y una reproducción en vivo en mi teléfono me mostró un error distinto

Por Victor Da Luz
iosswiftdebuggingdev-logdeep-cut-atlas

La pestaña Playlist en Deep Cut Atlas tenía un error intermitente: a veces, al iniciar, se quedaba trabada para siempre en “Loading playlist…”. Sin error, sin botón de reintentar, nada que hacer salvo forzar el cierre. Una sesión anterior ya lo había diagnosticado y arreglado, con las pruebas pasando y todo, y luego se pausó a mitad de la verificación y guardó el trabajo en el stash. Hoy lo retomé, y se convirtió en dos lecciones separadas.

Recuperar el fix

Aplicar un stash de dos días contra una base de código que tuvo varios commits desde entonces es exactamente el tipo de cosa que esperaba que doliera. No dolió. git stash apply hizo una fusión limpia a tres bandas en los tres archivos tocados, incluyendo uno que había sido modificado ese mismo día para un fix sin relación. Vale la pena recordar: un stash no es una instantánea congelada contra un commit específico, se fusiona igual que una rama.

Un linter que no ve lo que se ve a simple vista

El fix en sí agrega una protección de deduplicación de solicitudes en vuelo, para que dos llamadores concurrentes pidiendo el contenido de la misma playlist compartan una sola solicitud en vez de competir con dos. SwiftLint lo marcó de inmediato: “errors thrown inside this task are not handled”. Excepto que sí se manejan. El resultado se captura, se guarda, y se espera (await) dos líneas después con propagación de errores correcta. Comparé eso con otras dos funciones en el mismo archivo que hacen el patrón idéntico y no fueron marcadas, y la diferencia era académica: esas usan Task.detached, esta usa un Task { } plano. La regla aparentemente compara patrones sobre la sintaxis del inicializador en vez de rastrear si el resultado realmente se consume. Podría haber reestructurado el código para esquivarla, pero eso habría significado reclamar un beneficio de concurrencia (correr desprendido, fuera del actor principal) que ni siquiera aplica acá. Un comentario de deshabilitación acotado y explicado fue la solución más honesta.

La reproducción en vivo que no era el error que buscaba

Acá viene la parte que realmente importó. La verificación scripteada no estaba disponible para este caso, la ruta de automatización de interfaz en el dispositivo estaba caída (una dependencia de WebDriverAgent expiró por timeout, sin relación con este issue), y los lanzamientos scripteados en el dispositivo tampoco mostraban la salida de consola de la app. Así que hice la pasada con el teléfono en mano yo mismo.

Lo que vi: todo trabado. Pero no solo Playlist. History no mostraba portadas de álbum. Discover estaba trabado en su propio estado de carga. Las tres, al mismo tiempo.

Mi primer instinto fue un poco de pánico, ¿no funcionó el fix? Pero el error que había arreglado era acotado y específico: una carrera entre dos llamadas asíncronas particulares que ambas querían los datos de la misma playlist. Nunca tocaba el código de Discover en absoluto. Algo trabado simultáneamente en tres pestañas sin relación apuntaba a un problema distinto y más grande, no a una refutación del acotado.

Unas pocas verificaciones descartaron cosas rápido. El proceso de la app seguía vivo, no se había cerrado. El wifi estaba bien. La app real de Apple Music funcionaba perfecto en el mismo teléfono, la misma red, el mismo momento. Y no se disparó ninguna notificación de bloqueo, lo cual resultó ser una pista útil en sí misma en vez de un callejón sin salida: el detector de bloqueos de iOS vigila específicamente el hilo principal, así que su silencio sugería que la interfaz en sí no estaba congelada, algo asíncrono simplemente nunca terminaba.

No tengo un stack trace. Conseguir uno necesita el debugger de Xcode conectado en vivo. Así que en vez de adivinar una causa raíz que no podía ver, lo separé en su propia investigación y publiqué el fix que sí podía verificar en sus propios términos: probado, revisado en código, comparado por patrón contra un mecanismo idéntico que ya corre en producción en otra parte del mismo archivo. El error que apareció en el teléfono es real y necesita perfilado real, no una conjetura pegada a un fix sin relación solo porque apareció durante la misma sesión de pruebas.

Qué sigue

El fix acotado ya está fusionado. El bloqueo más amplio (las tres pestañas, sin cierre, sin notificación de bloqueo, la app real de Apple Music sin afectar) ahora es su propia investigación abierta, a la espera de una sesión de Instruments o un debugger conectado en vivo para ver realmente dónde se atasca el trabajo asíncrono.

Lecturas relacionadas