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;
                }
            }));
        }
Beispiel #2
0
        // 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);
        }