Skip to content

Commit c988456

Browse files
committed
Fix RecalculateSSRs race condition
this causes crashes and rare impossible scores on startup at this point in song loading, usually a Steps's timingdata is empty. when you try to get the timingdata of a Steps in that state, it gives you the Song's timingdata. that's bad for when you rarely iterate through 2 highscores that are on different charts of the same song at the same time, because the Etaner of the timingdata gets cleared in one instance while messing with it in another function (we pass references around instead of copies, so messing with the Etaner is bad)
1 parent ff224a3 commit c988456

File tree

1 file changed

+40
-28
lines changed

1 file changed

+40
-28
lines changed

src/Etterna/Singletons/ScoreManager.cpp

+40-28
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "Etterna/FileTypes/XmlFileUtil.h"
1414
#include "arch/LoadingWindow/LoadingWindow.h"
1515
#include "RageUtil/Misc/RageThreads.h"
16+
#include <cstdint>
1617

1718
ScoreManager* SCOREMAN = NULL;
1819

@@ -226,7 +227,8 @@ ScoresForChart::SetTopScores()
226227
eligiblescores.emplace_back(hs);
227228
}
228229

229-
// if there aren't 2 noccpbs in top scores we might as well use old cc scores -mina
230+
// if there aren't 2 noccpbs in top scores we might as well use old cc
231+
// scores -mina
230232
if (eligiblescores.size() < 2) {
231233
FOREACHM(int, ScoresAtRate, ScoresByRate, i)
232234
{
@@ -237,7 +239,7 @@ ScoresForChart::SetTopScores()
237239
eligiblescores.emplace_back(hs);
238240
}
239241
}
240-
242+
241243
if (eligiblescores.empty())
242244
return;
243245

@@ -351,50 +353,56 @@ ScoreManager::RecalculateSSRs(LoadingWindow* ld, const string& profileID)
351353
}
352354
int onePercent = std::max(static_cast<int>(scores.size() / 100 * 5), 1);
353355
int scoreindex = 0;
354-
mutex stepsMutex;
355-
vector<string> currentSteps;
356-
class StepsLock
356+
357+
mutex songVectorPtrMutex;
358+
vector<std::uintptr_t> currentlyLockedSongs;
359+
// This is meant to ensure mutual exclusion for a song
360+
class SongLock
357361
{
358362
public:
359-
mutex& stepsMutex;
360-
vector<string>& currentSteps;
361-
string& ck;
362-
StepsLock(vector<string>& vec, mutex& mut, string& k)
363-
: currentSteps(vec)
364-
, stepsMutex(mut)
365-
, ck(k)
363+
mutex& songVectorPtrMutex; // This mutex guards the vector
364+
vector<std::uintptr_t>&
365+
currentlyLockedSongs; // Vector of currently locked songs
366+
std::uintptr_t song; // The song for this lock
367+
SongLock(vector<std::uintptr_t>& vec, mutex& mut, std::uintptr_t k)
368+
: currentlyLockedSongs(vec)
369+
, songVectorPtrMutex(mut)
370+
, song(k)
366371
{
367372
bool active = true;
368373
{
369-
lock_guard<mutex> lk(stepsMutex);
370-
active = find(currentSteps.begin(), currentSteps.end(), ck) !=
371-
currentSteps.end();
374+
lock_guard<mutex> lk(songVectorPtrMutex);
375+
active = find(currentlyLockedSongs.begin(),
376+
currentlyLockedSongs.end(),
377+
song) != currentlyLockedSongs.end();
372378
if (!active)
373-
currentSteps.emplace_back(ck);
379+
currentlyLockedSongs.emplace_back(song);
374380
}
375381
while (active) {
382+
// TODO: Try to make this wake up from the destructor (CondVar's
383+
// maybe)
376384
std::this_thread::sleep_for(std::chrono::milliseconds(10));
377385
{
378-
lock_guard<mutex> lk(stepsMutex);
379-
active =
380-
find(currentSteps.begin(), currentSteps.end(), ck) !=
381-
currentSteps.end();
386+
lock_guard<mutex> lk(songVectorPtrMutex);
387+
active = find(currentlyLockedSongs.begin(),
388+
currentlyLockedSongs.end(),
389+
song) != currentlyLockedSongs.end();
382390
if (!active)
383-
currentSteps.emplace_back(ck);
391+
currentlyLockedSongs.emplace_back(song);
384392
}
385393
}
386394
}
387-
~StepsLock()
395+
~SongLock()
388396
{
389-
lock_guard<mutex> lk(stepsMutex);
390-
currentSteps.erase(
391-
find(currentSteps.begin(), currentSteps.end(), ck));
397+
lock_guard<mutex> lk(songVectorPtrMutex);
398+
currentlyLockedSongs.erase(find(
399+
currentlyLockedSongs.begin(), currentlyLockedSongs.end(), song));
392400
}
393401
};
394402
function<void(std::pair<vectorIt<HighScore*>, vectorIt<HighScore*>>,
395403
ThreadData*)>
396404
callback =
397-
[&stepsMutex, &currentSteps](
405+
[&songVectorPtrMutex, &currentlyLockedSongs](
398406
std::pair<vectorIt<HighScore*>, vectorIt<HighScore*>> workload,
399407
ThreadData* data) {
400408
auto pair =
@@ -414,12 +422,15 @@ ScoreManager::RecalculateSSRs(LoadingWindow* ld, const string& profileID)
414422
if (hs->GetSSRCalcVersion() == GetCalcVersion())
415423
continue;
416424
string ck = hs->GetChartKey();
417-
StepsLock lk(currentSteps, stepsMutex, ck);
418425
Steps* steps = SONGMAN->GetStepsByChartkey(ck);
419426

420427
if (!steps)
421428
continue;
422429

430+
SongLock lk(currentlyLockedSongs,
431+
songVectorPtrMutex,
432+
reinterpret_cast<std::uintptr_t>(steps->m_pSong));
433+
423434
if (!steps->IsRecalcValid()) {
424435
hs->ResetSkillsets();
425436
continue;
@@ -716,7 +727,8 @@ ScoresAtRate::LoadFromNode(const XNode* node,
716727
SCOREMAN->RegisterScore(&scores.find(sk)->second);
717728
SCOREMAN->AddToKeyedIndex(&scores.find(sk)->second);
718729
SCOREMAN->RegisterScoreInProfile(&scores.find(sk)->second, profileID);
719-
if (scores[sk].GetSSRCalcVersion() != GetCalcVersion() && SONGMAN->IsChartLoaded(ck))
730+
if (scores[sk].GetSSRCalcVersion() != GetCalcVersion() &&
731+
SONGMAN->IsChartLoaded(ck))
720732
SCOREMAN->scorestorecalc.emplace_back(&scores[sk]);
721733
}
722734
}

0 commit comments

Comments
 (0)