Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions F1Server.Service/Processors/FinalClassificationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using F1Server.Db.Entity.Repositories;
using F1Server.Db.Entity.Tables;
using F1Server.Service.Runtime;
using F1Server.WebApi.Cache;

using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -77,6 +78,9 @@ private bool ProcessFinalClassificationPacket(SessionRuntimeData sessionRuntimeD
dbFactory.GetRepository<CarTelemetryRepository>()?.RemoveBySessionId(sessionRuntimeData.SessionDbId, true);

dbFactory.GetRepository<LapRepository>()?.RemoveWhere(l => l.SessionId == sessionRuntimeData.SessionDbId && l.DbIsInvalidLapTime == 1);

// Removed laps can change the fastest lap of the session
FastestLapPerSessionCache.InvalidateSession(sessionRuntimeData.SessionDbId);
}

return isProcessed;
Expand Down
10 changes: 10 additions & 0 deletions F1Server.Service/Processors/SessionHistoryProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using F1Server.Db.Entity.Tables;
using F1Server.Service.Cache;
using F1Server.Service.Runtime;
using F1Server.WebApi.Cache;

using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -221,6 +222,9 @@ private void UpdateFinishedLap(ILapHistoryDataBase lapData, ushort lapNumber, Se
lapDbData.IsInvalidLapTime = isInvalidLapTime;

LapRepositoryCache.AddOrUpdate(lapDbData);

// The changed lap times can change the fastest lap of the session
FastestLapPerSessionCache.InvalidateSession(lapDbData.SessionId);
}
}
else
Expand Down Expand Up @@ -296,6 +300,9 @@ private void RefreshStoredLap(LapEntity lapDbData, ILapHistoryDataBase lapData,
});

LapRepositoryCache.AddOrUpdate(lapDbData);

// The refreshed lap is completed now and can be the new fastest lap of the session
FastestLapPerSessionCache.InvalidateSession(lapDbData.SessionId);
}

/// <summary>
Expand Down Expand Up @@ -344,6 +351,9 @@ private void RefreshStoredLap(LapEntity lapDbData, ILapHistoryDataBase lapData,
else
{
LapRepositoryCache.AddOrUpdate(lapDbData);

// The new lap is completed and can be the new fastest lap of the session
FastestLapPerSessionCache.InvalidateSession(lapDbData.SessionId);
}

return lapDbData;
Expand Down
4 changes: 4 additions & 0 deletions F1Server.Service/Processors/SessionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using F1Server.Db.Entity.Tables;
using F1Server.Service.Cache;
using F1Server.Service.Runtime;
using F1Server.WebApi.Cache;

using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -482,6 +483,9 @@ private void ClearPreviousSessionData(long sessionId, RepositoryFactory dbFactor

// Remove laps
dbFactory.GetRepository<LapRepository>()?.RemoveWhere(l => l.SessionId == sessionId);

// The reused session starts without laps, so its cached fastest lap is no longer valid
FastestLapPerSessionCache.InvalidateSession(sessionId);
}
catch (Exception ex)
{
Expand Down
4 changes: 4 additions & 0 deletions F1Server.Service/Runtime/PacketProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using F1Server.Db.Entity.Repositories;
using F1Server.Db.Entity.Tables;
using F1Server.Service.Processors;
using F1Server.WebApi.Cache;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -652,6 +653,9 @@ private void RemoveInvalidSessionFromDatabase()
// Removed?
if (isRemoved != null && isRemoved.Value)
{
// No data of a removed session may stay in the cache
FastestLapPerSessionCache.RemoveSession(Session.SessionDbId);

Session = null;
}
}
Expand Down
7 changes: 7 additions & 0 deletions F1Server.Service/Runtime/ParticipantRuntimeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using F1Server.Db.Entity.Repositories;
using F1Server.Db.Entity.Tables;
using F1Server.Service.Cache;
using F1Server.WebApi.Cache;

namespace F1Server.Service.Runtime;

Expand Down Expand Up @@ -336,6 +337,12 @@ public bool CompleteLap(int lapNumber)
},
obj => LapRepositoryCache.AddOrUpdate(obj)) ?? false;
}

