async void OnJudgerStatusUpdateMessage(string clientId, ClientStatusMsg msg) { using (await queueLock.LockAsync()) { // should we dispatch a new job for this judger? var remainingDispatches = msg.RequestForNewTask; using (await connectionLock.LockAsync()) { if (connections.TryGetValue(clientId, out var conn)) { conn.CanAcceptNewTask = msg.CanAcceptNewTask; conn.ActiveTaskCount = msg.ActiveTaskCount; while (remainingDispatches > 0) { if (await TryDispatchJobFromDatabase(conn)) { remainingDispatches--; } else { break; } } } } for (ulong i = 0; i < remainingDispatches; i++) { JudgerQueue.AddLast(clientId); } logger.LogInformation("Status::Judger: {0}", DEBUG_LogEnumerator(JudgerQueue)); } }
private async Task <Judger?> TryGetNextUsableJudger(bool blockNewTasks) { using (await connectionLock.LockAsync()) { while (JudgerQueue.First != null) { var nextJudger = JudgerQueue.First; JudgerQueue.RemoveFirst(); if (connections.TryGetValue(nextJudger.Value, out var conn)) { if (conn.CanAcceptNewTask) { // Change the status to false, until the judger reports // it can accept new tasks again conn.CanAcceptNewTask &= !blockNewTasks; return(conn); } } } } return(null); }
// 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(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); }