Caos não é um bug, é uma feature: introdução ao Chaos Engineering em C++
\nImagine: seu serviço C++ funcionou por meses sem falhas. E então — puf! — falha no disco, perda de pacote ou um SIGKILL repentino. E acabou. O sistema cai, os usuários ficam furiosos, e você procura desesperadamente o bug nos logs. Situação familiar?
Estamos acostumados a testar software em condições ideais: rede estável, disco rápido, memória ilimitada. Mas a realidade é cruel. É aqui que entra o Chaos Engineering — uma disciplina que nos ensina a quebrar intencionalmente o sistema para torná-lo mais forte. E hoje vamos falar sobre como aplicar essa abordagem a aplicações C++.
\nChaos Engineering para C++ não é sobre "ligar o chaos monkey e rezar". É sobre experimentos controlados que revelam pontos fracos antes que os usuários os encontrem. Aprenderemos a injetar falhas de memória, emular I/O lento e romper conexões de rede — tudo isso com código C++ puro.
\n\nPor que o C++ precisa especialmente de engenharia do caos?
\nA linguagem C++ dá ao desenvolvedor um enorme poder: gerenciamento manual de memória, acesso de baixo nível a recursos, ausência de coletor de lixo. Mas esse poder é uma faca de dois gumes. Um erro em delete[], um vazamento em um wrapper RAII ou um reinterpret_cast incorreto podem transformar um sistema estável em um frágil castelo de cartas.
O teste unitário tradicional raramente verifica como o código se comportará quando:
\n- \n
- Falta de memória RAM (
std::bad_alloc) \n - Fechamento repentino de um descritor de arquivo \n
- Atrasos na rede (picos de latência) \n
- Corrupção de dados em memória compartilhada \n
Chaos Engineering para C++ preenche essas lacunas. Não apenas escrevemos testes — nós provocamos o sistema a cometer erros e vemos se ele sobrevive.
\n\nFerramentas: como introduzir caos no código C++
\nPara realizar experimentos, precisaremos de ferramentas de Fault Injection (injeção de falhas). Em C++, podemos atuar em diferentes níveis:
\n- \n
- No nível do código: interceptação de chamadas de sistema via LD_PRELOAD ou
dlsym\n - No nível das bibliotecas: substituição de alocadores e sockets padrão \n
- No nível do SO: uso de
ptraceou eBPF para interferir no processo \n
Seguiremos o caminho mais prático — escreveremos nossa própria biblioteca para injeção de falhas, que pode ser facilmente integrada em qualquer projeto C++.
\n\nExemplo 1: Simulação de falha de alocação de memória
\nUm dos problemas mais comuns em C++ é o inesperado std::bad_alloc. Como seu código se comportará se new lançar uma exceção? Vamos verificar.
// chaos_memory.h\n#pragma once\n#include <new>\n#include <cstdlib>\n#include <atomic>\n#include <cstddef>\n\nclass ChaosAllocator {\npublic:\n // Define a probabilidade de falha (0.0 - 1.0)\n static void set_failure_probability(double prob) {\n failure_probability_ = prob;\n }\n\n static void* allocate(std::size_t size) {\n if (should_fail()) {\n throw std::bad_alloc();\n }\n return std::malloc(size);\n }\n\n static void deallocate(void* ptr) {\n std::free(ptr);\n }\n\nprivate:\n static bool should_fail() {\n // Gerador de números aleatórios simples para demonstração\n static std::atomic<unsigned int> seed = 42;\n seed = seed * 1103515245 + 12345;\n return (seed / 65536) % 100 < failure_probability_ * 100;\n }\n\n inline static double failure_probability_ = 0.0;\n};\nAgora podemos substituir o alocador padrão e verificar como nosso código lida com falhas de memória:
\n// main.cpp\n#include <iostream>\n#include <vector>\n#include "chaos_memory.h"\n\n// Alocador personalizado