IDisposable AssignObservables(string clientId, JudgerWebsocketWrapperTy client) { return(client.Messages.Subscribe((msg) => { logger.LogInformation($"Judger {clientId} sent message of type {msg.GetType().Name}"); switch (msg) { case JobResultMsg msg1: OnJobResultMessage(clientId, msg1); break; case JobProgressMsg msg1: OnJobProgressMessage(clientId, msg1); break; case PartialResultMsg msg1: OnPartialResultMessage(clientId, msg1); break; case ClientStatusMsg msg1: OnJudgerStatusUpdateMessage(clientId, msg1); break; case JobOutputMsg msg1: OnJobOutputMessage(clientId, msg1); break; default: logger.LogCritical("Unable to handle message type {0}", msg.GetType().Name); break; } })); }
// readonly HashSet<string> vacantJudgers = new HashSet<string>(); /// <summary> /// Try to use the provided HTTP connection to create a WebSocket connection /// between coordinator and judger. /// </summary> /// <param name="ctx"> /// The provided connection. Must be upgradable into websocket. /// </param> /// <returns> /// True if the websocket connection was made. /// </returns> public async ValueTask <bool> TryUseConnection(Microsoft.AspNetCore.Http.HttpContext ctx) { if (ctx.Request.Query.TryGetValue("token", out var auth)) { var tokenEntry = await Authenticate(auth); if (tokenEntry != null) { var ws = await ctx.WebSockets.AcceptWebSocketAsync(); var wrapper = new JudgerWebsocketWrapperTy( ws, jsonSerializerOptions, 4096, wsLogger); var judger = new Judger(auth, tokenEntry, wrapper); { using var _ = await connectionLock.LockAsync(); connections.Add(auth, judger); } logger.LogInformation($"Connected to judger {auth}"); /* * Note: * * We do not add judger to judger queue upon creation, * although it should be available. On the contrary, we rely * on the judger to send a ClientStatusMessage to declare it * is ready, and add it to queue at that time. */ try { using (var conn = judger.Socket.Messages.Connect()) using (var subscription = AssignObservables(auth, judger.Socket)) { await wrapper.WaitUntilClose(); } } catch (Exception e) { logger.LogError(e, $"Aborted connection to judger {auth}"); } logger.LogInformation($"Disconnected from judger {auth}"); { using var __ = await queueLock.LockAsync(); if (JudgerQueue.First != null) { var curr = JudgerQueue.First; while (curr != null) { if (curr.Value == auth) { JudgerQueue.Remove(curr); } curr = curr.Next; } } } { using var _ = await connectionLock.LockAsync(); connections.Remove(auth); } return(true); } else { ctx.Response.StatusCode = 401; // unauthorized } } else { ctx.Response.StatusCode = 401; // unauthorized } return(false); }
// readonly HashSet<string> vacantJudgers = new HashSet<string>(); /// <summary> /// Try to use the provided HTTP connection to create a WebSocket connection /// between coordinator and judger. /// </summary> /// <param name="ctx"> /// The provided connection. Must be upgradable into websocket. /// </param> /// <returns> /// True if the websocket connection was made. /// </returns> public async ValueTask <bool> TryUseConnection(HttpContext ctx) { if (ctx.Request.Query.TryGetValue("token", out var auth)) { var tokenEntry = await Authenticate(auth); if (tokenEntry != null) { // A connection id is passed to ensure that the client can safely // replace a previous unfinished connection created by itself. ctx.Request.Query.TryGetValue("conn", out var connId_); var connId = connId_.FirstOrDefault(); var connLock = await connectionLock.LockAsync(); if (connections.TryGetValue(auth, out var lastConn)) { if (lastConn.ConnectionId != null && connId != null && lastConn.ConnectionId == connId) { // replace this session await lastConn.Socket.Close(System.Net.WebSockets.WebSocketCloseStatus.PolicyViolation, "Duplicate connection", CancellationToken.None); connections.Remove(auth); } else { ctx.Response.StatusCode = StatusCodes.Status409Conflict; connLock.Dispose(); return(false); } } connLock.Dispose(); var ws = await ctx.WebSockets.AcceptWebSocketAsync(); var wrapper = new JudgerWebsocketWrapperTy( ws, jsonSerializerOptions, 4096); var judger = new Judger(auth, tokenEntry, wrapper, connId); { using var _ = await connectionLock.LockAsync(); connections.Add(auth, judger); } logger.LogInformation($"Connected to judger {auth}"); /* * Note: * * We do not add judger to judger queue upon creation, * although it should be available. On the contrary, we rely * on the judger to send a ClientStatusMessage to declare it * is ready, and add it to queue at that time. */ try { using (var conn = judger.Socket.Messages.Connect()) using (var subscription = AssignObservables(auth, judger.Socket)) { await wrapper.WaitUntilClose(); } } catch (Exception e) { logger.LogError(e, $"Aborted connection to judger {auth}"); } logger.LogInformation($"Disconnected from judger {auth}"); { using var __ = await queueLock.LockAsync(); if (JudgerQueue.First != null) { var curr = JudgerQueue.First; while (curr != null) { if (curr.Value == auth) { JudgerQueue.Remove(curr); } curr = curr.Next; } } } { using var _ = await connectionLock.LockAsync(); connections.Remove(auth); } return(true); } else { ctx.Response.StatusCode = 401; // unauthorized } } else { ctx.Response.StatusCode = 401; // unauthorized } return(false); }
// readonly HashSet<string> vacantJudgers = new HashSet<string>(); /// <summary> /// Try to use the provided HTTP connection to create a WebSocket connection /// between coordinator and judger. /// </summary> /// <param name="ctx"> /// The provided connection. Must be upgradable into websocket. /// </param> /// <returns> /// True if the websocket connection was made. /// </returns> public async ValueTask <bool> TryUseConnection(HttpContext ctx) { using var scope = scopeProvider.CreateScope(); if (ctx.Request.Query.TryGetValue("token", out var authStrings)) { var auth = authStrings.First(); var tokenEntry = await Authenticate(auth); if (tokenEntry != null) { // A connection id is passed to ensure that the client can safely // replace a previous unfinished connection created by itself. ctx.Request.Query.TryGetValue("conn", out var connId_); var connId = connId_.FirstOrDefault(); var connLock = await connectionLock.LockAsync(); if (connections.TryGetValue(auth, out var lastConn)) { if (lastConn.ConnectionId != null && connId != null && lastConn.ConnectionId == connId) { // replace this session await lastConn.Socket.Close(System.Net.WebSockets.WebSocketCloseStatus.PolicyViolation, "Duplicate connection", CancellationToken.None); connections.Remove(auth); } else { ctx.Response.StatusCode = StatusCodes.Status409Conflict; connLock.Dispose(); return(false); } } connLock.Dispose(); var ws = await ctx.WebSockets.AcceptWebSocketAsync(); var wrapper = new JudgerWebsocketWrapperTy( ws, jsonSerializerOptions, logger: scope.ServiceProvider.GetService <ILogger <JudgerWebsocketWrapperTy> >()); var judger = new Judger(auth, tokenEntry, wrapper, connId); { using var _ = await connectionLock.LockAsync(); connections.Add(auth, judger); } await wrapper.SendMessage(new ServerHelloMsg()); logger.LogInformation($"Connected to judger {auth}"); try { using var subscription = AssignObservables(auth, judger.Socket); await wrapper.WaitUntilClose(); } catch (Exception e) { logger.LogError(e, $"Aborted connection to judger {auth}"); } logger.LogInformation($"Disconnected from judger {auth}"); { using var _ = await connectionLock.LockAsync(); connections.Remove(auth); } return(true); } else { ctx.Response.StatusCode = 401; // unauthorized } } else { ctx.Response.StatusCode = 401; // unauthorized } return(false); }