DeepSeek Harness: Ciclo de Vida do Plugin

Última atualização: 2026-08-31

Cada plugin no Cordis não é um bloco de código estático, mas uma entidade viva com um ciclo de vida — um Fiber. Entender a Fiber state machine significa entender a jornada completa de "aguardando para carregar" a "executando" a "saída graciosa," que é a chave para escrever plugins robustos.

💡 Dica: O valor central do Fiber é "ciclo de vida observável" — cada transição de estado tem condições de gatilho claras e efeitos colaterais observáveis. Você sempre sabe em que estado um plugin está, e por quê.

📋 Pré-requisitos: Ter completado 13-effect.md, entender auto-limpeza e ctx.effect()

1. O Que Você Vai Aprender

Máquina de Estados Fiber


2. Conceito Fiber

(1) O Que É Fiber

▶ Exemplo 1: Interface Fiber

Fiber é a abstração do Cordis sobre o estado de runtime de um plugin — cada instância de plugin corresponde a um objeto Fiber que registra em qual estágio do ciclo de vida o plugin está atualmente.

TYPESCRIPT
interface Fiber {
  id: string
  name: string
  state: FiberState
  context: Context
  parent: Fiber | null
  children: Fiber[]
}

(2) Por Que Fiber É Necessário

Sem Fiber, plugins têm apenas dois estados: "carregado" e "descarregado." Mas na prática:

Fiber fornece um modelo claro de state machine para esses estados intermediários.

(3) Fiber e Context

TEXT 📖 Somente leitura
Fiber = "alma" do plugin (informação de estado)
Context = "corpo" do plugin (recursos e ambiente)

Cada Fiber possui um Context; o ciclo de vida do Context está vinculado ao Fiber.


3. Estados do Ciclo de Vida

(1) Diagrama de Estados

100%
stateDiagram-v2
    [*] --> pending: Plugin registrado
    pending --> active: Dependências prontas + apply bem-sucedido
    pending --> errored: apply falha
    active --> disposing: Descarregamento solicitado
    errored --> active: Tentativa bem-sucedida
    errored --> disposing: Tentativa abandonada
    disposing --> disposed: Limpeza concluída

(2) Significados dos Estados

Estado Significado ctx Disponível Reversível
pending Aguardando dependências Limitado ✅
active Executando normalmente Completo ✅
disposing Limpando recursos Somente leitura ❌
disposed Destruído Indisponível ❌
errored Falha na inicialização Indisponível ✅

(3) Estado pending

Quando um plugin declara inject mas as dependências ainda não foram registradas, Fiber entra em pending:

TYPESCRIPT
export const inject = ['tools']

// Serviço tools ainda não registrado → Fiber está pending
// tools registrado → Fiber transiciona para active, chama apply

Durante pending:

(4) Estado active

Após apply executar com sucesso, Fiber entra em active:

TYPESCRIPT
export function apply(ctx: Context) {
  // Fiber agora está active
  ctx.logger.info('Estou vivo!')
  
  ctx.on('session/created', (s) => {
    // Pode usar todos os serviços normalmente
  })
}

Durante active:

(5) Estado disposing

Após receber uma solicitação de descarregamento, Fiber entra em disposing e começa a limpar recursos:

TEXT 📖 Somente leitura
Processo disposing:
1. Parar de aceitar novas requisições
2. Executar funções de limpeza ctx.effect() em ordem inversa
3. Auto-remover listeners de eventos
4. Limpar timers
5. Desregistrar comandos e serviços

(6) Estado disposed

Após todos os recursos serem limpos, Fiber entra em disposed:


4. Criação e Destruição de Fiber

(1) Momento de Criação

Fiber é automaticamente criado quando um plugin é registrado:

TYPESCRIPT
// Lógica interna do framework (pseudocódigo)
function registerPlugin(plugin: PluginDefinition) {
  const fiber = new Fiber({
    id: generateId(),
    name: plugin.name,
    state: hasDeps(plugin) ? 'pending' : 'active'
  })
  
  if (fiber.state === 'active') {
    fiber.context = createContext(fiber)
    plugin.apply(fiber.context)
  }
}

