New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DolphinQt: Avoid leaking the GameListModel instance to gracefully shutdown the GameTracker and prevent a crash on exit #7714
DolphinQt: Avoid leaking the GameListModel instance to gracefully shutdown the GameTracker and prevent a crash on exit #7714
Conversation
|
@Techjar Thanks for the feedback! Much appreciated. I wasn't too sure about whether using shared pointers was a good fit for this case. Since these are all UI components, I assumed (maybe incorrectly) that all this would be running in the UI thread, therefore using shared pointers to prevent race conditions would be an overkill. As of this pull request, the GameListModel is constructed with GameList as a parent, so it lives just about as long as the GameList instance. However, it relies on By using a combination of std::shared_ptr and std::weak_ptr, we can still make the GameList own the GameListModel instance, and whilst the pointer in Settings would be auto reset when the GameList instance is destroyed. I will amend the pull request later today. Another good question... Why is Settings in charged of storing the GameListModel single instance pointer (or, after this pull request, a pointer to the single instance)? Thoughts? |
|
I don't see any reason why it is actually stored in |
0fe21c0
to
a8c16ab
Compare
|
Following @BhaaLseN 's idea, I ended up removing the cheeky However (1), after doing so, I've then discovered that there was probably a reason why it was decided to leak the GameListModel/GameTracker instances... Basically, if the reproduction steps are followed (as described in the pull request's description), there will be a big chance of deadlock when the GameTracker's destructor is executed. I've managed to get around the deadlock by calling However (2), this work-around has only surfaced more issues... The code running inside the More work is required... My feeling is that both |
|
I also think it would be better to use callbacks (or Qt signals/slots) in the classes that need access to the model. As far as I could tell, they only need the model to get a list of all games; and for that they could simply raise a signal to have someone else fill in the blanks. Could look like this (somewhat pseudo, assuming it works similarly to events in C#; at least docs say that code after // in NetPlaySetupDialog (and the other places):
signals:
void RequestGameList(std::vector<GameInfo>& list_of_games_to_be_shown);
public:
void PopulateGameList()
{
std::vector<GameInfo> list_of_games_to_show_here;
emit RequestGameList(list_of_games_to_show_here);
for (auto game : list_of_games_to_show_here) { /* ... */ }
}
// in GameList (or any other place that might be appropriate):
slots:
void FillRequestedGameList(std::vector<GameInfo>& list_of_games_to_be_shown)
{
// add all the games that should be shown, potentially filtering them first
list_of_games_to_be_shown.push_back(game1);
list_of_games_to_be_shown.push_back(game2);
list_of_games_to_be_shown.push_back(game7);
}I'm just hoping that disconnection is automatic (or at least good enough) and won't cause any problems there. As far as I can tell, signals without connected slots will just do nothing and leave the |
79d9c46
to
4227627
Compare
@BhaaLseN After removing the GameTracker's worker thread and the
|
4227627
to
363ee48
Compare
|
@BhaaLseN In the end, QPointer has been used instead. Having to define several signals and slots in several components was a bit convoluted, specially around things like the |
|
I'm pretty sure |
|
It seems it depends on the connection type (https://doc.qt.io/qt-5/signalsandslots.html#signals). In this case, you are right, slots would be executed immediately as I believe they'd be running in the same thread and all (i.e. direct connection type). There was some discussion here: https://stackoverflow.com/questions/8455887/stack-object-qt-signal-and-parameter-as-reference. I guess I wouldn't use signals with output parameters in my own application, just to be safe. |
363ee48
to
7cb4152
Compare
|
With this PR I get an empty game list at startup. After about 3 seconds the complete list plops in. This didn't happen before. |
|
Hi @Tilka , thanks for giving the patch a go and the feedback. In my tests, the list (30 games) is loaded immediately as the main window shows up. How large is your list? Platform? The only change that I can think of that may have affected performance negatively is the thread's priority, which I decided to set to the very lowest, Would you mind giving it another go? |
7cb4152
to
272844f
Compare
|
There are currently 427 items in my game list. You could probably simulate this with a few hundred dol files. The thread priority change didn't improve the speed. I'm on Linux. The games are on a network drive mounted via local WiFi, but this shouldn't matter much, on master it's fast. I'll try to figure out what's going on... |
|
Is it possible that master is just showing the stale cached game list and updates it in the background as needed whereas you are only showing the cache once it has been validated? That would explain why master crashes for me if I close it right away (probably the crash you are fixing here). |
|
That is exactly what I'm looking at at the moment. Now I'm making sure that my local |
|
I think I have spotted one(the?) problem. In short: you were right. I messed the order of the lines In master, the
|
272844f
to
0ee2781
Compare
|
Now the list shows up immediately, but when I close Dolphin right away I get this: |
|
It's not consistent though. Also, shutting down takes longer (as long as the cache verification I guess). |
|
Right... I don't think there is much to do here (but I may be wrong, as your stacktrace is not giving us all the info). In The thread is potentially blocked by one of the cache functions, which you reckon may take several seconds to complete... If it takes longer than 5, bad things are going to happen... Ideally, we could try to identify what function of the cache is taking long (is it Another option would be to increment the timeout from 5 seconds to 10 seconds... That should be a good value for everybody... |
0ee2781
to
3c7e7b1
Compare
|
I have added the aforementioned flag to In theory, you should be able to close Dolphin at any time (even when the cache is still being built up for the first time), and it should react quickly, without crashing, and without leaking the GameListModel or the GameTracker. |
|
Thanks for giving it a go, @iwubcode . After 20 months, this PR has become my nemesis. I've just realized that the failed (The fact that the last error/warning in the log points to a missing I'll update the PR to keep the signature as it was. |
925d20e
to
69d568a
Compare
|
Updated to avoid modifying signature of functions in |
|
I don't like how the "Dolphin could not find any [games]" message is shown for a second when dolphin launches before the list populates. |
Well spotted... Let me correct that... (I enforced the connections to be |
69d568a
to
de2cb33
Compare
It turned out these changes were not as straight forward as I thought they would be... I ended up reverting some of my previous changes, and fixing the original problem (i.e. crashes and/or deadlocks on shutdown) in a different way. (In fact, now the diff is actually smaller than before.) So, overall, with these changes:
|
de2cb33
to
2017fcf
Compare
|
Updated to address formatting. |
2017fcf
to
fcd97f6
Compare
|
fcd97f6
to
6252f8d
Compare
…tance. The GameListModel instance will be passed as a constructor parameter where needed.
…to avoid crashes when accessing stale, already-destroyed data, and to favor responsiveness.
…hread to prevent deadlocks on shutdown.
…led as notified by GameListRefreshRequested and GameListRefreshCompleted.
6252f8d
to
ee13e6e
Compare
|
@jordan-woyak , Although I mentioned I'd create a separate PR for the extra improvements you suggested, I ended up adding the commit to this PR. I was not too sure when this PR was going to progress, and I didn't want to create a PR with a dependency to this PR. Arguably, the last two commits of this PR could be moved to their own PR. With this final commit, both the Refresh button and the Purge Game List Cache action will be disabled only briefly. As soon as the refresh request is processed, they will be re-enabled, allowing the user to refresh/purge the list again mid-way. Current behavior in Dolphin 5.0-12716 (dirty): Demo of this PR: (For the sake of recording the videos, I added an artificial delay, so that the list would update slowly.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to behave well now and code looks good.
Thanks for approving and your time. Much appreciated. |
|
@jordan-woyak Is there anything I can do to speed things up and get this through? In the past, the PRs I worked on were merged shortly after being approved, but I have a gut feeling that this one may fall through the cracks. |
|
Thanks for merging. Very nice. |


Because the GameListModel was never destroyed, its GameTracker instance outlived the Config module, which led to some crashes on shutdown.
The fix consists of binding the lifespan of GameListModel to the GameList instance, ensuring that the GameTracker is indeed destructed.
In order to be able to destroy the GameTracker instance, some race conditions, deadlocks and other crashes had to be addressed. These issues were all caused by a bad combination of the GameTracker's worker thread and the
RunOnObject()function.To summarize the list of changes:
RunOnObject()has been replaced withQueueOnObject()in GameTracker, to prevent a deadlock on shutdown.The crash could be reproduced with the following steps: