DeepSeek Harness: Auto-Limpeza e ctx.effect()
Última atualização: 2026-08-31
Um dos designs mais poderosos do Cordis é a auto-limpeza — quando um plugin é descarregado, todos os recursos registrados através de ctx são automaticamente recuperados sem necessidade de liberação manual. Mas quando recursos não são gerenciados diretamente por ctx, ctx.effect() é seu ponto de entrada para limpeza manual.
📋 Pré-requisitos: Ter completado 11-first-plugin.md, entender a função apply e Context
1. O Que Você Vai Aprender
- Princípio de auto-limpeza: todos os recursos registrados através de ctx são automaticamente recuperados
ctx.effect(): registro de limpeza manual de recursos- Padrão de função de limpeza retornada
- Limpeza adequada de setInterval/setTimeout
- Padrões de limpeza de conexão de rede
- Erros comuns de limpeza e como evitá-los
2. Princípio de Auto-Limpeza
(1) ctx É o Centro de Registro de Recursos
Cada instância ctx mantém um registro gravando todos os recursos limáveis registrados pelo plugin:
class Context {
private _disposables: Disposable[] = []
register(disposable: Disposable) {
this._disposables.push(disposable)
}
dispose() {
for (const d of this._disposables.reverse()) {
d.dispose()
}
}
}
Quando o plugin é descarregado, ctx.dispose() recupera todos os recursos em ordem inversa de registro.
(2) Tipos de Recursos Auto-Limáveis
Os seguintes recursos registrados através de ctx são todos automaticamente limados:
| Método de Registro | Comportamento de Limpeza |
|---|---|
ctx.on('event', handler) |
Remove listener de evento |
ctx.setInterval(fn, ms) |
Limpa timer |
ctx.setTimeout(fn, ms) |
Limpa timer |
ctx.command('name') |
Desregistra comando |
ctx.service('name', impl) |
Desregistra serviço |
(3) Exemplo de Auto-Limpeza
▶ Exemplo 1: Auto-Limpeza de Recursos
import { Context } from '@deepseek-ai/cordis'
export const name = 'auto-cleanup-demo'
export function apply(ctx: Context) {
// Todos os registros abaixo são automaticamente limados
ctx.on('session/created', (s) => {
ctx.logger.info(`session: ${s.id}`)
})
ctx.setInterval(() => {
ctx.logger.info('tick')
}, 10000)
ctx.command('demo')
.action(() => 'demo command')
}
// Ao descarregar o plugin: listener removido + timer limpo + comando desregistrado, zero código manual
3. ctx.effect(): Limpeza Manual de Recursos
(1) Por Que a Limpeza Manual É Necessária
Nem todos os recursos podem ser registrados diretamente através de ctx. Por exemplo:
- Conexões criadas por bibliotecas de terceiros (ex.: WebSocket, pools de conexão de banco de dados)
- Recursos criados por APIs nativas do Node.js (ex.:
net.Server) - Modificações de estado global (ex.: variáveis
process.envtemporárias)
É aqui que ctx.effect() entra:
ctx.effect(() => {
// Retorna uma função de limpeza
return () => {
// Lógica de limpeza
}
})
(2) Padrão de Uso do ctx.effect()
▶ Exemplo 2: Limpeza Manual com ctx.effect()
import { Context } from '@deepseek-ai/cordis'
export const name = 'manual-cleanup'
export function apply(ctx: Context) {
const connection = createExternalConnection()
ctx.effect(() => {
return () => {
connection.close()
ctx.logger.info('connection closed')
}
})
}
ctx.effect() recebe uma função factory que retorna uma função de limpeza. Quando o plugin é descarregado, Cordis chama a função de limpeza para liberar recursos.
(3) Dois Estilos de Uso
// Estilo 1: Retornar função de limpeza (recomendado)
ctx.effect(() => {
const ws = new WebSocket('ws://localhost:8080')
return () => ws.close()
})
// Estilo 2: Passar referência de função de limpeza
const cleanup = () => { /* ... */ }
ctx.effect(cleanup)
A vantagem do Estilo 1 é que criação e limpeza do recurso estão na mesma closure, mantendo a lógica coesa.
4. Padrão de Função de Limpeza Retornada
(1) Padrão Padrão
ctx.effect(() => {
const resource = acquireResource()
return () => {
releaseResource(resource)
}
})
Este padrão "adquirir-liberar" é similar ao try-finally:
// Modelo mental equivalente try-finally
try {
const resource = acquireResource()
// Usar recurso
} finally {
releaseResource(resource)
}
(2) Limpando Múltiplos Recursos
export function apply(ctx: Context) {
ctx.effect(() => {
const db = openDatabase()
const cache = openCache()
return () => {
cache.close() // Fechar dependente primeiro
db.close() // Depois fechar a dependência
}
})
}
⚠️ A ordem de limpeza importa — feche primeiro objetos que dependem de outros recursos, depois feche os recursos dos quais dependem.
(3) Tratamento de Erros em Funções de Limpeza
ctx.effect(() => {
const conn = createConnection()
return () => {
try {
conn.close()
} catch (e) {
ctx.logger.warn('cleanup error:', e)
}
}
})
Exceções em funções de limpeza não devem interromper a limpeza de outros recursos. Cordis internamente tem proteção try-catch para cada função de limpeza, mas tratamento explícito é mais seguro.
5. Limpeza Adequada de setInterval/setTimeout
(1) Método de Auto-Limpeza (Recomendado)
export function apply(ctx: Context) {
// Usar ctx.setInterval — auto-limpeza
ctx.setInterval(() => {
ctx.logger.info('heartbeat')
}, 30000)
}
(2) API Nativa + ctx.effect()
Se precisar usar o setInterval nativo:
export function apply(ctx: Context) {
const timer = setInterval(() => {
ctx.logger.info('heartbeat')
}, 30000)
ctx.effect(() => {
return () => clearInterval(timer)
})
}
(3) Comparação
| Método | Quantidade de Código | Confiabilidade | Recomendado |
|---|---|---|---|
ctx.setInterval |
1 linha | Alta (automático) | ✅ |
Nativo + ctx.effect() |
3 linhas | Média (manual) | ⚠️ |
| Nativo (sem limpeza) | 1 linha | Baixa (vazamento) | ❌ |
(4) Armadilha do setTimeout
// ❌ Errado: setTimeout ainda dispara após descarregamento
export function apply(ctx: Context) {
setTimeout(() => {
ctx.logger.info('delayed action') // Plugin pode já estar descarregado!
}, 5000)
}
// ✅ Correto: usar ctx.setTimeout
export function apply(ctx: Context) {
ctx.setTimeout(() => {
ctx.logger.info('delayed action') // Não dispara após descarregamento
}, 5000)
}
6. Padrões de Limpeza de Conexão de Rede
(1) Servidor HTTP
▶ Exemplo 3: Limpeza de Conexão HTTP
import { createServer } from 'http'
export function apply(ctx: Context) {
const server = createServer((req, res) => {
res.end('ok')
})
server.listen(3456)
ctx.effect(() => {
return () => {
server.close()
ctx.logger.info('HTTP server closed')
}
})
}
(2) Conexão WebSocket
import WebSocket from 'ws'
export function apply(ctx: Context) {
const ws = new WebSocket('ws://localhost:8080')
ws.on('open', () => {
ctx.logger.info('ws connected')
})
ctx.effect(() => {
return () => {
if (ws.readyState === WebSocket.OPEN) {
ws.close()
}
}
})
}
(3) Pool de Conexão de Banco de Dados
import { Pool } from 'pg'
export function apply(ctx: Context) {
const pool = new Pool({
connectionString: 'postgresql://localhost/mydb',
max: 10
})
ctx.effect(() => {
return async () => {
await pool.end()
ctx.logger.info('db pool closed')
}
})
}
⚠️ Funções de limpeza podem ser async. Cordis aguardará a conclusão da limpeza assíncrona antes de continuar com a limpeza subsequente.
(4) Limpeza de Listener de Eventos
export function apply(ctx: Context) {
const emitter = getExternalEmitter()
const handler = (data: any) => {
ctx.logger.info('event:', data)
}
emitter.on('data', handler)
ctx.effect(() => {
return () => {
emitter.off('data', handler)
}
})
}
7. Erros Comuns de Limpeza
(1) Esquecer de Registrar Limpeza
// ❌ Vazamento: timer ainda executa após descarregamento
export function apply(ctx: Context) {
setInterval(() => {
console.log('orphan timer')
}, 1000)
}
Correção: Use ctx.setInterval ou registre ctx.effect().
(2) Ordem Errada de Limpeza
// ❌ Fechar banco de dados primeiro, depois fechar cache que depende dele
ctx.effect(() => {
const db = openDB()
const cache = new Cache(db)
return () => {
db.close() // Fechou db primeiro
cache.close() // Cache internamente acessa db → erro
}
})
Correção: Inverter a ordem de limpeza.
(3) Exceção Não Capturada na Limpeza
// ❌ Função de limpeza lança exceção, interrompendo limpeza subsequente
ctx.effect(() => {
return () => {
throw new Error('cleanup failed') // Outros effects podem não executar
}
})
Correção: Envolver lógica de limpeza em try-catch.
(4) Referência de Closure Obsoleta
// ❌ Referencia variável externa que pode ser inválida após descarregamento
let globalRef: SomeObject | null = new SomeObject()
export function apply(ctx: Context) {
ctx.effect(() => {
return () => {
globalRef!.cleanup() // globalRef pode ter sido setado para null por outro código
}
})
}
Correção: Capturar a referência dentro da closure do effect.
❓ Perguntas Frequentes
ctx.on() registra um listener de evento que é automaticamente removido ao descarregar. ctx.effect() registra uma função de limpeza arbitrária chamada ao descarregar. Eles se complementam: ctx.on lida com eventos, ctx.effect lida com outros recursos.📖 Resumo
- Auto-limpeza é a funcionalidade central do Cordis: recursos registrados através de ctx são automaticamente recuperados ao descarregar
ctx.effect()registra limpeza manual de recursos, retornando uma função de limpeza- Funções de limpeza executam em ordem inversa de registro (LIFO); atenção à ordem de dependências
- Preferir
ctx.setInterval/setTimeoutem vez de APIs nativas - Conexões de rede, pools de conexão de banco de dados, etc. devem usar
ctx.effect()para registro de limpeza - Envolver funções de limpeza em try-catch para prevenir que exceções interrompam limpeza subsequente
📝 Exercícios
1. ⭐ Básico: Escreva um plugin que exiba uma contagem a cada segundo usando ctx.setInterval. Inicie e confirme que o timer é limpo adequadamente ao descarregar.
2. ⭐⭐ Intermediário: Escreva um plugin que cria um servidor HTTP escutando na porta 3456, com limpeza registrada via ctx.effect(). Inicie, teste requisições HTTP, depois descarregue o plugin e confirme que a porta é liberada.
3. ⭐⭐⭐ Desafio: Escreva um plugin que gerencia tanto uma conexão WebSocket quanto um pool de conexão de banco de dados, garantindo que a limpeza feche o WebSocket primeiro e depois o banco de dados, com tratamento de exceções nas funções de limpeza. Teste: deliberadamente lance um erro ao fechar o banco de dados, e verifique que o WebSocket ainda é fechado adequadamente.