Skip to content

SignalR Java Client 9.0.4: Memory leak in stream() method due to unbounded ReplaySubject #63134

@ilyagorbunov

Description

@ilyagorbunov

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Describe the bug

The SignalR Java client version 9.0.4 has a memory leak when using the stream() method for real-time data streaming. The issue is caused by the use of unbounded ReplaySubject.create() in two places:

  1. InvocationRequest.java:14 - private final Subject<Object> pendingCall = ReplaySubject.create();
  2. HubConnection.java:796 - ReplaySubject<T> subject = ReplaySubject.create(); (in the stream() method)

These unbounded replay subjects cache ALL emitted items indefinitely, causing memory to grow without bounds during long-running WebSocket connections with continuous data streams.

To Reproduce

Steps to reproduce the behavior:

  1. Create a SignalR Java client connection
  2. Use the stream() method to subscribe to a high-frequency data stream (e.g., real-time trading data)
  3. Keep the connection running for extended periods (>1 hour)
  4. Monitor memory usage - it will continuously grow until OutOfMemoryError occurs

Example code:

HubConnection hubConnection = HubConnectionBuilder.create("wss://example.com/hub")
    .withAccessTokenProvider(Single.just(accessToken))
    .build();

hubConnection.start().blockingAwait();

// This will cause memory leak over time
Observable<MarketData> stream = hubConnection.stream(MarketData.class, "StreamMarketData", "symbol");
stream.subscribe(data -> {
    // Process real-time data
});

Expected behavior

The streaming connection should maintain stable memory usage over time, not accumulating all historical data in memory.

Actual behavior

Memory usage grows continuously as ReplaySubject caches all emitted items. This leads to OutOfMemoryError after extended periods of streaming.

Workaround

Currently, we're forced to periodically disconnect and reconnect the SignalR connection every 5-15 minutes to clear the internal buffers and prevent OOM errors.

Suggested Fix

Replace unbounded ReplaySubject.create() with either:

  1. Bounded replay: ReplaySubject.createWithSize(bufferSize)
  2. PublishSubject for no caching: PublishSubject.create()
  3. Make buffer size configurable

Environment

  • SignalR Java Client version: 9.0.4
  • Java version: 11/17
  • Platform: Android
  • RxJava version: 3.x

Stack trace

java.lang.OutOfMemoryError: Failed to allocate a 24 byte allocation with 1349584 free bytes and 1317KB until OOM
    at com.android.org.conscrypt.ConscryptEngine.newResult(ConscryptEngine.java:1363)
    at com.android.org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:933)
    ...
    at okhttp3.internal.ws.WebSocketReader.processNextFrame(WebSocketReader.kt:0)
    at okhttp3.internal.ws.RealWebSocket.loopReader(RealWebSocket.kt:293)

Additional context

This issue affects production applications using SignalR for real-time data streaming, particularly in financial trading applications where data streams are continuous and high-frequency.

Expected Behavior

No response

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

No response

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-signalrIncludes: SignalR clients and serversbugThis issue describes a behavior which is not expected - a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions