La pestaña Discover que quedó atascada excavando para siempre
Llegó un reporte de error con una captura de pantalla: la pestaña Discover estaba congelada en “Buscando cortes ocultos / Revisando los artistas de la biblioteca en busca de lanzamientos que todavía no se tienen”, con un enlace “Seguir buscando” que no hacía nada, sin importar cuántas veces se tocara. Este se convirtió en un error de dos capas, y la segunda capa solo salió a la luz porque una prueba de regresión la atrapó antes de publicar nada.
Problema
Discover recorre una biblioteca de forma incremental: revisa un lote de artistas, guarda en caché lo nuevo, recuerda cuándo se revisó cada artista por última vez, y solo lo vuelve a revisar una vez que quedó desactualizado. La pestaña muestra “Buscando cortes ocultos” mientras algún artista siga sin revisarse, y cambia a un feed de resultados o a un estado vacío de “todo al día” una vez que todo se revisó al menos una vez.
“Atascado para siempre, reintentar no ayuda” es una forma muy específica de error. Suele significar que algo que se supone debería cambiar entre intentos en realidad no está cambiando.
Por qué este enfoque
Seguí el camino real del código en vez de adivinar inestabilidad de MusicKit. El mecanismo que decide “¿ya se revisó todo?” solo se actualiza cuando la obtención del catálogo de un artista tiene éxito. Una obtención fallida simplemente incrementa un contador y sigue, el artista queda completamente ausente de la caché, lo que hace que la búsqueda “¿ya se revisó este artista?” lo trate como “nunca revisado, el más viejo, hay que revisarlo primero”. Cada actualización lo vuelve a elegir, vuelve a fallar (por la razón que sea que falló la primera vez), y la revisión de “¿ya está todo cubierto?” nunca puede llegar al 100% si aunque sea un solo artista nunca logra completar una obtención con éxito. Eso es exactamente “atascado sin importar cuántas veces se toque”.
El disparador real más probable: un artista en una biblioteca que MusicKit no puede resolver limpiamente a un artista de catálogo, un artista autopublicado o emparejado con iTunes, algo fuera de los casos prolijos que cubren los datos de prueba simulados. Ese modo de falla simplemente no existe en una biblioteca de prueba pequeña y armada a mano, así que nada lo atrapó hasta que lo hizo un dispositivo real con una biblioteca real (más grande y desordenada).
Implementación
La solución: una obtención fallida igual debería contar como “revisada”, solo que sin resultados nuevos, reutilizando la misma ventana de 7 días de desactualización que la app ya tiene como mecanismo natural de “reintentar más tarde”, en vez de inventar un nuevo concepto de contador de reintentos o backoff. Un artista crónicamente problemático rota fuera de “siempre primero” y se vuelve a revisar automáticamente más adelante, igual que cualquier otro artista que simplemente quedó desactualizado.
Escribí la solución, corrí las pruebas, y una de mis pruebas nuevas falló. Bien, para eso sirve exactamente una prueba de regresión nueva. Resultó que había una segunda protección preexistente: “si todos los artistas de un lote fallaron, tratar todo como catastrófico y no cachear nada”. Esa protección es legítima para un arranque en frío real donde toda la tubería está rota (sin red, autenticación revocada), no conviene cachear “nada” en silencio y darlo por terminado. Pero una vez que la mayor parte de la biblioteca ya tuvo éxito, la rotación naturalmente se reduce a solo los artistas problemáticos restantes. Con el tiempo un lote termina compuesto enteramente por ese único artista malo, lo cual es entonces el 100% de las fallas de ese lote, lo cual activa la misma protección “catastrófica”, y lanza un error antes de que siquiera se ejecute la actualización de caché de mi solución.
La solución real necesitaba las dos piezas: marcar las fallas como revisadas, Y hacer que la protección de falla catastrófica solo se active cuando no haya habido ninguna cobertura exitosa previa (un arranque en frío genuino de tubería rota), no simplemente porque “este lote pequeño en particular resultó fallar por completo”.
Trampas
Este es el tipo de error básicamente invisible desde una prueba de una sola ronda. “Un artista falla, los demás tienen éxito, la cobertura avanza” pasa bien en la primera revisión. La ruptura solo aparece en la segunda ronda, una vez que el lote se reduce hasta quedar exactamente con el artista problemático sobrante, un escenario que ninguna de las pruebas existentes construía, porque se habían escrito para demostrar que la función funciona, no para demostrar que sobrevive a un elemento persistentemente malo a través de múltiples rotaciones. Escribí una prueba nueva que ejercita específicamente “el mismo artista falla en cada actualización, a través de múltiples llamadas”, eso fue lo que realmente atrapó la segunda protección.
Resultados
Compilé, instalé, y lancé la solución en un dispositivo físico personalmente (vía devicectl, no simulador, MusicKit no funciona ahí en absoluto) y confirmé que Discover ahora muestra lanzamientos reales en vez de quedar atascado. Suite completa: 165/165, incluyendo las dos pruebas nuevas que ejercitan específicamente un artista crónicamente fallido a través de múltiples rondas.
Hallazgo extra de la misma pasada por el dispositivo, sin relación con este error: tocar una tarjeta de lanzamiento de Discover no hace absolutamente nada. Revisé el código, el propio comentario de documentación de la tarjeta dice literalmente que la acción de agregar a lista de reproducción debía publicarse junto con la acción de deslizar “No me interesa”, y solo se publicó la mitad de eso. Se registró por separado en vez de meterlo en esta solución, ya que es un vacío de función real con sus propias preguntas de diseño, no un error de una línea.
Lecturas relacionadas
La ventana espejada mintió sobre que el teléfono estaba desbloqueado
Un arreglo de agrupación de una línea que ya estaba escrito, y tres muros entre eso y la prueba: aislamiento de actores, el alcance exclusivo a Mac de log stream, y una sesión espejada que parece desbloqueada cuando el dispositivo no lo está.
Álbumes de preestreno, y la solución que no pude verificar del todo
Los marcadores de posición 'Track N' de Apple se mostraban como si fueran datos reales. La única señal de detección que no pude confirmar se convirtió en la única señal de la que dejé de depender.
Un botón de suscripción, una API de MusicKit que nunca había usado, y una sheet que se cierra en silencio
musicSubscriptionOffer presenta la sheet nativa de Apple, y nada en la app se enteraría jamás de que se cerró. La acción correctamente codificada que igual era un callejón sin salida funcional.