Skip to content

Commit

Permalink
Allow configuring grading script concurrency and timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
JanWichelmann committed Jan 1, 2025
1 parent 4a130ba commit 34a31ea
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 31 deletions.
8 changes: 6 additions & 2 deletions src/Ctf4e.LabServer/Controllers/DashboardController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
using Microsoft.AspNetCore.Mvc;
using Ctf4e.LabServer.Constants;
using Ctf4e.LabServer.InputModels;
using Ctf4e.LabServer.Options;
using Ctf4e.LabServer.Services;
using Ctf4e.Utilities;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Ctf4e.LabServer.Controllers;

[Route("")]
[Route("group")]
[Authorize]
public class DashboardController(IStateService stateService, ICtfApiClient ctfApiClient, ILabConfigurationService labConfiguration)
public class DashboardController(IStateService stateService, ICtfApiClient ctfApiClient, ILabConfigurationService labConfiguration, IOptions<LabOptions> options)
: ControllerBase<DashboardController>
{
protected override MenuItems ActiveMenuItem => MenuItems.Group;
Expand Down Expand Up @@ -67,7 +69,9 @@ public async Task<IActionResult> ShowDashboardAsync()
private async Task<bool> CheckInputAsync(int exerciseId, object input)
{
// Don't allow the user to cancel this too early, but also ensure that the application doesn't block too long
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
// TODO we may want to implement a JS-based grading button that indicates that the grading process is running and does not leave the site in a loading state forever
int timeout = options.Value.DockerContainerGradingTimeout ?? 30;
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout));

// Get current user
int userId = GetCurrentUser().UserId;
Expand Down
4 changes: 4 additions & 0 deletions src/Ctf4e.LabServer/Options/LabOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public class LabOptions

public string DockerContainerGradeScriptPath { get; set; }

public int? DockerContainerGradingConcurrencyCount { get; set; }

public int? DockerContainerGradingTimeout { get; set; }

public bool PassAsGroup { get; set; }

public string PageTitle { get; set; }
Expand Down
71 changes: 42 additions & 29 deletions src/Ctf4e.LabServer/Services/DockerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ public class DockerService : IDockerService, IDisposable
private readonly IOptions<LabOptions> _options;

private readonly DockerClient _dockerClient;
private readonly SemaphoreSlim _gradingLock;

public DockerService(IOptions<LabOptions> options)
{
_options = options;

int concurrencyCount = _options.Value.DockerContainerGradingConcurrencyCount ?? 100;
_gradingLock = new SemaphoreSlim(concurrencyCount, concurrencyCount);

// Only initialize this if container support is enabled
if(_options.Value.EnableDocker)
{
Expand Down Expand Up @@ -73,40 +77,49 @@ public async Task InitUserAsync(int userId, string userName, string password, Ca

if(string.IsNullOrWhiteSpace(gradingScriptPath))
throw new NotSupportedException("Grading script is not specified.");
// Prepare command
bool stringInputPresent = input != null;
var execCreateResponse = await _dockerClient.Exec.ExecCreateContainerAsync(containerName, new ContainerExecCreateParameters

// Wait for turn
await _gradingLock.WaitAsync(cancellationToken);
try
{
AttachStdin = stringInputPresent,
AttachStderr = true,
AttachStdout = true,
Tty = false,
Cmd = new List<string>
// Prepare command
bool stringInputPresent = input != null;
var execCreateResponse = await _dockerClient.Exec.ExecCreateContainerAsync(containerName, new ContainerExecCreateParameters
{
gradingScriptPath,
userId.ToString(),
exerciseId.ToString()
},
Detach = false
}, cancellationToken);
AttachStdin = stringInputPresent,
AttachStderr = true,
AttachStdout = true,
Tty = false,
Cmd = new List<string>
{
gradingScriptPath,
userId.ToString(),
exerciseId.ToString()
},
Detach = false
}, cancellationToken);

// Run command
var execStream = await _dockerClient.Exec.StartAndAttachContainerExecAsync(execCreateResponse.ID, false, cancellationToken);

// Transmit input, if present
if(stringInputPresent)
{
var inputBytes = Encoding.UTF8.GetBytes(input);
await execStream.WriteAsync(inputBytes, 0, inputBytes.Length, cancellationToken);
execStream.CloseWrite();
}
// Run command
var execStream = await _dockerClient.Exec.StartAndAttachContainerExecAsync(execCreateResponse.ID, false, cancellationToken);

// Wait for completion
var (stdout, stderr) = await execStream.ReadOutputToEndAsync(cancellationToken);
// Transmit input, if present
if(stringInputPresent)
{
var inputBytes = Encoding.UTF8.GetBytes(input);
await execStream.WriteAsync(inputBytes, 0, inputBytes.Length, cancellationToken);
execStream.CloseWrite();
}

// Wait for completion
var (stdout, stderr) = await execStream.ReadOutputToEndAsync(cancellationToken);

// Passed?
return (stdout.Trim() == "1", stderr);
// Passed?
return (stdout.Trim() == "1", stderr);
}
finally
{
_gradingLock.Release();
}
}

public void Dispose()
Expand Down

0 comments on commit 34a31ea

Please sign in to comment.