Bug: Bun C++ Exception Crash with PartyWebSocket (partysocket library)

Abierto
#304 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
20/100
Tipo de issue
Error
Claridad
Bastante claro
Estado de actividad
Estancado
Stack tecnológico
typescript
Área
networking

Línea de trabajo

Comienza con backend/apps/support/src/lib/chat-api-client.ts y compara las rutas de PartyWebSocket y native WebSocket descritas en el issue. Ejecuta la reproducción prolongada con [email protected] en Bun v1.3.5 y confirma después que la aplicación sigue ejecutándose más allá del intervalo informado de 3–4 minutos sin el crash de parallel-GC; el issue relacionado de Bun puede proporcionar contexto adicional.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

Bun v1.3.5 crashes with a "pure virtual method called" C++ exception after running for 3-4 minutes when using PartyWebSocket from the partysocket library. The crash occurs during JavaScriptCore's parallel garbage collection when marking WebCore/DOM objects.

Environment

  • Bun version: v1.3.5 (1e86cebd)
  • OS: Linux x86_64 (baseline)
  • Kernel: v6.18.0 | glibc v2.42
  • Library: [email protected] (via catalog)
  • Crash time: Consistently after 3-4 minutes of runtime

Stack Trace

Bun v1.3.5 (1e86cebd) on linux x86_64_baseline [AutoCommand]

**panic**: A C++ exception occurred

- JSGlobalObject.zig:854: `Zig__GlobalObject__onCrash`
- compiler_builtins.6c1f39da0e88e811-cgu.247:0: `__cxxabiv1::__terminate`
- ClassInfo.h:124: `WTF::Ref<WTF::SharedTask<void (...)>, WTF::RawPtrTraits<WTF::SharedTask<void (...)> >, WTF::DefaultRefDerefTraits<WTF::SharedTask<void (...)> > > JSC::Subspace::forEachMarkedCellInParallel<JSC::SlotVisitor, void WebCore::DOMGCOutputConstraint::executeImplImpl<JSC::SlotVisitor>(...)::'lambda'(...)::operator(...) const::'lambda'(...)>(...)::'lambda'(...)::operator(...) const::'lambda'(...) const&)::Task::run`
- MarkingConstraint.cpp:96: `JSC::MarkingConstraintSolver::runExecutionThread`
- SlotVisitor.cpp:686: `JSC::SlotVisitor::drainFromShared`
- Heap.cpp:1502: `WTF::SharedTaskFunctor<void (...)::$_1>::run`
- ParallelHelperPool.cpp:110: `WTF::ParallelHelperPool::Thread::work`
- AutomaticThread.cpp:225: `WTF::Detail::CallableWrapper<WTF::AutomaticThread::start(...)::$_0, void>::call`
- Function.h:82: `WTF::wtfThreadEntryPoint`

Root Cause

The crash occurs during JavaScriptCore's parallel garbage collection when marking WebCore::DOMGCOutputConstraint objects. The PartyWebSocket class from the partysocket library creates WebCore/DOM objects that are destroyed while parallel GC threads are trying to mark them, causing the "pure virtual method called" error.

Key observation: The stack trace shows WebCore::DOMGCOutputConstraint, indicating this is related to DOM/WebCore objects, not just WebSocket connections. The partysocket library likely wraps or extends native WebSocket functionality in a way that creates additional WebCore objects that become invalid during GC.

Problematic Code

Before (Causing Crash)
// backend/apps/support/src/lib/chat-api-client.ts
import { WebSocket as PartyWebSocket } from 'partysocket'

// Create WebSocket connection using partysocket (with auto-reconnect)
let sharedWebSocket: PartyWebSocket | null = null
let sharedWsClient: ContractRouterClient<typeof chatContract> | null = null

function getSharedWebSocketClient(): ContractRouterClient<typeof chatContract> {
  // Return existing client if WebSocket is open
  if (
    sharedWsClient &&
    sharedWebSocket &&
    sharedWebSocket.readyState === PartyWebSocket.OPEN
  ) {
    return sharedWsClient
  }

  // Create new WebSocket connection with partysocket
  // partysocket provides automatic reconnection and message buffering
  sharedWebSocket = new PartyWebSocket(getWebSocketUrl())  // ❌ CRASHES HERE

  // Create oRPC client from WebSocket
  const link = new WSLink({
    websocket: sharedWebSocket as any,
  })

  sharedWsClient = createORPCClient(link) as ContractRouterClient<
    typeof chatContract
  >

  sharedWebSocket.addEventListener('close', () => {
    sharedWsClient = null
  })

  return sharedWsClient
}

