Skip to content

Commit

Permalink
Many experimental changes
Browse files Browse the repository at this point in the history
  • Loading branch information
peppy committed Oct 28, 2024
1 parent 9d75389 commit 4f19bfc
Showing 1 changed file with 40 additions and 56 deletions.
96 changes: 40 additions & 56 deletions GlobalRankLookupCache/Controllers/BeatmapItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,39 @@ namespace GlobalRankLookupCache.Controllers
{
internal class BeatmapItem
{
public List<int> Scores;

private readonly int beatmapId;
private readonly string highScoresTable;

public List<int> Scores;
private DateTimeOffset lastPopulation;
private int requestsSinceLastPopulation;

private readonly TaskCompletionSource<bool> populated = new TaskCompletionSource<bool>();

public BeatmapItem(int beatmapId, string highScoresTable)
{
this.beatmapId = beatmapId;
this.highScoresTable = highScoresTable;
}

private bool populationInProgress;

private DateTimeOffset lastPopulation;
private int requestsSinceLastPopulation;

private readonly TaskCompletionSource<bool> populated = new TaskCompletionSource<bool>();

public async Task<(int position, int total)> Lookup(int score)
{
requestsSinceLastPopulation++;
Interlocked.Increment(ref requestsSinceLastPopulation);

if (!populated.Task.IsCompleted && population_tasks_semaphore.CurrentCount > 0)
await Task.WhenAny(Task.Delay(1000), Task.Run(repopulateScores));

bool success = await waitForPopulation();
// may change due to re-population; use a local copy
var scores = Scores;

if (!success)
if (!populated.Task.IsCompleted)
{
// do quick lookup
using (var db = await Program.GetDatabaseConnection())
using (var cmd = db.CreateCommand())
{
Console.WriteLine($"performing quick lookup for {beatmapId}...");
Console.Write("q");

cmd.CommandTimeout = 10;
cmd.CommandText = $"select count(*) from {highScoresTable} where beatmap_id = {beatmapId} and score > {score} and hidden = 0";
Expand All @@ -47,68 +49,51 @@ public BeatmapItem(int beatmapId, string highScoresTable)
cmd.CommandText = $"select count(*) from {highScoresTable} where beatmap_id = {beatmapId} and hidden = 0";
int total = (int)(long)(await cmd.ExecuteScalarAsync())!;

Console.WriteLine($"performed quick lookup for {beatmapId} = {pos}/{total}");
Console.Write("Q");

return (pos, total);
}
}

// may change due to re-population; use a local copy
var scores = Scores;

// check whether last update was too long ago
if ((DateTime.Now - lastPopulation).TotalSeconds > scores.Count)
queuePopulation();

int result = scores.BinarySearch(score + 1);
return (scores.Count - (result < 0 ? ~result : result), scores.Count);
}

private async Task<bool> waitForPopulation()
{
if (populated.Task.IsCompleted)
return true;

queuePopulation();

await Task.WhenAny(Task.Delay(1000), populated.Task);

return populated.Task.IsCompleted;
}

private void queuePopulation()
{
if (populationInProgress)
return;

// ensure only one population occurs
lock (this)
{
if (!populationInProgress)
// For seldom requested beatmaps, skip re-population.
if (requestsSinceLastPopulation >= 5)
{
_ = Task.Run(repopulateScores);
}
else
{
populationInProgress = true;
Task.Run(repopulateScores);
Console.Write($".");
lastPopulation = DateTimeOffset.Now;
requestsSinceLastPopulation--;
}
}

int result = scores.BinarySearch(score + 1);
return (scores.Count - (result < 0 ? ~result : result), scores.Count);
}

private readonly SemaphoreSlim populationSemaphore = new SemaphoreSlim(1);

private static readonly SemaphoreSlim population_tasks_semaphore = new SemaphoreSlim(10);

private async Task repopulateScores()
{
// For seldom requested beatmaps, skip re-population.
if (requestsSinceLastPopulation < 5)
{
Console.WriteLine($"Skipping population of {beatmapId} due to few requests");
lastPopulation = DateTimeOffset.Now;
requestsSinceLastPopulation--;
// Drop excess requests. If they are common they will arrive again.
if (population_tasks_semaphore.CurrentCount == 0)
return;

if (!await populationSemaphore.WaitAsync(100))
return;
}

await population_tasks_semaphore.WaitAsync();

var scores = new List<int>();

bool isRepopulate = Scores != null;

try
{
using (var db = await Program.GetDatabaseConnection())
Expand All @@ -119,9 +104,7 @@ private async Task repopulateScores()
cmd.CommandTimeout = 120;
cmd.CommandText = $"SELECT user_id, score FROM {highScoresTable} WHERE beatmap_id = {beatmapId} AND hidden = 0";

Console.WriteLine(Scores != null
? $"Repopulating for {beatmapId} after {(int)(DateTimeOffset.Now - lastPopulation).TotalMinutes} minutes..."
: $"Populating for {beatmapId}...");
Console.Write(isRepopulate ? "r" : "p");

using (var reader = await cmd.ExecuteReaderAsync())
{
Expand All @@ -138,7 +121,7 @@ private async Task repopulateScores()

scores.Reverse();

Console.WriteLine($"Populated for {beatmapId} ({scores.Count} scores).");
Console.Write(isRepopulate ? "R" : "P");
}

Scores = scores;
Expand All @@ -148,11 +131,12 @@ private async Task repopulateScores()
}
catch
{
Console.Write("E!");
// will retry next lookup
}

population_tasks_semaphore.Release();
populationInProgress = false;
populationSemaphore.Release();
}
}
}

0 comments on commit 4f19bfc

Please sign in to comment.