dotnet / dotnet/aspnetcore

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

Open
#63,134 4 comments 0 reactions 0 assignees View on GitHub
area-signalr bug
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [x] 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 pendingCall = ReplaySubject.create();`
2. `HubConnection.java:796` - `ReplaySubject 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:
```java
HubConnection hubConnection = HubConnectionBuilder.create("wss://example.com/hub")
.withAccessTokenProvider(Single.just(accessToken))
.build();

hubConnection.start().blockingAwait();

// This will cause memory leak over time
Observable 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_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.