// Created at module load time - WebSocket created immediately
export const chatApiClient: ContractRouterClient<typeof chatContract> =
  getSharedWebSocketClient()  // ❌ Creates PartyWebSocket immediately
After (Fixed)
// backend/apps/support/src/lib/chat-api-client.ts
// REMOVED: import { WebSocket as PartyWebSocket } from 'partysocket'
// Using native WebSocket instead

// Create WebSocket connection using native WebSocket
let sharedWebSocket: WebSocket | null = null
let sharedWsClient: ContractRouterClient<typeof chatContract> | null = null

function getSharedWebSocketClient(): ContractRouterClient<typeof chatContract> {
  // Return existing client if WebSocket is open
  if (
    sharedWsClient &&
    sharedWebSocket &&
    sharedWebSocket.readyState === WebSocket.OPEN  // ✅ Native WebSocket
  ) {
    return sharedWsClient
  }

  // Create new WebSocket connection with native WebSocket
  // Using native WebSocket instead of PartyWebSocket to avoid GC crashes
  sharedWebSocket = new WebSocket(getWebSocketUrl())  // ✅ Native WebSocket

  // Create oRPC client from WebSocket
  const link = new WSLink({
    websocket: sharedWebSocket as any,
  })

  sharedWsClient = createORPCClient(link) as ContractRouterClient<
    typeof chatContract
  >

  sharedWebSocket.addEventListener('close', () => {
    sharedWsClient = null
  })

  return sharedWsClient
}

// Lazy-load chatApiClient to avoid creating WebSocket at module load time
let _chatApiClient: ContractRouterClient<typeof chatContract> | null = null

export const chatApiClient: ContractRouterClient<typeof chatContract> =
  new Proxy({} as ContractRouterClient<typeof chatContract>, {
    get(_target, prop) {
      if (!_chatApiClient) {
        _chatApiClient = getSharedWebSocketClient()  // ✅ Lazy-loaded
      }
      return (_chatApiClient as any)[prop]
    },
  })

Workaround

Replace PartyWebSocket from partysocket with native WebSocket:

  1. Remove partysocket import:

    // REMOVED: import { WebSocket as PartyWebSocket } from 'partysocket'
    
  2. Use native WebSocket:

    const websocket = new WebSocket(wsUrl)  // Instead of new PartyWebSocket(wsUrl)
    
  3. Lazy-load WebSocket connections to avoid creating them at module load time:

    // Use Proxy to lazy-load instead of creating at module load
    export const chatApiClient = new Proxy({}, {
      get(_target, prop) {
        if (!_chatApiClient) {
          _chatApiClient = getSharedWebSocketClient()
        }
        return (_chatApiClient as any)[prop]
      },
    })
    

Related Issues

Additional Context

The crash is not related to:

  • Native WebSocket usage (works fine)
  • Async iterators with native WebSocket
  • EventPublisher or mergeIterators
  • AbortSignal usage

The crash is specifically related to:

  • PartyWebSocket from partysocket library
  • Objects created by partysocket that reference WebCore/DOM
  • These objects being destroyed during parallel GC marking

Expected Behavior

The application should run indefinitely without crashing. Native WebSocket works correctly, but PartyWebSocket causes crashes after 3-4 minutes.

Actual Behavior

Application crashes with C++ exception after 3-4 minutes when using PartyWebSocket. Replacing with native WebSocket resolves the issue.

Reproduction

  1. Create a WebSocket connection using PartyWebSocket from partysocket
  2. Keep the connection alive for 3-4 minutes
  3. Wait for JavaScriptCore's parallel GC to run
  4. Crash occurs when GC tries to mark WebCore objects created by partysocket

Impact

  • Severity: High - causes application crashes
  • Frequency: 100% reproducible after 3-4 minutes
  • Workaround: Use native WebSocket instead of PartyWebSocket
Lenguaje dominante
TypeScript
Estrellas
1.3k
Forks
84
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de cloudflare/partykit

Todos los issues de cloudflare/partykit

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.