@@ -28,13 +28,19 @@ local socketPresenceWorkflowKey = KEYS[3]
2828local workflowUsersPrefix = ARGV[1]
2929local workflowMetaPrefix = ARGV[2]
3030local socketId = ARGV[3]
31+ local workflowIdHint = ARGV[4]
3132
3233local workflowId = redis.call('GET', socketWorkflowKey)
3334if not workflowId then
3435 workflowId = redis.call('GET', socketPresenceWorkflowKey)
35- if not workflowId then
36- return nil
37- end
36+ end
37+
38+ if not workflowId and workflowIdHint ~= '' then
39+ workflowId = workflowIdHint
40+ end
41+
42+ if not workflowId then
43+ return nil
3844end
3945
4046local workflowUsersKey = workflowUsersPrefix .. workflowId .. ':users'
@@ -215,19 +221,14 @@ export class RedisRoomManager implements IRoomManager {
215221 KEYS . socketSession ( socketId ) ,
216222 KEYS . socketPresenceWorkflow ( socketId ) ,
217223 ] ,
218- arguments : [ 'workflow:' , 'workflow:' , socketId ] ,
224+ arguments : [ 'workflow:' , 'workflow:' , socketId , workflowIdHint ?? '' ] ,
219225 } )
220226
221227 if ( typeof workflowId === 'string' && workflowId . length > 0 ) {
222228 logger . debug ( `Removed socket ${ socketId } from workflow ${ workflowId } ` )
223229 return workflowId
224230 }
225231
226- // Fallback without global SCAN: direct cleanup using workflow hint from socket rooms / join context.
227- if ( workflowIdHint ) {
228- return this . removeUserFromWorkflowHint ( socketId , workflowIdHint )
229- }
230-
231232 return null
232233 } catch ( error ) {
233234 if ( ( error as Error ) . message ?. includes ( 'NOSCRIPT' ) && ! retried ) {
@@ -292,52 +293,6 @@ export class RedisRoomManager implements IRoomManager {
292293 return exists > 0
293294 }
294295
295- private async removeUserFromWorkflowHint (
296- socketId : string ,
297- workflowIdHint : string
298- ) : Promise < string | null > {
299- try {
300- const pipeline = this . redis . multi ( )
301- pipeline . hDel ( KEYS . workflowUsers ( workflowIdHint ) , socketId )
302- pipeline . del ( KEYS . socketWorkflow ( socketId ) )
303- pipeline . del ( KEYS . socketSession ( socketId ) )
304- pipeline . del ( KEYS . socketPresenceWorkflow ( socketId ) )
305-
306- const results = await pipeline . exec ( )
307- if ( results . some ( ( result ) => result instanceof Error ) ) {
308- logger . error ( 'Pipeline partially failed during hinted fallback cleanup' , {
309- socketId,
310- workflowIdHint,
311- } )
312- return null
313- }
314-
315- const hDelResult = results [ 0 ]
316- const removedCount =
317- typeof hDelResult === 'number'
318- ? hDelResult
319- : typeof hDelResult === 'string'
320- ? Number . parseInt ( hDelResult , 10 ) || 0
321- : 0
322-
323- if ( removedCount <= 0 ) {
324- return null
325- }
326-
327- await this . redis . hSet (
328- KEYS . workflowMeta ( workflowIdHint ) ,
329- 'lastModified' ,
330- Date . now ( ) . toString ( )
331- )
332-
333- logger . warn ( `Removed socket ${ socketId } from workflow ${ workflowIdHint } via hinted fallback` )
334- return workflowIdHint
335- } catch ( error ) {
336- logger . error ( 'Failed hinted fallback cleanup' , { socketId, workflowIdHint, error } )
337- return null
338- }
339- }
340-
341296 async updateUserActivity (
342297 workflowId : string ,
343298 socketId : string ,
0 commit comments