Commit eac8ad2
fix(reconnect): listener bug fixes + transport factory policy hook (#45)
## Summary
- Add `ClientOptions.Builder.reconnect(boolean)` (default `true`). When
`false`, the per-resource WebSocket clients connect once via a plain
`okhttp3.WebSocketListener` and propagate failures directly, bypassing
`ReconnectingWebSocketListener` (and its hardcoded
`connectionFuture.get(4000, MILLISECONDS)` ceiling).
- Auto-disable for custom transports: when
`DeepgramClientBuilder.transportFactory(...)` (or its async counterpart)
is set, `setAdditional()` also calls `builder.reconnect(false)`. Custom
transports like `SageMakerTransportFactory` already manage their own
connection lifecycle; wrapping them in another retry layer creates
double-retry storms under burst load.
- Patch applied to all four per-resource WebSocket clients: `listen/v1`,
`listen/v2`, `agent/v1`, `speak/v1`. Same structural change in each:
`connect()` branches on `clientOptions.reconnect()`; `disconnect()`,
`sendMedia()`, `sendMessage()`, `assertSocketIsOpen()` use
`directWebSocket` when the listener is null.
- The four files are added to the "temporarily frozen" section of
`.fernignore` so the patch survives the next Fern regen. Unfreeze once
the change has been pushed upstream into the Fern generator template.
## Why
Validated against a 400-concurrent-stream burst test on a 10×
`ml.g6.2xlarge` endpoint (Deepgram on SageMaker, replicating a
customer-reported failure):
| Metric | Before | After (this PR + transport-side timeouts) | Δ |
|---|---:|---:|---:|
| `ThrottlingException` line-count | 68,909 | 240 | −99.7 % |
| `ModelStreamError` line-count | 30,442 | 1,128 | −96.3 % |
| Streams hitting AWS SDK `Attempt Count: 4` | 29,030 | 96 | −99.7 % |
| 4-second `connection timeout after 4000` | 13 | 0 | eliminated |
| Total error log lines | 1,322,666 | 33,972 | −97.4 % |
| Wall time | 313.57 s | 312.61 s | unchanged |
| Transcript count | 41,337 | 40,467 | unchanged |
Same work done, ~97 % less error noise. Pairs with
[`deepgram/deepgram-java-sdk-transport-sagemaker#14`](deepgram/deepgram-java-sdk-transport-sagemaker#14)
(Layer 1 transport-side timeouts), but each provides independent value.
## Backwards compatibility
**Existing callers that do NOT use `transportFactory(...)` see zero
behavior change.** The new `reconnect()` field defaults to `true`, the
`ReconnectingWebSocketListener`-wrapped code path is unchanged for the
OkHttp WebSocket transport (Deepgram cloud, self-hosted Deepgram via raw
WS), and the lower-level `ClientOptions.Builder.webSocketFactory(...)`
seam is also unaffected (only the explicit
`DeepgramClientBuilder.transportFactory(...)` API triggers the
auto-disable).
| Customer shape | `transportFactory != null`? | `reconnect()` | Wraps
in `ReconnectingWebSocketListener`? | Behavior change |
|---|---|---|---|---|
| Deepgram Cloud (`wss://api.deepgram.com`) | no | `true` | yes
(unchanged) | none |
| Self-hosted Deepgram (raw WS) | no | `true` | yes (unchanged) | none |
| Custom `WebSocketFactory` via `ClientOptions` (lower-level seam) | no
| `true` | yes (unchanged) | none |
| `transportFactory(SageMakerTransportFactory)` | yes | `false`
(auto-set) | no (direct path) | yes — desired |
| `transportFactory(<future custom transport>)` | yes | `false`
(auto-set) | no (direct path) | yes; opt back in with `.reconnect(true)`
|
## Edge case worth calling out
A SageMaker caller who has been tuning retry behavior via
`wsClient.reconnectOptions(customOpts)` will find those options
**silently ignored** after this change (`reconnect=false` skips the
listener entirely). Their tuning was almost certainly a workaround for
the very storm we are now eliminating, so the right migration is to
delete the `reconnectOptions(...)` call. Customers who genuinely want
both a custom transport AND SDK-side reconnect can re-enable explicitly
with `.reconnect(true)` after `.transportFactory(...)`:
```java
DeepgramClient.builder()
.transportFactory(sagemakerFactory)
.reconnect(true) // explicit override; not recommended for SageMaker
.build();
```
This is a runtime-quality change, not an API break — no compile errors
for any existing customer. Recommend mentioning in CHANGELOG and
migration notes for the next minor release.
## Test plan
- [x] `./gradlew build` — passes (spotless + compile + tests).
- [x] `./gradlew test` — all existing tests pass.
- [x] End-to-end validation with patched JAR linked into a customer
load-test harness against a real SageMaker endpoint (numbers above).
- [ ] CI passes.
- [ ] Push template change upstream into the Fern generator and unfreeze
the four per-resource WebSocket client files in `.fernignore`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Greg Holmes <greg.holmes@deepgram.com>1 parent f44678a commit eac8ad2
6 files changed
Lines changed: 285 additions & 10 deletions
File tree
- src
- main/java/com/deepgram/core
- transport
- test/java/com/deepgram/core
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
22 | 27 | | |
23 | 28 | | |
24 | 29 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| |||
Lines changed: 65 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
29 | 32 | | |
30 | | - | |
| 33 | + | |
31 | 34 | | |
32 | | - | |
| 35 | + | |
33 | 36 | | |
34 | | - | |
| 37 | + | |
35 | 38 | | |
36 | 39 | | |
37 | 40 | | |
| 41 | + | |
| 42 | + | |
38 | 43 | | |
39 | 44 | | |
40 | 45 | | |
| |||
66 | 71 | | |
67 | 72 | | |
68 | 73 | | |
| 74 | + | |
69 | 75 | | |
70 | 76 | | |
71 | 77 | | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
72 | 104 | | |
73 | 105 | | |
74 | 106 | | |
75 | 107 | | |
76 | | - | |
| 108 | + | |
77 | 109 | | |
78 | | - | |
| 110 | + | |
| 111 | + | |
79 | 112 | | |
80 | 113 | | |
81 | 114 | | |
| |||
86 | 119 | | |
87 | 120 | | |
88 | 121 | | |
89 | | - | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
90 | 126 | | |
91 | 127 | | |
92 | 128 | | |
93 | 129 | | |
94 | 130 | | |
95 | 131 | | |
96 | | - | |
| 132 | + | |
97 | 133 | | |
98 | 134 | | |
99 | 135 | | |
100 | | - | |
| 136 | + | |
101 | 137 | | |
102 | 138 | | |
103 | 139 | | |
| |||
399 | 435 | | |
400 | 436 | | |
401 | 437 | | |
| 438 | + | |
| 439 | + | |
402 | 440 | | |
403 | 441 | | |
404 | 442 | | |
405 | 443 | | |
406 | 444 | | |
407 | 445 | | |
| 446 | + | |
408 | 447 | | |
409 | 448 | | |
410 | 449 | | |
| |||
422 | 461 | | |
423 | 462 | | |
424 | 463 | | |
| 464 | + | |
| 465 | + | |
425 | 466 | | |
426 | 467 | | |
427 | 468 | | |
428 | 469 | | |
429 | 470 | | |
430 | 471 | | |
| 472 | + | |
431 | 473 | | |
432 | 474 | | |
433 | 475 | | |
| |||
455 | 497 | | |
456 | 498 | | |
457 | 499 | | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
458 | 510 | | |
459 | 511 | | |
460 | 512 | | |
| |||
463 | 515 | | |
464 | 516 | | |
465 | 517 | | |
| 518 | + | |
466 | 519 | | |
467 | 520 | | |
468 | 521 | | |
| |||
487 | 540 | | |
488 | 541 | | |
489 | 542 | | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
490 | 546 | | |
491 | 547 | | |
492 | 548 | | |
| |||
Lines changed: 16 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
34 | 49 | | |
Lines changed: 8 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
4 | 5 | | |
5 | 6 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
34 | 42 | | |
35 | 43 | | |
36 | 44 | | |
| |||
0 commit comments