-
Notifications
You must be signed in to change notification settings - Fork 153
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
Handle dropped gamestate after UDP download #286
base: master
Are you sure you want to change the base?
Conversation
a875f25
to
b318df2
Compare
I'm reworked retransmission and bring back serverId update, please check f8e7b42 |
One issue I can see is that this forces a gamestate retransmit if a map restart occurs while the client is loading the map. For example, on a server with a short warmup, this may cause slow loading clients to be sent for a second map load after the warmup completes. May I ask what is the compatibility reason for bringing back the serverid change during map restart? |
It is needed to discard outdated commands for CS_ACTIVE clients
This is expected and intentional: client must acknowledge gamestate, new map / map restart == new gamestate
Is this a real issue? Because there is no map restart from server-side at warmup end, it just sends |
I thought we had already addressed this with
That behavior appears to be specific to baseq3a. I assume it is an issue to some degree for most other mods. |
Move commands only, there's also client commands
That's correct, although that extra re-load is very unlikely and not a big deal comparing to maintaining overall coherency |
It's a reliable command, so if you happened to (coincidentally) call it right before a map restart, won't the client just keep resending and it will still be executed post-restart anyway?
Commands from wrong gamestate were already dropped, I don't understand how it relates to map restart. |
b318df2
to
42725fa
Compare
The current q3e version does seem to have fixed the issue with dropped gamestates after downloads, but it does it in a way that may not be ideal for client compatibility. The server currently expects the client to acknowledge the exact message number of the gamestate, or a gamestate retransmit is forced. Normally the client does sent the correct acknowledge, due to these lines; Lines 2056 to 2058 in 9fd02e4
However, if you comment out these lines, the client will go into an infinite loop receiving gamestates when trying to join a server running the current (post f8e7b42) version of q3e. If you add I'm concerned this is adding an unnecessary dependency on the client, where potentially subtle changes can break compatibility with newer q3e versions (but still work on every other server). Sometimes players have old versions of unusual clients and it's hard to account for the behavior of every possible client. I've updated this PR to restore a more traditional serverid-based gamestate check for non-downloading clients, with a separate message acknowledge-based check for downloading clients (with guardrails to avoid gamestate loops). |
|
I'm just looking at the first point to make sure we're on the same page, ignoring the no-snapshot or client changes for now. Are you saying the current version of this PR has a problem with incorrectly placing clients into the game? As far as I know correct serverID + clc_move should always mean client is ready to be placed in the game. It's not apparent to me there is any need or even value in additional messageAcknowledge based checks before spawning the client. Other engines (vanilla, ioq3, etc.) have no such check either and I'm not aware of any issues linked to it. Could you provide steps for how this "serverId = sv.serverId but incorrect messageAcknowledge" scenario would unfold? It's possible I missed something but at the moment I don't understand how this would happen. |
It is not correct to allow client to join by simple |
My understanding of the connection process is like this:
I still don't understand the problem. The serverId does not allow just any gamestate, it has to be one from the current map. The way the connection process works, it should generally always be the most recent one sent, but even if an older version did somehow get loaded it would likely still be fine because of the way SV_UpdateConfigstrings takes care of modified configstrings. |
|
Seems questionable this is very useful. For an overflow to happen because of extra csUpdated values accumulated prior to a gamestate resend, but not due to the (likely much longer) map loading time, seems very unlikely...
Why is it needed to skip sending snapshots to clients? Why is dedicated variable needed instead of just skipping for non CS_ACTIVE? |
To avoid drifting
It might be not needed but potential side-effects needs to be analyzed |
Could you explain what you mean by the potential overflow? I know there is always a remote chance of an overflow when using csUpdated, since it causes commands to be sent when the client goes to CS_ACTIVE which technically could cause a command count or snapshot size overflow. Clearing csUpdated values between gamestate retransmits could help reduce the amount of commands in heavy packet loss (repeated gamestate retransmit) situations, but I suspect the chance of an overflow is so rare to begin with that it doesn't make more than a marginal difference.
With the original gamestate check it shouldn't really be possible for the acknowledge to be "lost". The way the check works is:
That is part of how the gamestate drop detection works. Without some form of incremented message number you can't reliably tell the difference between a dropped gamestate and one that is just delayed. It may be possible to do a different implementation that periodically retransmits the gamestate (with the same message number) instead of relying on drop detection, but I wouldn't assume it's worth the trouble of using a new untested approach if the original way works well enough. |
every
Packet drop could happen on client -> server side and vice versa so server could send first gamestate, client receives that, ack to server is lost, server sends second gamestate, second gamestate is lost but client sends command again outgoingSequence + 1 -- so in total client received gamestate1 but server thinks its acknowledged gamestate2 - which is totally wrong so exact gamestate ack is mandatory
There is no need to send snapshots till client acknowledges gamestate, it just disrupts gamestate acknowledge, it also seems to help with delta compression disruption as I no longer see |
Yes but there is still the question of rarity. Clearing csUpdated between gamestate retransmit may make negligible practical difference in overflow chances.
In general, server sending second gamestate should imply the first gamestate is actually lost, not just the "ack" is lost. I thought I explained that in my message?
Dropped gamestate handling appears to be broken since 909ac2b. Any dropped gamestate results in a stuck client, as retransmit condition
I suspect the issue may be somewhere else. It could be related to changes in q3e to checks for creating delta frames here. |
Wasn't csupdated created because of changes made by ioq3 in the way that commands happen at certain time, I don't think the vanilla had that. |
Here is the commit and bug tracker link. It's an optimization so that if the same configstring changes multiple times while a client is loading the map, the server only sends the final version and can skip the intermediate versions. |
42725fa
to
8135f46
Compare
I reviewed the latest Q3e changes as of f0c43d3. It appears the issue of hypothetical clients that don't acknowledge the exact gamestate number has been improved; such clients will now only go into a gamestate loop if there is a dropped gamestate or UDP download, not every time connecting the server. While much better than before, I believe this can be improved further because in the normal gamestate retransmit case (wrong serverid + post-gamestate message acknowledge) the gamestate is verifiably dropped and it's not possible for the client to load an old gamestate at any point in the future regardless of any packet loss or reordering. Therefore the exact message number ack requirement is unnecessary even if the gamestate has been retransmitted and csUpdated cleared. I've updated this PR so that dropped gamestate is always detected by the original method (wrong serverid and messageAcknowledge > gamestateMessageNum). For UDP downloads there is an extra check; gamestate must be acknowledged by either a move command or exact message number ack. I think this implementation is simpler and more robust and it fixes the gamestate loop cases noted above. csUpdated is still cleared in cases where it is safe to do so. |
|
Could you explain how that code is not correct? It looks correct to me and seems consistent with ioq3, but I don't have much experience with oldServerTime behavior so there might be something I'm missing. What really seems incorrect to me is this: Quake3e/code/server/sv_ccmds.c Lines 294 to 301 in f5122f4
|
it says // this client has acknowledged the new gamestate which is NOT correct |
Removed unnecessary checks for client acknowledging the exact gamestate message number.
8135f46
to
3708e7c
Compare
Okay, so nothing wrong functionally, just the comment? I moved it under the if statement so it should be more correct now. |
Some of this might not be a big deal functionally, but it still can matter for code quality. If there are misleading assumptions it can cause trouble for somebody trying to understand the code or build on top of it even if it mostly works currently. Lines 152 to 157 in f5122f4
I'm not a fan of this for several reasons, from least to most significant:
Conversely, the reasons I like my approach with post-download specific check:
|
I wouldn't agree because my approach is more universal and verbose
Practically there is no difference between downloading and non-downloading clients: the only thing that matter is that if we sent gamestate once within new serverId (so we can accept any
Again, from server perspective there is no difference between downloading and non-downloading clients: if we sent gamestate once with pacticular Original code also implies serverId & gamestateMessageNum match for gamestate acknowledge, I see nothing there to "optimize" except |
I think this is where we have a fundamental difference in understanding. I do not see any consistency benefit at all from requiring exact gamestate messageAcknowledge number at any time. It just doesn't make sense to me. Is this based on a problem seen in practice or strictly theoretical?
Could you point to where in the original code gamestateMessageNum match is expected? |
This was your last explanation on the matter. Is this still your current understanding? I don't think it's correct as I've tried to explain before.
The problem starts at "ack to server is lost, server sends second gamestate". What does it mean "ack to server is lost"? Let's say the server sent the gamestate, with messageNum = 50. It continues to send SNAPFLAG_NOT_ACTIVE snapshots with messageNum = 51, 52, 53, ... As stated the client has received the gamestate successfully. That means CL_ParseGamestate has been called, cl.serverId has been set to the server's current serverId, and clc.serverMessageSequence has been set to 50. The message sequence may continue to increment (51, 52, ...) as snapshots arrive. It's also stated that client to server packet loss is in effect. Note the gamestate retransmit condition: |
Correction: download/whatever completes, server sends new gamestate, client packet with old
This case already handled by
It was like that when With recent state optimizations I see nothing to discuss there, original issue is already resolved |
My point is, if you exclude the UDP download case, there never is or was a problem with the client receiving an older gamestate than the last one the server sent. As I understand it, it is impossible as long as UDP downloads are a special case due to some unique client behavior. After a download completes the client hits this block here: Lines 2010 to 2021 in 959876a
The client is now at CA_CONNECTING waiting the gamestate triggered by the "donedl" command. If the gamestate is dropped, the standard retransmit method would normally work, but there is a slight problem unique to UDP downloads: if the map didn't change the client will already have the correct serverId from before the download started and will not pass the There's a few ways you can address this:
So basically UDP downloads are a unique special case, the only one where a client can send the correct serverId but not actually have the gamestate, and that's why this PR reflects that with a specific variable and check just for this condition. Otherwise the normal retransmit check with |
Currently, when a UDP download completes, there is a possibility that the gamestate message from the server is dropped due to network packet loss. Normally dropped gamestates are detected and resent by checking if client messages contain an old serverid value, but after downloads the client can actually have the correct serverid (if the map didn't change). As a result, the gamestate is never resent and the client can hang with "awaiting gamestate" message.
This fix adds an extra check for dropped gamestate for clients that just finished a UDP download, based on detecting if the client is sending messages with no move commands. Since this check is not quite as standard as the serverid method, I added some extra checks to minimize any chance of false positives in non-standard clients. This fix has no effect on non-UDP downloading clients.