fix: corrigir memory leaks e testes de mocks
- Fechar ConvexClient antigo antes de criar novo (evita memory leak) - Adicionar flag disposed para prevenir race condition em useEffect - Reduzir polling SSE de 1s para 5s (balanco entre responsividade e carga) - Adicionar .take() aos mocks de testes para compatibilidade 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
638faeb287
commit
508f915cf9
5 changed files with 39 additions and 6 deletions
|
|
@ -83,6 +83,15 @@ async function ensureClient(): Promise<ClientCache> {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fechar cliente antigo antes de criar novo (evita memory leak)
|
||||||
|
if (cached) {
|
||||||
|
try {
|
||||||
|
cached.client.close()
|
||||||
|
} catch {
|
||||||
|
// Ignora erro ao fechar cliente antigo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const client = new ConvexClient(convexUrl)
|
const client = new ConvexClient(convexUrl)
|
||||||
cached = { client, token: data.token, convexUrl }
|
cached = { client, token: data.token, convexUrl }
|
||||||
return cached
|
return cached
|
||||||
|
|
|
||||||
|
|
@ -1090,9 +1090,11 @@ const resolvedAppUrl = useMemo(() => {
|
||||||
|
|
||||||
let prevUnread = 0
|
let prevUnread = 0
|
||||||
let unsub: (() => void) | null = null
|
let unsub: (() => void) | null = null
|
||||||
|
let disposed = false
|
||||||
|
|
||||||
subscribeMachineUpdates(
|
subscribeMachineUpdates(
|
||||||
(payload) => {
|
(payload) => {
|
||||||
if (!payload) return
|
if (disposed || !payload) return
|
||||||
const totalUnread = payload.totalUnread ?? 0
|
const totalUnread = payload.totalUnread ?? 0
|
||||||
const hasSessions = (payload.sessions ?? []).length > 0
|
const hasSessions = (payload.sessions ?? []).length > 0
|
||||||
|
|
||||||
|
|
@ -1107,6 +1109,7 @@ const resolvedAppUrl = useMemo(() => {
|
||||||
prevUnread = totalUnread
|
prevUnread = totalUnread
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
if (disposed) return
|
||||||
console.error("chat updates (Convex) erro:", err)
|
console.error("chat updates (Convex) erro:", err)
|
||||||
const msg = (err?.message || "").toLowerCase()
|
const msg = (err?.message || "").toLowerCase()
|
||||||
if (msg.includes("token de máquina") || msg.includes("revogado") || msg.includes("expirado") || msg.includes("inválido")) {
|
if (msg.includes("token de máquina") || msg.includes("revogado") || msg.includes("expirado") || msg.includes("inválido")) {
|
||||||
|
|
@ -1115,10 +1118,16 @@ const resolvedAppUrl = useMemo(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).then((u) => {
|
).then((u) => {
|
||||||
unsub = u
|
// Se o effect já foi desmontado antes da Promise resolver, cancelar imediatamente
|
||||||
|
if (disposed) {
|
||||||
|
u()
|
||||||
|
} else {
|
||||||
|
unsub = u
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
disposed = true
|
||||||
unsub?.()
|
unsub?.()
|
||||||
}
|
}
|
||||||
}, [token, attemptSelfHeal])
|
}, [token, attemptSelfHeal])
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export async function GET(request: Request) {
|
||||||
sendEvent("heartbeat", { ts: Date.now() })
|
sendEvent("heartbeat", { ts: Date.now() })
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
|
||||||
// Poll interno a cada 1s e push via SSE (responsivo para chat)
|
// Poll interno a cada 5s e push via SSE (balanco entre responsividade e carga)
|
||||||
const pollInterval = setInterval(async () => {
|
const pollInterval = setInterval(async () => {
|
||||||
if (isAborted) {
|
if (isAborted) {
|
||||||
clearInterval(pollInterval)
|
clearInterval(pollInterval)
|
||||||
|
|
@ -118,7 +118,7 @@ export async function GET(request: Request) {
|
||||||
clearInterval(heartbeatInterval)
|
clearInterval(heartbeatInterval)
|
||||||
controller.close()
|
controller.close()
|
||||||
}
|
}
|
||||||
}, 1_000)
|
}, 5_000)
|
||||||
|
|
||||||
// Enviar evento inicial de conexao
|
// Enviar evento inicial de conexao
|
||||||
sendEvent("connected", { ts: Date.now() })
|
sendEvent("connected", { ts: Date.now() })
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,12 @@ describe("convex.machines.getById", () => {
|
||||||
collect: vi.fn(async () => [
|
collect: vi.fn(async () => [
|
||||||
{ revoked: false, expiresAt: FIXED_NOW + 60_000, lastUsedAt: FIXED_NOW - 1000, usageCount: 5 },
|
{ revoked: false, expiresAt: FIXED_NOW + 60_000, lastUsedAt: FIXED_NOW - 1000, usageCount: 5 },
|
||||||
]),
|
]),
|
||||||
|
take: vi.fn(async () => [
|
||||||
|
{ revoked: false, expiresAt: FIXED_NOW + 60_000, lastUsedAt: FIXED_NOW - 1000, usageCount: 5 },
|
||||||
|
]),
|
||||||
})),
|
})),
|
||||||
collect: vi.fn(async () => []),
|
collect: vi.fn(async () => []),
|
||||||
|
take: vi.fn(async () => []),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ function ticketsChain(collection: Doc<"tickets">[]) {
|
||||||
}),
|
}),
|
||||||
order: vi.fn(() => chain),
|
order: vi.fn(() => chain),
|
||||||
collect: vi.fn(async () => collection),
|
collect: vi.fn(async () => collection),
|
||||||
|
take: vi.fn(async (limit: number) => collection.slice(0, limit)),
|
||||||
}
|
}
|
||||||
return chain
|
return chain
|
||||||
}
|
}
|
||||||
|
|
@ -74,9 +75,11 @@ export function createReportsCtx({
|
||||||
cb?.(noopIndexBuilder)
|
cb?.(noopIndexBuilder)
|
||||||
return {
|
return {
|
||||||
collect: vi.fn(async () => queues),
|
collect: vi.fn(async () => queues),
|
||||||
|
take: vi.fn(async (limit: number) => queues.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
collect: vi.fn(async () => queues),
|
collect: vi.fn(async () => queues),
|
||||||
|
take: vi.fn(async (limit: number) => queues.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,9 +89,11 @@ export function createReportsCtx({
|
||||||
cb?.(noopIndexBuilder)
|
cb?.(noopIndexBuilder)
|
||||||
return {
|
return {
|
||||||
collect: vi.fn(async () => categories),
|
collect: vi.fn(async () => categories),
|
||||||
|
take: vi.fn(async (limit: number) => categories.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
collect: vi.fn(async () => categories),
|
collect: vi.fn(async () => categories),
|
||||||
|
take: vi.fn(async (limit: number) => categories.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,8 +108,10 @@ export function createReportsCtx({
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cb?.(builder as { eq: (field: unknown, value: unknown) => unknown })
|
cb?.(builder as { eq: (field: unknown, value: unknown) => unknown })
|
||||||
|
const events = ticketId ? ticketEventsByTicket.get(ticketId) ?? [] : []
|
||||||
return {
|
return {
|
||||||
collect: vi.fn(async () => (ticketId ? ticketEventsByTicket.get(ticketId) ?? [] : [])),
|
collect: vi.fn(async () => events),
|
||||||
|
take: vi.fn(async (limit: number) => events.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -121,8 +128,10 @@ export function createReportsCtx({
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cb?.(builder as { eq: (field: unknown, value: unknown) => unknown })
|
cb?.(builder as { eq: (field: unknown, value: unknown) => unknown })
|
||||||
|
const sessions = agentId ? ticketWorkSessionsByAgent.get(agentId) ?? [] : []
|
||||||
return {
|
return {
|
||||||
collect: vi.fn(async () => (agentId ? ticketWorkSessionsByAgent.get(agentId) ?? [] : [])),
|
collect: vi.fn(async () => sessions),
|
||||||
|
take: vi.fn(async (limit: number) => sessions.slice(0, limit)),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -131,8 +140,10 @@ export function createReportsCtx({
|
||||||
return {
|
return {
|
||||||
withIndex: vi.fn(() => ({
|
withIndex: vi.fn(() => ({
|
||||||
collect: vi.fn(async () => []),
|
collect: vi.fn(async () => []),
|
||||||
|
take: vi.fn(async () => []),
|
||||||
})),
|
})),
|
||||||
collect: vi.fn(async () => []),
|
collect: vi.fn(async () => []),
|
||||||
|
take: vi.fn(async () => []),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue