@@ -15,6 +15,9 @@ public class RemoteExecutionHub(ILogger<RemoteExecutionHub> logger) : Hub
1515
1616 private static readonly ConcurrentDictionary < Guid , TaskCompletionSource < byte [ ] > > pendingAssemblyRequests = new ( ) ;
1717
18+ // Track pending assembly requests per connection to avoid duplicate requests
19+ private static readonly ConcurrentDictionary < string , ConcurrentDictionary < string , Task < byte [ ] > > > pendingAssemblyRequestsByConnection = new ( ) ;
20+
1821 private static ServerMetrics ? lastMetrics ;
1922 private static DateTime lastMetricsTimestamp ;
2023 private static TimeSpan lastTotalProcessorTime ;
@@ -28,6 +31,7 @@ public override Task OnConnectedAsync()
2831 RemoteJobAssemblyLoadContext assemblyLoadContext = new RemoteJobAssemblyLoadContext ( $ "RemoteJob_{ Guid . NewGuid ( ) } ") ;
2932
3033 _ = connections . TryAdd ( Context . ConnectionId , assemblyLoadContext ) ;
34+ _ = pendingAssemblyRequestsByConnection . TryAdd ( Context . ConnectionId , new ConcurrentDictionary < string , Task < byte [ ] > > ( ) ) ;
3135
3236 logger . LogInformation ( "Connection {ConnectionId} established" , Context . ConnectionId ) ;
3337
@@ -42,6 +46,8 @@ public override Task OnDisconnectedAsync(Exception? exception)
4246 logger . LogInformation ( "Connection {ConnectionId} disconnected" , Context . ConnectionId ) ;
4347 }
4448
49+ _ = pendingAssemblyRequestsByConnection . TryRemove ( Context . ConnectionId , out _ ) ;
50+
4551 return base . OnDisconnectedAsync ( exception ) ;
4652 }
4753
@@ -99,8 +105,6 @@ private async Task<RemoteExecutionResult> ExecuteTask(RemoteExecutionRequest req
99105 {
100106 _ = Interlocked . Increment ( ref activeTasks ) ;
101107
102- logger . LogInformation ( req . MethodName ) ;
103-
104108 try
105109 {
106110 if ( ! connections . TryGetValue ( Context . ConnectionId , out RemoteJobAssemblyLoadContext ? assemblyLoadContext ) )
@@ -282,17 +286,36 @@ private async Task<Assembly> RequestAssemblyAsync(string assemblyName)
282286 {
283287 try
284288 {
285- Guid guid = Guid . NewGuid ( ) ;
286- TaskCompletionSource < byte [ ] > tcs = new TaskCompletionSource < byte [ ] > ( ) ;
289+ if ( ! pendingAssemblyRequestsByConnection . TryGetValue ( Context . ConnectionId , out ConcurrentDictionary < string , Task < byte [ ] > > ? connectionPendingRequests ) )
290+ {
291+ throw new InvalidOperationException ( "Connection not found" ) ;
292+ }
287293
288- _ = pendingAssemblyRequests . TryAdd ( guid , tcs ) ;
294+ Task < byte [ ] > assemblyBytesTask = connectionPendingRequests . GetOrAdd ( assemblyName , key =>
295+ {
296+ return Task . Run ( async ( ) =>
297+ {
298+ try
299+ {
300+ Guid guid = Guid . NewGuid ( ) ;
301+ TaskCompletionSource < byte [ ] > tcs = new TaskCompletionSource < byte [ ] > ( ) ;
289302
290- await Clients . Caller . SendAsync ( "RequestAssembly" , assemblyName , guid ) ;
303+ _ = pendingAssemblyRequests . TryAdd ( guid , tcs ) ;
304+
305+ await Clients . Caller . SendAsync ( "RequestAssembly" , key , guid ) ;
306+
307+ // Wait for the assembly with a timeout
308+ return await tcs . Task . WaitAsync ( TimeSpan . FromSeconds ( 30 ) ) ;
309+ }
310+ finally
311+ {
312+ _ = connectionPendingRequests . TryRemove ( key , out _ ) ;
313+ }
314+ } ) ;
315+ } ) ;
291316
292- // Wait for the assembly with a timeout
293- byte [ ] assemblyBytes = await tcs . Task . WaitAsync ( TimeSpan . FromSeconds ( 30 ) ) ;
317+ byte [ ] assemblyBytes = await assemblyBytesTask ;
294318
295- // Return a temporary assembly just for metadata inspection
296319 using MemoryStream ms = new MemoryStream ( assemblyBytes ) ;
297320 return Assembly . Load ( assemblyBytes ) ;
298321 }
@@ -307,16 +330,34 @@ private async Task<byte[]> GetAssemblyBytesAsync(Assembly assembly)
307330 {
308331 string assemblyName = assembly . GetName ( ) . FullName ! ;
309332
310- Guid guid = Guid . NewGuid ( ) ;
311- TaskCompletionSource < byte [ ] > tcs = new TaskCompletionSource < byte [ ] > ( ) ;
333+ if ( ! pendingAssemblyRequestsByConnection . TryGetValue ( Context . ConnectionId , out ConcurrentDictionary < string , Task < byte [ ] > > ? connectionPendingRequests ) )
334+ {
335+ throw new InvalidOperationException ( "Connection not found" ) ;
336+ }
312337
313- _ = pendingAssemblyRequests . TryAdd ( guid , tcs ) ;
338+ Task < byte [ ] > assemblyBytesTask = connectionPendingRequests . GetOrAdd ( assemblyName , key =>
339+ {
340+ return Task . Run ( async ( ) =>
341+ {
342+ try
343+ {
344+ Guid guid = Guid . NewGuid ( ) ;
345+ TaskCompletionSource < byte [ ] > tcs = new TaskCompletionSource < byte [ ] > ( ) ;
346+
347+ _ = pendingAssemblyRequests . TryAdd ( guid , tcs ) ;
314348
315- await Clients . Caller . SendAsync ( "RequestAssembly" , assemblyName , guid ) ;
349+ await Clients . Caller . SendAsync ( "RequestAssembly" , key , guid ) ;
316350
317- byte [ ] assemblyBytes = await tcs . Task . WaitAsync ( TimeSpan . FromSeconds ( 30 ) ) ;
351+ return await tcs . Task . WaitAsync ( TimeSpan . FromSeconds ( 30 ) ) ;
352+ }
353+ finally
354+ {
355+ _ = connectionPendingRequests . TryRemove ( key , out _ ) ;
356+ }
357+ } ) ;
358+ } ) ;
318359
319- return assemblyBytes ;
360+ return await assemblyBytesTask ;
320361 }
321362
322363 private async Task PreLoadReferencedAssembliesAsync ( RemoteJobAssemblyLoadContext assemblyLoadContext , Assembly assembly )
0 commit comments