Volver a la wiki

Servicio Connector WebSocket — Diagnóstico del Flapping (terminal/agent/core/connector.py)

Propósito

El módulo connector.py del Local Agent gestiona la conexión WebSocket con el servidor SaaS. Desde v2.16.0, expone diagnostico del flapping mediante logging detallado de cada cierre y stats públicas de reconexión.

Nota de nomenclatura: en el código actual la clase se llama SaaSConnector (terminal/agent/core/connector.py). El nombre WebSocketConnector usado en las secciones históricas de abajo es el que tenía cuando se escribió este documento (v2.16.0) — el contrato descrito (connect/send/stats) sigue siendo el mismo.

Funciones principales

WebSocketConnector (clase principal)

Gestiona la conexión, reconexiones, buffers de entrada/salida.

__init__(url, token, handlers)

connect()

_receive_loop()

send(message)

ConnectionStats (dataclass)

Estructura que acumula stats de la conexión:

@dataclass
class ConnectionStats:
    started_at: datetime
    last_connected_at: Optional[datetime] = None
    reconnect_count: int = 0
    last_close_code: Optional[int] = None
    last_error: Optional[str] = None
    last_session_secs: float = 0.0
    close_codes_recent: List[Tuple[int, datetime]] = field(default_factory=list)  # últimas 10

Actualización:

Endpoint /info — Sección saas.ws

Antes (v2.15.1): no exponía stats.

Desde v2.16.0: sección saas.ws incluye:

{
  "saas": {
    "ws": {
      "connected": true,
      "reconnect_count": 3539,
      "last_close_code": 1006,
      "last_session_secs": 6.67,
      "close_codes_recent": [
        {"code": 1006, "timestamp": "2026-07-21T14:23:45Z"},
        {"code": 1006, "timestamp": "2026-07-21T14:23:52Z"},
        ...
      ],
      "last_error": "ConnectionResetError: [WinError 10054] ...",
      "uptime_secs": 123456
    }
  }
}

Desde 2.27.1: get_status() añade queued_commands (tamaño de la cola de CommandQueue en ese instante) — un valor alto y sostenido delata un comando lento monopolizando el trabajador.

Diagnóstico del flapping

Indicadores de inestabilidad

IndicadorUmbralAcción
reconnect_count > 100/díaAltoInvestigar causa raíz (red, cloud, servidor)
last_session_secs < 30CríticoConexión muere casi inmediatamente — fallo de handshake o timeout
last_close_code = 1006 (abnormal)AnómaloRed inestable o cierre por timeout (no es limpio)
last_close_code = 1011 (server error)Error del serverSaaS devuelve error interno
last_close_code = 1000 (normal)OKCierre limpio (raro en reconexión, common en shutdown)
queued_commands alto y sostenidoAnómalo (2.27.1+)Un comando (deep discovery contra equipo mudo) monopoliza el trabajador — no bloquea ya el receive loop, pero sí retrasa el resto de encargos

Interpretación de close_codes

CódigoNombreCausa típica
1000Normal ClosureCierre limpio (raramente observado en flapping)
1006Abnormal ClosureRed inestable, timeout TCP, no-response
1011Server ErrorSaaS devolvió error interno (5xx)
4000-4999CustomErrores de aplicación (p.ej. auth fallida, tenant inválido)

Post-mortem de YogaEdu (21-07-2026)

Post-mortem CCIB (07-09-2026)

Segundo incidente de flapping en el mismo portátil (YogaEdu), causa raíz distinta a la de julio — detalle completo en [[incident—20260907—ccib-comando-bloqueante-websocket]]:

CommandQueue — cola de comandos con un solo trabajador (2.27.1)

Módulo nuevo terminal/agent/core/command_queue.py::CommandQueue. Antes de 2.27.1, cada comando que llegaba por el websocket (command, device_op, handlers registrados) se ejecutaba awaiteado dentro de _receive_loop() — un comando lento (deep discovery, ver post-mortem CCIB arriba) dejaba el socket sin leer.

Logging detallado

Cada operación clave se loguea en agent.log:

[2026-07-21 14:23:45] [connector.py] INFO: WS connected (attempt 1, 0 reconnections)
[2026-07-21 14:23:52] [connector.py] INFO: WS closed: code=1006, duration=6.67s, reason=abnormal_closure
[2026-07-21 14:23:52] [connector.py] INFO: Reconnecting in 1.0s... (attempt 2)
[2026-07-21 14:23:53] [connector.py] INFO: WS connected (attempt 2, 1 reconnection)
...

Antes (v2.15.1): solo había INFO: connected y nada más.

Integración con sync.py

El connector dispara callbacks:

Tests

Referencias de código

Véase también

Subir