Bug: Bun C++ Exception Crash with PartyWebSocket (partysocket library)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 20/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Đình trệ
- Công nghệ
- typescript
- Lĩnh vực
- networking
Hướng nghiên cứu
Bắt đầu với backend/apps/support/src/lib/chat-api-client.ts và so sánh các đường đi PartyWebSocket và native WebSocket được mô tả trong issue. Chạy bản tái hiện kéo dài với [email protected] trên Bun v1.3.5, sau đó xác nhận rằng ứng dụng vẫn tiếp tục chạy quá khoảng thời gian 3–4 phút được báo cáo mà không xảy ra parallel-GC crash; issue liên quan của Bun có thể cung cấp thêm ngữ cảnh.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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:
-
Remove partysocket import:
// REMOVED: import { WebSocket as PartyWebSocket } from 'partysocket' -
Use native WebSocket:
const websocket = new WebSocket(wsUrl) // Instead of new PartyWebSocket(wsUrl) -
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
- Similar crash reported with worker threads: https://github.com/oven-sh/bun/issues/14559
- Both involve "pure virtual method called" during parallel GC marking
- Both occur after several minutes of runtime
Additional Context
The crash is not related to:
- Native
WebSocketusage (works fine) - Async iterators with native WebSocket
- EventPublisher or mergeIterators
- AbortSignal usage
The crash is specifically related to:
PartyWebSocketfrompartysocketlibrary- Objects created by
partysocketthat 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
- Create a WebSocket connection using
PartyWebSocketfrompartysocket - Keep the connection alive for 3-4 minutes
- Wait for JavaScriptCore's parallel GC to run
- 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
WebSocketinstead ofPartyWebSocket
- Ngôn ngữ chính
- TypeScript
- Star
- 1.3k
- Fork
- 84
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của cloudflare/partykit
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
cloudflare/partykit#425 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
cloudflare/partykit#424 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
cloudflare/partykit#423 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
cloudflare/partykit#419 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
cloudflare/partykit#411 ·
Tất cả issue của cloudflare/partykit
Issue tương tự
-
Add: Upin & Ipin Channel HDĐang mởcheck:passed streams:add
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
S: triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Maintainer thường phản hồi trong vòng 1 ngày
-
iot client
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Open-Locker/Open-Locker#365 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 69/100
inmanta/web-console#7787 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
shoelace-style/webawesome#2979 ·
Maintainer thường phản hồi trong vòng 1 ngày