async void OnJobProgressMessage(string clientId, JobProgressMsg msg)
        {
            // TODO: Send job progress to web clients
            using var scope = scopeProvider.CreateScope();
            var db = GetDb(scope);

            var job = await db.Jobs.Where(j => j.Id == msg.JobId).FirstOrDefaultAsync();

            if (job == null)
            {
                logger.LogError("Cannot find job {0}, error?", msg.JobId);
                return;
            }

            frontendService.OnJobStautsUpdate(msg.JobId, new Models.WebsocketApi.JobStatusUpdateMsg
            {
                JobId = msg.JobId,
                Stage = msg.Stage
            });

            if (job.Stage != msg.Stage)
            {
                job.Stage = msg.Stage;
                await db.SaveChangesAsync();
            }
        }
        public async void OnJobProgressMessage(string clientId, JobProgressMsg msg)
        {
            using var scope = scopeProvider.CreateScope();
            var db = GetDb(scope);

            FlowSnake jobId = msg.JobId;
            var       job   = await db.Jobs.Where(j => j.Id == jobId).FirstOrDefaultAsync();

            if (job == null)
            {
                logger.LogError("Cannot find job {0}, error?", jobId);
                return;
            }
            if (!ShouldChangeStage(job))
            {
                logger.LogError("Judger {0} tried to progress a stopped job {1}, error?", clientId, jobId);
                return;
            }

            frontendService.OnJobStautsUpdate(jobId, new Models.WebsocketApi.JobStatusUpdateMsg {
                JobId = jobId,
                Stage = msg.Stage
            });

            if (job.Stage != msg.Stage)
            {
                job.Stage = msg.Stage;
                await db.SaveChangesAsync();
            }

            // Clear output when job gets cancelled
            if (job.Stage == JobStage.Aborted || job.Stage == JobStage.Cancelled)
            {
                var redis   = scope.ServiceProvider.GetService <RedisService>() !;
                var redisDb = await redis.GetDatabase();

                await redisDb.KeyDeleteAsync(
                    new RedisKey[] { FormatJobStdout(jobId), FormatJobError(jobId) },
                    flags : CommandFlags.FireAndForget);
            }
        }