@@ -7,6 +7,7 @@ import { singleton } from "~/utils/singleton";
7
7
8
8
export type RelayRealtimeStreamsOptions = {
9
9
ttl : number ;
10
+ cleanupInterval : number ;
10
11
fallbackIngestor : StreamIngestor ;
11
12
fallbackResponder : StreamResponder ;
12
13
waitForBufferTimeout ?: number ; // Time to wait for buffer in ms (default: 500ms)
@@ -17,6 +18,7 @@ interface RelayedStreamRecord {
17
18
stream : ReadableStream < Uint8Array > ;
18
19
createdAt : number ;
19
20
lastAccessed : number ;
21
+ locked : boolean ;
20
22
finalized : boolean ;
21
23
}
22
24
@@ -33,7 +35,7 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
33
35
// Periodic cleanup
34
36
this . cleanupInterval = setInterval ( ( ) => {
35
37
this . cleanup ( ) ;
36
- } , this . options . ttl ) . unref ( ) ;
38
+ } , this . options . cleanupInterval ) . unref ( ) ;
37
39
}
38
40
39
41
async streamResponse (
@@ -76,6 +78,23 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
76
78
}
77
79
}
78
80
81
+ // Only 1 reader of the stream can use the relayed stream, the rest should use the fallback
82
+ if ( record . locked ) {
83
+ logger . debug ( "[RelayRealtimeStreams][streamResponse] Stream already locked, using fallback" , {
84
+ streamId,
85
+ runId,
86
+ } ) ;
87
+
88
+ return this . options . fallbackResponder . streamResponse (
89
+ request ,
90
+ runId ,
91
+ streamId ,
92
+ environment ,
93
+ signal
94
+ ) ;
95
+ }
96
+
97
+ record . locked = true ;
79
98
record . lastAccessed = Date . now ( ) ;
80
99
81
100
logger . debug ( "[RelayRealtimeStreams][streamResponse] Streaming from ephemeral record" , {
@@ -106,7 +125,7 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
106
125
"Content-Type" : "text/event-stream" ,
107
126
"Cache-Control" : "no-cache" ,
108
127
Connection : "keep-alive" ,
109
- "x-relay-realtime-streams" : "true" ,
128
+ "x-trigger- relay-realtime-streams" : "true" ,
110
129
} ,
111
130
} ) ;
112
131
}
@@ -157,6 +176,7 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
157
176
createdAt : Date . now ( ) ,
158
177
lastAccessed : Date . now ( ) ,
159
178
finalized : false ,
179
+ locked : false ,
160
180
} ;
161
181
this . _buffers . set ( bufferKey , record ) ;
162
182
} else {
@@ -167,12 +187,21 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
167
187
168
188
private cleanup ( ) {
169
189
const now = Date . now ( ) ;
190
+
191
+ logger . debug ( "[RelayRealtimeStreams][cleanup] Cleaning up old buffers" , {
192
+ bufferCount : this . _buffers . size ,
193
+ } ) ;
194
+
170
195
for ( const [ key , record ] of this . _buffers . entries ( ) ) {
171
196
// If last accessed is older than ttl, clean up
172
197
if ( now - record . lastAccessed > this . options . ttl ) {
173
198
this . deleteBuffer ( key ) ;
174
199
}
175
200
}
201
+
202
+ logger . debug ( "[RelayRealtimeStreams][cleanup] Cleaned up old buffers" , {
203
+ bufferCount : this . _buffers . size ,
204
+ } ) ;
176
205
}
177
206
178
207
private deleteBuffer ( bufferKey : string ) {
@@ -216,6 +245,7 @@ export class RelayRealtimeStreams implements StreamIngestor, StreamResponder {
216
245
function initializeRelayRealtimeStreams ( ) {
217
246
return new RelayRealtimeStreams ( {
218
247
ttl : 1000 * 60 * 5 , // 5 minutes
248
+ cleanupInterval : 1000 * 60 , // 1 minute
219
249
fallbackIngestor : v1RealtimeStreams ,
220
250
fallbackResponder : v1RealtimeStreams ,
221
251
} ) ;
0 commit comments