if (isFinished)
{
// The completed lap can be the new fastest lap of the session
FastestLapPerSessionCache.InvalidateSession(lapEntity.SessionId);
}
}

return isFinished;
Expand Down
172 changes: 146 additions & 26 deletions F1Server.Tests/Cache/FastestLapPerSessionCacheTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,21 @@ public class FastestLapPerSessionCacheTests
/// </summary>
private const uint FastestLapTime = 80000U;

/// <summary>
/// Lap time in milliseconds of a lap that is added after the fastest lap of a session was already cached
/// </summary>
private const uint FasterLapTime = 78500U;

/// <summary>
/// Reference lap time in milliseconds of the track created for the tests in this class
/// </summary>
private const uint ReferenceLapTime = 79000U;

/// <summary>
/// Format of a lap time
/// </summary>
private const string LapTimeLiteral = @"mm\:ss\.fff";

/// <summary>
/// Name of the driver created for the tests in this class
/// </summary>
Expand All @@ -56,6 +66,31 @@ public class FastestLapPerSessionCacheTests
/// </summary>
private static readonly List<long> _testSessionIds = [];

/// <summary>
/// Database id of the track created for the tests in this class
/// </summary>
private static long _testTrackId;

/// <summary>
/// Database id of the driver created for the tests in this class
/// </summary>
private static long _testDriverId;

/// <summary>
/// Database id of the nationality created for the tests in this class
/// </summary>
private static long _testNationalityId;

/// <summary>
/// Database id of the team created for the tests in this class
/// </summary>
private static long _testTeamId;

/// <summary>
/// Game session code of the last session created for the tests in this class
/// </summary>
private static ulong _lastSessionCode = 198000000000UL;

#endregion // Fields

#region Properties
Expand Down Expand Up @@ -119,36 +154,19 @@ public static async Task ClassInit(TestContext context)

Assert.IsTrue(dbFactory.GetRepository<TrackRepository>()?.Add(trackEntity), "Track entity could not be added to the database!");

_testTrackId = trackEntity.Id;
_testDriverId = driverEntity.Id;
_testNationalityId = nationalityEntity.Id;
_testTeamId = teamEntity.Id;

for (var sessionIndex = 0; sessionIndex < TestSessionCount; sessionIndex++)
{
var sessionEntity = new SessionEntity
{
SessionId = 198000000000UL + (ulong)sessionIndex,
CreationTimestamp = DateTime.UtcNow,
SessionType = SessionType.Qualifying1,
TrackId = trackEntity.Id,
GameVersionId = 1
};

Assert.IsTrue(dbFactory.GetRepository<SessionRepository>()?.Add(sessionEntity), "Session entity could not be added to the database!");

var participantEntity = new ParticipantEntity
{
SessionId = sessionEntity.Id,
DriverId = driverEntity.Id,
NationalityId = nationalityEntity.Id,
TeamId = teamEntity.Id,
DriverName = TestDriverName,
ArrayIndex = 1,
IsHumanControlled = true
};

Assert.IsTrue(dbFactory.GetRepository<ParticipantRepository>()?.Add(participantEntity), "Participant entity could not be added to the database!");
var sessionId = AddSession(dbFactory, out var participantId);

AddLap(dbFactory, sessionEntity.Id, participantEntity.Id, 1, FastestLapTime);
AddLap(dbFactory, sessionEntity.Id, participantEntity.Id, 2, FastestLapTime + 1500U);
AddLap(dbFactory, sessionId, participantId, 1, FastestLapTime);
AddLap(dbFactory, sessionId, participantId, 2, FastestLapTime + 1500U);

_testSessionIds.Add(sessionEntity.Id);
_testSessionIds.Add(sessionId);
}
}
}
Expand Down Expand Up @@ -286,10 +304,112 @@ public async Task FastestLapPerSessionCacheCancelledWarmUpDoesNotMarkCacheAsInit
}
}

