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
Make SO_POLL complete asynchronously in IOS_NET SO #8813
Conversation
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.
This PR sounds fine to me but there are things that should be addressed.
- You should probably squashed your 2 commits together.
- Please, have a look at Contributing.md.
- Then, check with clang-format that the formatting is correct.
| const int write_event = (POLLOUT | POLLWRBAND); | ||
| const int read_event = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI); | ||
| ufds.push_back({sock.fd, read_event | write_event, 0}); | ||
| nfds = std::max(nfds, sock.wii_fd + 1); |
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.
Good catch, the wii_fd should be used here.
|
This will leave an unanswered/hanging IPC when saving/loading states. |
|
Thanks for the comments. I'll fix these ASAP.
This means I should add a DoState method to WiiSockMan, and answer IPC calls with errors when loading from a save state, right? EDIT: Instead of making SO_POLL return an error, I made it return the events POLLHUP | POLLERR which made more sense. |
|
I think that would make the most sense, yes. |
| auto socket_iter = WiiSockets.begin(); | ||
| auto end_socks = WiiSockets.end(); | ||
| ufds.clear(); |
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.
Can't the ufds variable be local to this function rather than a class member? It doesn't seem to be used elsewhere.
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.
Is it really appropriate to allocate and deallocate a vector in an update function? clear() preserves the allocated capacity.
| n++; | ||
| } | ||
|
|
||
| auto mid = std::partition(pending_polls.begin(), pending_polls.end(), [&](auto& pcmd) { |
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.
Can't a similar logic like WiiSocket::Update be used here or is there a performance issue?
auto it = pending_polls.begin();
while (it != pending_polls.end())
{
// SOME CODE
if (shall_return || has_timeout || whatever_exit_code)
{
// Write IPC Reply
it = pending_polls.erase(it);
}
else
{
++it;
}This logic might be able to get rid of the finished_polls and failed_polls member variables.
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.
I just replaced that with an equivalent erase-remove idiom.
It still allowed me to remove these two member variables. The only performance benefit to doing it this way instead of iterating is to do only one erase operation, which doesn't really matter in typical cases (IOS doesn't allow polling more than a couple tens of file descriptors). The other benefits in using the stdlib for container manipulations are purely theoretical, but are (maybe) still there. But mostly being less error prone.
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.
My only concern about doing it this way, is that it will call begin()/end()/remove_if()/erase() every time regardless of the pending_polls size. I also think that the whole update poll logic should belong to its own method: "UpdatePollCommands" or something like that.
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.
Most (if not all - can't speak for MSVC) of these calls are inlined, the only external reference (on g++) is a realloc_insert call, probably for the push_back on ufds, which will probably never get called since its size is preallocated before the loop. I'll move all that logic into a separate function, though, it's true that it's too big to leech off another method.
|
Hm, this type error on the poll() call didn't happen on the previous windows builds. And MSVC doesn't seem to like the implicit constexpr capture from outside the scope of a lambda, even though the standard allows it. Or maybe I'm reading the error wrong. |
I've had that problem as well (though only when building with CMake, for some reason). I'd suggest just capturing it to make MSVC happy. |
| { | ||
| int native; | ||
| int wii; | ||
| } mapping[] = { |
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.
I wonder if this could (or even should) be static (and maybe const), perhaps even constexpr?
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.
constexpr seems to work
|
I set error_event as a And about poll, I had to cast to an u32 since Windows doesn't provide the posix nfds_t type... |
|
|
||
| void WiiSockMan::UpdatePollCommands() | ||
| { | ||
| constexpr static int error_event = (POLLHUP | POLLERR); |
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.
The static isn't necessary here. Given it's a trivial primitive type, this will be constant folded into expressions anyway (especially given it's constexpr).
Our convention is also to place the lifetime specifiers before mutability ones, just to keep in mind for the future.
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.
I know, but it won't build on Windows without it due to a compiler bug. Other alternative is capturing it explicitly but this implicitly discards the constexpr qualifier
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.
Sorry, I missed a few things here and there.
| @@ -685,25 +686,23 @@ s32 WiiSockMan::DeleteSocket(s32 s) | |||
|
|
|||
| void WiiSockMan::Update() | |||
| { | |||
| s32 nfds = 0; | |||
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.
You shouldn't call poll if WiiSockets is empty. Otherwise, it spams error logs on Windows. Regardless, you still need to clear previous ufds values and update poll commands.
|
It seems Windows is still having issues, I'll have look to see what is going on. |
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.
Could you please squash all your commits into a single one?
| @@ -13,6 +13,13 @@ | |||
| #include <ws2tcpip.h> | |||
| #endif | |||
|
|
|||
| // WSAPoll doesn't support POLLPRI and POLLWRBAND flags | |||
| #ifdef _WIN32 | |||
| #define UNSUPPORTED_WSAPOLL POLLPRI | POLLWRBAND | |||
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.
Sorry, that was an oversight of #3316. It was missing parentheses.
Can you please change it to a constexpr constant instead
constexpr int UNSUPPORTED_WSAPOLL = POLLPRI | POLLWRBAND;|
This PR introduces a regression in DJ Hero. I'll try to see where it's from. On masterOn this PR |
|
Does it require real nand certificates to reproduce? |
|
You'll need a real NAND and find the logging function (OSReport, ___blank, etc.) which should be found using "Generate symbols from > Signature database" if you're lucky. After, that these messages should appear on the log console if OSReport logs are enabled. At first glance, it seems to be a timing-issue since I can reproduce the issue on master if I put a breakpoint on the SOConnect function and wait about 3~5 seconds before resuming.
|
|
Unfortunately I don't have access to a real nand at the moment. |
|
No, it doesn't. I just hope, that's not yet another issue with |
|
I managed to reproduce the in-game error message without certificates under Linux. OSReport is detected without generating symbols from the signature database, but I don't see these messages. |
|
There are multiple logging functions, so you might need to generate symbols or do a bit of reverse-engineering to find this one. |
|
It actually does multiple SO_POLL calls, see attached log. |
|
Ok, I've figured it out. It seems like when you do a poll with both POLLOUT and POLLWRNORM, the OS won't set POLLWRNORM even though they are equivalent. I think the most obvious solution is to re poll with a 0 timeout with the correct events. Well, at least doing so lets me get the "DJ Hero servers are unavailable" error on Linux. But I don't know if Windows will behave the same, and how nand certs may influence the login process. |
|
IMHO, that is not the best way to address this issue since:
Unfortunately, the best way I can see to address the issue would be to revert back to I expect small performance impacts on games using |
|
Shouldn't most of these issues be solved simply by lazily pruning closed sockets from each |
|
Reverting back to On top of that you pointed out an issue forcing Dolphin to use poll twice while monitoring all events. In other word, I'm pretty sure that relying on such behaviours will cause other issues since not all poll implementations behave the same way. In sum, it's safer and less error-prone (and probably more accurate) to store poll events in the PollCommand structure and call each requested poll in an async manner in |
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.
I'll write some hardware tests to confirm some of these.
| auto& pfds = pcmd.wii_fds; | ||
| // Happens only on savestate load | ||
| int count; | ||
| if (pcmd.wii_fds[0].revents & error_event) |
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.
I think you shouldn't bother with the pollfd structures when a savestate is loaded. Just EnqueueIPCReply with -SO_ENETRESET.
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.
Wouldn't games get confused from receiving SO_ENETRESET after they initialized the SO module? I think the worst they are expected to handle is every socket hanging up.
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.
Fair point. Technically, I assume that could happens if you loose your Wi-Fi/Ethernet connection. Anyway, feel free to disregard this comment as your approach is valid.
|
Here is an hardware test: Regarding the results:
Run the commented tests at your own risk. The result is the one expected, |
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.
Thanks for your patience. After these changes based on hardware tests, I believe this PR should be ready.
| auto& pfds = pcmd.wii_fds; | ||
| // Happens only on savestate load | ||
| int count; | ||
| if (pcmd.wii_fds[0].revents & error_event) |
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.
Fair point. Technically, I assume that could happens if you loose your Wi-Fi/Ethernet connection. Anyway, feel free to disregard this comment as your approach is valid.
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.
Could you please squash your commits into a single one.
| // Negative timeout indicates wait forever | ||
| const s64 timeout = static_cast<s64>(Memory::Read_U64(request.buffer_in)); | ||
|
|
||
| const unsigned nfds = request.buffer_out_size / 0xc; |
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.
const u32 nfds|
|
||
| std::vector<pollfd_t> ufds(nfds); | ||
|
|
||
| for (int i = 0; i < nfds; ++i) | ||
| for (unsigned i = 0; i < nfds; ++i) |
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.
u32 i = 0;| } | ||
| else | ||
| { | ||
| // Update FDs to a negative FD in case they were closed: |
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.
This comment needs to be changed to something more fitting like:
// Make the behavior of poll consistent across platforms by not passing:
// - Set with invalid fds, revents is set to 0 (Linux) or POLLNVAL (Windows)
// - Set without a valid socket, raises an error on Windows| return GetHostSocket(Memory::Read_U32(pcmd.buffer_out + 0xc * i)) > 0; | ||
| }); | ||
| // move all the valid pollfds to the front of the vector | ||
| for (auto i = 0; i < mid - original_order.begin(); ++i) |
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.
Can't mid - original_order.begin() be replaced with n_valid?
| auto mid = std::partition(original_order.begin(), original_order.end(), [&](auto i) { | ||
| return GetHostSocket(Memory::Read_U32(pcmd.buffer_out + 0xc * i)) > 0; | ||
| }); | ||
| // move all the valid pollfds to the front of the vector |
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.
// Move| if (count < 0) | ||
| count = GetNetErrorCode(count, "UpdatePollCommands", false); | ||
|
|
||
| // move everything back to where they were |
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.
// Move| [this](auto& pcmd) { | ||
| const auto request = Request(pcmd.request_addr); | ||
| auto& pfds = pcmd.wii_fds; | ||
| int count = 0; |
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.
This should probably be renamed ret as it might not return the count but a network error code.
|
Could you please change/remove the commit description:
Do you have any games or homebrew that use SOPoll so we can test this PR against? |
|
Well besides DJ Hero and your test suite, no, I don't. I'll try to look around if I find some larger homebrew. I'd suspect online games with a large active modding community like MKWii to be very likely to use it. |
|
On the homebrew side: |
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.
The commit description is still not amended.
I haven't found issues with this PR while doing some testing. I haven't tried multiplayer games yet.
| int ret = 0; | ||
|
|
||
| // Happens only on savestate load | ||
| if (pcmd.wii_fds[0].revents & error_event) |
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.
if (pfds[0].revents & error_event)| // Happens only on savestate load | ||
| if (pcmd.wii_fds[0].revents & error_event) | ||
| { | ||
| ret = static_cast<int>(pcmd.wii_fds.size()); |
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.
ret = static_cast<int>(pfds.size());|
I'm sorry, I'm not much proficient with git. Isn't amending just git commit --amend and force push, or am I forgetting something here? Or is the description still not what you wanted? |
|
@nbouteme No worries. Feel free to ask or go over IRC if you're having troubles with git. In fact, you're amending your commit but not the commit message/description. During the amend process, you need to change/remove the commit description (i.e. the sentences under the commit message) with you text editor. Alternatively, you can do it directly from command-line: git commit --amend -m "IOS/Network: Make poll complete asynchronously"If you want to change the commit description you'll need to pass an extra git commit --amend -m "commit message" -m "commit description"You can check your commit history using |
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.
Otherwise, the logic LGTM. I'm going to test it against some multiplayer games later today to confirm it doesn't introduce regressions.
| const s64 timeout = static_cast<s64>(Memory::Read_U64(request.buffer_in)); | ||
|
|
||
| const u32 nfds = request.buffer_out_size / 0xc; | ||
| if (nfds <= 0 || nfds > WII_SOCKET_FD_MAX) |
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.
if (nfds == 0 || nfds > WII_SOCKET_FD_MAX)| std::iota(original_order.begin(), original_order.end(), 0); | ||
| // Select indices with valid fds | ||
| auto mid = std::partition(original_order.begin(), original_order.end(), [&](auto i) { | ||
| return GetHostSocket(Memory::Read_U32(pcmd.buffer_out + 0xc * i)) > 0; |
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.
AFAIK, 0 is a valid socket fd, so it should be >= 0.
| const u32 nfds = request.buffer_out_size / 0xc; | ||
| if (nfds <= 0 || nfds > WII_SOCKET_FD_MAX) | ||
| { | ||
| ERROR_LOG(IOS_NET, "IOCTL_SO_POLL failed: Invalid array size %d, ret= %d", nfds, -SO_EINVAL); |
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.
ret=%d (without the space)
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.
LGTM. Tested on Windows 10 with Super Smash Bros. Brawl, Mario Strikers Charged Football, DJHero. I didn't find regression with them. I tested SSBB & MSC multiplayer modes using altwfc servers.
|
@nbouteme Could you please rebase your PR to the latest master and update the |
|
Is there any particular game or case that this helps so I could properly make a demonstration of how it affects things? If it's all internally changing things, that's fine too, I just wanted to know if I could make some media for the Progress Report. Thanks! |
|
Thanks for merging, and also thanks a lot to sepalani who helped a lot to make this a proper PR. Unfortunately I don't think this change would be really visible to end users : it theoretically eliminates stutters, or possible deadlocks causing games to hang, when a game uses SO_POLL with a non-zero timeout, but I'm don't really know whether wii games do that often. I don't think they do, or else the stutter would have probably broken some games and this would have been fixed way earlier. On the other hand, it may also help with some homebrews but I don't think this is most users use cases. My use case was a mod to add some online functionnality to a game that lacked it, but using poll with a timeout of 0 didn't sit right with me... It's not as efficient as I'd like it to be because in the end, poll and select still need to be called with a 0 timeout, since IOS HLE runs on the emulation thread. This still allows people to write mods and have a behavior that's more in line with what would happen on a real wii. A thread waiting on a SO_POLL call to return will remain dormant instead of being immediately woken up then immediately sleep again, so it reduces CPU usage on PPC. If IOS HLE ran on a separate thread, then the socket polling mechanism wouldn't be executed as often and would reduce CPU usage in HLE code by a bit, but this poses a lot of harder challenges. But if/when this will eventually be done, then anything that uses non-zero timeouts on SO_POLL will use a bit less CPU. So yeah, it's not that big of a deal in the end, it's more like a convenience for mod developers. |
|
Yeah, this mixed with other async PRs seem to have eliminated some of the stuttering when connecting to servers online. If there's no great demonstration yet, once more of it is done I'll definitely be able to show off a bigger change. Mario Kart Wii in particular used to be a mess of 3 - 4 second stutters while connecting, and it's slowly been getting better. |

This is to make the behavior more consistent with the behavior on console.
Currently, SO_POLL blocks the PPC until poll() completes, so this prevents PPC threads on DolphinOS and libogc's LWP from running while waiting for the call to complete, while on a real console, IOS lets the PPC run, as with most other calls, and notifies the PPC through the usual mechanism.
I don't know if how I implemented it is the preferred way in regard to Dolphin's internals: Basically each SO_POLL operation gets queued into a poll table with the associated request, and when an event happens on a socket that is referenced in a poll table in WiiSockMan::Update, an IPC reply is generated for the request.