(2) Gatilhos de Destruição

Destruição de Fiber é acionada por:

Gatilho Descrição
ctx.dispose() Descarregamento ativo
Fiber pai destruído Descarregamento em cascata
Recarregamento por mudança de config Fiber antigo destruído, novo Fiber criado

(3) Ordem de Destruição

100%
graph TD
    TRIGGER[Gatilho de descarregamento] --> STOP[Parar novas requisições]
    STOP --> CHILD[Destruir Fibers filhos]
    CHILD --> CLEAN[Executar funções de limpeza]
    CLEAN --> DISPOSED[Marcar disposed]

5. Relações Fiber Pai-Filho

(1) Estrutura Hierárquica

Fibers suportam relações pai-filho, formando estruturas em árvore:

100%
graph TD
    ROOT[Root Fiber<br/>dsh-core] --> A[Plugin A Fiber]
    ROOT --> B[Plugin B Fiber]
    A --> A1[Sub-plugin A1]
    A --> A2[Sub-plugin A2]

(2) Estabelecendo Relações Pai-Filho

▶ Exemplo 2: Criando Sub-Plugins via ctx.plugin()

Crie Fibers filhos via ctx.plugin():

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.plugin({
    name: 'sub-plugin',
    apply(subCtx: Context) {
      subCtx.logger.info('Eu sou um fiber filho')
    }
  })
}

(3) Destruição em Cascata

Quando um Fiber pai é destruído, todos os Fibers filhos são automaticamente destruídos:

TEXT 📖 Somente leitura
Descarregar Plugin A:
  → Primeiro destruir Sub-plugin A1
  → Depois destruir Sub-plugin A2
  → Finalmente destruir Plugin A

Esta ordem de destruição "filhos antes do pai" garante que dependências não sejam quebradas.

(4) Herança de Escopo

Fibers filhos herdam o ctx do Fiber pai:

TYPESCRIPT
// Serviços registrados pelo plugin pai são acessíveis aos plugins filhos
export function apply(ctx: Context) {
  ctx.provide('parent-service', { ... })
  
  ctx.plugin({
    name: 'child',
    inject: ['parent-service'],
    apply(childCtx) {
      childCtx['parent-service'] // ✅ Pode acessar serviço do pai
    }
  })
}

6. Tratamento de Erros e Rollback de Estado

(1) Falha no apply

Quando apply lança uma exceção, Fiber entra no estado errored:

TYPESCRIPT
export function apply(ctx: Context) {
  throw new Error('inicialização falhou')
  // Fiber → errored
}

(2) Tentativa Automática

Cordis automaticamente tenta novamente Fibers com erro:

TEXT 📖 Somente leitura
1ª tentativa: apply() → throw Error → errored
2ª tentativa: (aguardar 1s) apply() → throw Error → errored
3ª tentativa: (aguardar 2s) apply() → throw Error → errored
4ª tentativa: (aguardar 4s) apply() → sucesso → active

O intervalo de tentativa usa backoff exponencial: 1s → 2s → 4s → 8s → ... → máx 60s.

(3) Configuração da Estratégia de Tentativa

TYPESCRIPT
export const Config = Schema.object({
  maxRetries: Schema.number().default(5).description('Máximo de tentativas'),
  retryInterval: Schema.number().default(1000).description('Intervalo inicial de tentativa (ms)')
})

(4) Tentativa Manual

TYPESCRIPT
ctx.on('fiber/errored', (fiber) => {
  ctx.logger.warn(`plugin ${fiber.name} com erro, tentando novamente...`)
  fiber.retry()
})

(5) Erros Não Retentáveis

Alguns erros não devem ser tentados novamente:

TYPESCRIPT
export function apply(ctx: Context) {
  if (!process.env.REQUIRED_VAR) {
    // Erro de configuração, tentar novamente não ajuda
    throw new NonRetryableError('REQUIRED_VAR não está definida')
  }
}