/// <summary>
/// Verifies that a lap added to an already cached session is reported after the session was invalidated, so a
/// running session does not keep the fastest lap of the moment its entry was calculated
/// </summary>
/// <returns>A task that represents the asynchronous operation</returns>
[TestMethod]
public async Task FastestLapPerSessionCacheInvalidateSessionReturnsNewFastestLap()
{
long sessionId;
long participantId;

using (var dbFactory = RepositoryFactory.CreateInstance())
{
sessionId = AddSession(dbFactory, out participantId);

AddLap(dbFactory, sessionId, participantId, 1, FastestLapTime);
}

var cachedLapData = await FastestLapPerSessionCache.GetFastestLapDataForSessionAsync(sessionId).ConfigureAwait(false);

Assert.AreEqual(TimeSpan.FromMilliseconds(FastestLapTime).ToString(LapTimeLiteral), cachedLapData.FastestLap, "The only lap of the session should be reported as fastest lap!");

using (var dbFactory = RepositoryFactory.CreateInstance())
{
AddLap(dbFactory, sessionId, participantId, 2, FasterLapTime);
}

var staleLapData = await FastestLapPerSessionCache.GetFastestLapDataForSessionAsync(sessionId).ConfigureAwait(false);

Assert.AreSame(cachedLapData, staleLapData, "Without an invalidation the already cached data of the session should be returned!");

FastestLapPerSessionCache.InvalidateSession(sessionId);

var updatedLapData = await FastestLapPerSessionCache.GetFastestLapDataForSessionAsync(sessionId).ConfigureAwait(false);

Assert.AreEqual(TimeSpan.FromMilliseconds(FasterLapTime).ToString(LapTimeLiteral), updatedLapData.FastestLap, "After an invalidation the faster lap added to the session should be reported as fastest lap!");
}

/// <summary>
/// Verifies that a removed session is calculated again instead of returning the data of the deleted session
/// </summary>
/// <returns>A task that represents the asynchronous operation</returns>
[TestMethod]
public async Task FastestLapPerSessionCacheRemoveSessionDropsCachedData()
{
long sessionId;

using (var dbFactory = RepositoryFactory.CreateInstance())
{
sessionId = AddSession(dbFactory, out var participantId);

AddLap(dbFactory, sessionId, participantId, 1, FastestLapTime);
}

var cachedLapData = await FastestLapPerSessionCache.GetFastestLapDataForSessionAsync(sessionId).ConfigureAwait(false);

FastestLapPerSessionCache.RemoveSession(sessionId);

var reloadedLapData = await FastestLapPerSessionCache.GetFastestLapDataForSessionAsync(sessionId).ConfigureAwait(false);

Assert.AreNotSame(cachedLapData, reloadedLapData, "A removed session must not be served from the cache anymore!");
}

#endregion // Methods

#region Private methods

/// <summary>
/// Adds a session with one participant to the test database
/// </summary>
/// <param name="dbFactory">Repository factory used to store the session</param>
/// <param name="participantId">Database id of the participant created for the session</param>
/// <returns>Database id of the created session</returns>
private static long AddSession(RepositoryFactory dbFactory, out long participantId)
{
_lastSessionCode += 1UL;

var sessionEntity = new SessionEntity
{
SessionId = _lastSessionCode,
CreationTimestamp = DateTime.UtcNow,
SessionType = SessionType.Qualifying1,
TrackId = _testTrackId,
GameVersionId = 1
};

Assert.IsTrue(dbFactory.GetRepository<SessionRepository>()?.Add(sessionEntity), "Session entity could not be added to the database!");

var participantEntity = new ParticipantEntity
{
SessionId = sessionEntity.Id,
DriverId = _testDriverId,
NationalityId = _testNationalityId,
TeamId = _testTeamId,
DriverName = TestDriverName,
ArrayIndex = 1,
IsHumanControlled = true
};

Assert.IsTrue(dbFactory.GetRepository<ParticipantRepository>()?.Add(participantEntity), "Participant entity could not be added to the database!");

participantId = participantEntity.Id;

return sessionEntity.Id;
}

/// <summary>
/// Adds a valid completed lap to the test database
/// </summary>
Expand Down
Loading
Loading