fix(sprint-5/etapa-4e): lockAcquireTimeout 30s + eliminar getSessionSafe — corta freeze en guardar perfil
getSessionSafe() tenía un problema de diseño: Promise.race([getSession(), timeout]) abandona la Promise de getSession() cuando gana el timeout. La Promise abandonada deja una operación en PROCESS_LOCKS[name] que bloquea el chain. Con lockAcquireTimeout=5s (default), un token refresh lento dispara ProcessLockAcquireTimeoutError cuyo handler hace await previousOperation (colgada) → freeze permanente. Fix: lockAcquireTimeout: 30000 (margen para redes lentas) + eliminar getSessionSafe() de supabase.ts, library-store, process-store y useReportData. Las queries Supabase llaman getSession() internamente vía _getAccessToken() que espera initializePromise automáticamente — la barrera era redundante. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { getSessionSafe } from '@/lib/supabase'
|
||||
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
||||
import { supabaseResourceRepo } from '@/persistence/supabase/resource-repo'
|
||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||
@@ -30,8 +29,6 @@ export function useReportData(processId: string): ReportData {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
// Barrera única con timeout: garantiza sesión restaurada antes del fan-out en paralelo
|
||||
await getSessionSafe()
|
||||
const [proc, sim, acts, res] = await Promise.all([
|
||||
supabaseProcessRepo.getById(processId),
|
||||
supabaseSimulationRepo.getLatestByProcess(processId),
|
||||
|
||||
@@ -18,16 +18,10 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
||||
// navigator.locks) — evita el deadlock a costa de no sincronizar refresh entre tabs,
|
||||
// lo cual es aceptable para este producto (uso mayormente single-tab).
|
||||
lock: processLock,
|
||||
// Aumentado de 5000 (default) a 30000ms.
|
||||
// Con 5s, un token refresh lento dispara ProcessLockAcquireTimeoutError, cuyo handler
|
||||
// hace await previousOperation (colgada) → chain bloqueado permanente.
|
||||
// 30s da margen para refresh en redes lentas o cold starts de Supabase Auth.
|
||||
lockAcquireTimeout: 30000,
|
||||
},
|
||||
})
|
||||
|
||||
// supabase.auth.getSession() puede colgarse indefinidamente si el cliente queda
|
||||
// esperando un lock interno (navigator.locks) que nunca se libera. Esta barrera
|
||||
// nunca bloquea más de 4s: si no resuelve a tiempo, sigue de largo sin sesión confirmada
|
||||
// en vez de dejar la UI con un spinner/skeleton infinito.
|
||||
export async function getSessionSafe(): Promise<void> {
|
||||
await Promise.race([
|
||||
supabase.auth.getSession(),
|
||||
new Promise((resolve) => setTimeout(resolve, 4000)),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { create } from 'zustand'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { getSessionSafe } from '@/lib/supabase'
|
||||
import { supabaseProcessRepo, type ProcessWithUserNames } from '@/persistence/supabase/process-repo'
|
||||
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
|
||||
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
|
||||
@@ -42,11 +41,6 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
|
||||
load: async () => {
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
// Barrera única: garantiza que el cliente Supabase tenga la sesión restaurada
|
||||
// antes de salir a buscar datos en paralelo (evita 0 filas por RLS en el primer load).
|
||||
// Con timeout defensivo: si el cliente se cuelga, no bloquea la UI más de 4s.
|
||||
await getSessionSafe()
|
||||
|
||||
// Timeout global: si el cliente Supabase queda colgado (ej. lock interno nunca liberado),
|
||||
// isLoading nunca debe quedar en true para siempre — la UI debe poder mostrar error y reintentar.
|
||||
const [groups, processes, settings] = await Promise.race([
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { create } from 'zustand'
|
||||
import { getSessionSafe } from '@/lib/supabase'
|
||||
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
|
||||
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
|
||||
import { supabaseGatewayRepo } from '@/persistence/supabase/gateway-repo'
|
||||
@@ -36,9 +35,6 @@ export const useProcessStore = create<ProcessState>((set, get) => ({
|
||||
loadProcess: async (processId) => {
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
// Barrera única con timeout: garantiza sesión restaurada antes del fan-out en paralelo
|
||||
// sin bloquear isLoading indefinidamente si el cliente Supabase se cuelga (ver Sprint 4 Etapa 5B)
|
||||
await getSessionSafe()
|
||||
const [process, activities, gateways, resources] = await Promise.all([
|
||||
supabaseProcessRepo.getById(processId),
|
||||
supabaseActivityRepo.getByProcess(processId),
|
||||
@@ -54,7 +50,6 @@ export const useProcessStore = create<ProcessState>((set, get) => ({
|
||||
|
||||
setCurrentProcess: async (process, userId) => {
|
||||
await supabaseProcessRepo.save(process, userId)
|
||||
await getSessionSafe()
|
||||
const [activities, gateways, resources] = await Promise.all([
|
||||
supabaseActivityRepo.getByProcess(process.id),
|
||||
supabaseGatewayRepo.getByProcess(process.id),
|
||||
|
||||
Reference in New Issue
Block a user