(6) Erros na Fase de Limpeza

Erros durante a fase disposing não impedem Fiber de transicionar para disposed, mas são registrados:

TEXT 📖 Somente leitura
[warn] erro de limpeza no plugin my-plugin: Conexão já fechada
[info] plugin my-plugin disposed (com 1 avisos de limpeza)

7. Isolamento de Contexto Fiber

(1) Cada Fiber Tem um ctx Independente

TYPESCRIPT
const fiberA = new Fiber({ name: 'plugin-a' })
const fiberB = new Fiber({ name: 'plugin-b' })

fiberA.context !== fiberB.context  // true

(2) Limites de Isolamento

Recurso Isolado Descrição
Listeners de evento ✅ Cada Fiber registra independentemente
Timers ✅ Cada Fiber limpa independentemente
Comandos ⚠️ Compartilhados globalmente, mas com escopos
Serviços ❌ Compartilhados globalmente
Configuração ✅ Cada plugin é independente

(3) Equilibrando Compartilhamento e Isolamento de Serviços

Serviços são compartilhados globalmente — esta é uma decisão de design do Cordis. O serviço A registrado pelo plugin A pode ser usado pelo plugin B via inject. Se isolamento é necessário, use o mecanismo de escopo (veja 20-scope.md).

(4) Consultas de Estado

▶ Exemplo 3: Consultando Estado do Fiber

TYPESCRIPT
// Consultar estado do Fiber
ctx.fiber.state           // 'active'
ctx.fiber.id              // 'fiber-abc-123'
ctx.fiber.parent          // Fiber pai ou null
ctx.fiber.children        // Fiber[]

❓ Perguntas Frequentes

P Qual a relação entre Fiber e Thread?
R Nenhuma. Fiber não é uma thread do SO, mas uma unidade de estado lógico do Cordis. Todos os Fibers executam no mesmo event loop do Node.js.
P Posso criar um Fiber manualmente?
R Não recomendado. Ao registrar sub-plugins via ctx.plugin(), o framework automaticamente cria Fibers.
P Plugins com erro consomem recursos?
R Não. Um Fiber com erro não mantém recursos ctx; apenas o objeto Fiber em si (memória mínima) é retido para rastreamento de estado.
P Um Fiber filho pode sobreviver após seu pai ser destruído?
R Não. Relações pai-filho são dependências fortes — destruição do pai sempre destrói os filhos. Se precisar de ciclos de vida independentes, não estabeleça relações pai-filho.
P Como monitorar todos os estados de Fiber?
R Escute eventos de ciclo de vida do Fiber: typescript ctx.on('fiber/created', (fiber) => { ... }) ctx.on('fiber/active', (fiber) => { ... }) ctx.on('fiber/disposing', (fiber) => { ... }) ctx.on('fiber/disposed', (fiber) => { ... }) ctx.on('fiber/errored', (fiber) => { ... })
P Quando mudanças de config causam reconstrução do Fiber, as funções de limpeza antigas são executadas?
R Sim. Na mudança de config, o Fiber antigo passa pelo fluxo completo disposing → disposed; todas as funções de limpeza são executadas. Depois um novo Fiber é criado e entra em pending/active.

📖 Resumo


📝 Exercícios

1. ⭐ Básico: Escreva um plugin que exiba o estado e id do Fiber atual em apply. Inicie e verifique os logs para confirmar que o Fiber está no estado active.

2. ⭐⭐ Intermediário: Escreva um plugin que deliberadamente lança um erro em apply (simulando falha de inicialização). Observe o estado errored do Fiber e o comportamento de tentativa. Depois corrija o erro e verifique que o Fiber recupera para active.

3. ⭐⭐⭐ Desafio: Crie uma estrutura Fiber pai-filho: o plugin pai registra um serviço, o plugin filho o usa via inject. Descarregue o plugin pai e verifique que o filho também é destruído em cascata. Exiba a ordem de destruição nas funções de limpeza para confirmar "filhos antes do pai."

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%