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.

💡 Dica: Auto-limpeza é a rede de segurança do Cordis; ctx.effect() é seu complemento. O princípio: use registro via ctx sempre que possível; use ctx.effect() apenas quando não puder.

📋 Pré-requisitos: Ter completado 11-first-plugin.md, entender a função apply e Context

1. O Que Você Vai Aprender

Limpeza de Efeito


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:

TYPESCRIPT
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

TYPESCRIPT
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:

É aqui que ctx.effect() entra:

TYPESCRIPT
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()

TYPESCRIPT
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

TYPESCRIPT
// 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

TYPESCRIPT
ctx.effect(() => {
  const resource = acquireResource()
  
  return () => {
    releaseResource(resource)
  }
})

Este padrão "adquirir-liberar" é similar ao try-finally:

TYPESCRIPT
// Modelo mental equivalente try-finally
try {
  const resource = acquireResource()
  // Usar recurso
} finally {
  releaseResource(resource)
}

(2) Limpando Múltiplos Recursos

TYPESCRIPT
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

TYPESCRIPT
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)

TYPESCRIPT
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:

TYPESCRIPT
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

TYPESCRIPT
// ❌ Errado: setTimeout ainda dispara após descarregamento
export function apply(ctx: Context) {
  setTimeout(() => {
    ctx.logger.info('delayed action') // Plugin pode já estar descarregado!
  }, 5000)
}
TYPESCRIPT
// ✅ 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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ❌ 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

TYPESCRIPT
// ❌ 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

P Funções de limpeza do ctx.effect() podem ser async?
R Sim. Cordis suporta funções de limpeza assíncronas e aguardará a conclusão antes de continuar. Nota: se a limpeza demorar muito, atrasará o descarregamento de todo o plugin.
P Qual a ordem de execução de múltiplas chamadas ctx.effect()?
R Ordem inversa de registro (LIFO, como uma pilha). Effects registrados depois são limpos primeiro, garantindo ordenação correta de dependências.
P Recursos serão limpos se o plugin crashar?
R Sim. Cordis ainda tenta chamar todas as funções de limpeza registradas quando um plugin sai anormalmente, incluindo as registradas via ctx.effect(). Esta é a garantia de segurança do Cordis.
P Posso registrar ctx.effect() fora do apply?
R Tecnicamente sim (contanto que você tenha uma referência ctx), mas não é recomendado. Effects registrados fora do apply não pertencem a nenhum ciclo de vida de plugin e podem causar comportamento de limpeza imprevisível.
P Qual a diferença entre ctx.on() e ctx.effect()?
R 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


📝 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%