Change:
Sending in multiple PN spaces on PTO is particularly useful when the peer only has receive keys for 1 pn space, and the you don't know which.
(deleted comment, because the initially mentioned problem was incorrect. the requirement to arm the PTO timer after the handshake finishes is in Section 5.2.2)
There's some architectural inconsistency around the treatment of 1RTT PTOs at the end of the handshake. On the one hand, we are not supposed to set the PTO timer for 1RTT before the handshake is complete. This is to prevent us from retransmitting stuff the peer may not be able to process. On the other hand, we are supposed to arm the PTO timer when the handshake is confirmed. This is the point where we're guaranteed that the peer has receive keys. So there is a weird gap between handshake complete and handshake confirmed where new 1RTT packets will arm the PTO, but outstanding 0RTT or 1RTT packets from before handshake complete will not have an active PTO timer. This isn't the end of the world -- the connection will recover -- but it doesn't make any sense, and could result in either excessive PTO delays, or 1RTT probes that the peer can't decode.
NAME can we get a status update?
The PTO is always armed when there are bytes in flight(except when the loss timer is armed), it's just a matter of what PN spaces it can be armed for at a given point in time. Once the connection is handshake complete, it's immediately allowed to arm it for inflight 1-RTT ack-eliciting packets. Re-reading the text, I couldn't find anything explicitly calling out that the alarm may need to be recomputed and reset in the handshake confirmed case, though it is stated for discarding keys. As clarified in when the client is handshake complete but not confirmed, it is unsure whether the server has Handshake keys or ApplicationData keys, but it's very unlikely to have both. As such, sending something ack-eliciting from both PN spaces is safest.
The packet number space that endpoint should send probe packet is determined by GetEarliestTimeAndSpace function. It chooses packet number space where the latest packet has earliest dispatch time. Suppose client sent 2 packets: Handshake(clienthandshake) and Short packet(clientshort). Somehow it didn't get any ACK for them, on first PTO timeout, it resent Handshake (client_handshake). This makes Short packet number space becomes the earliest packet number space and next PTO timeout, client sent Short probe packet assuming that IsHandshakeCompleted() means handshake completion not handshake confirmation. I think it should instead send Handshake probe packet in this case. We have unified PTO timer and sending probe in alternate manner is a bit strange.
I agree that this is weird. It's worse than you describe because the exponent on the intervals between Handshake packets is 4, not 2. We send probes (just a PING) when probing the short packet as recommended, so it isn't too, too bad, but it is also not that great. I looked into this in mozilla/neqo if people want more context. I didn't have the time to think through what the ideal solution might be.
As discussed in neqo issue thread, which packet number spaces should be chosen depends on the situation. ngtcp2 sends Initial and Handshake probes at the same time if server sees they are in-flight when PTO timer fires so that it can coalesce them and also handle the situation that client might drop Initial key. This is a deviation from the draft-27. For the particular situation that this issue describes, I think client can just send Handshake probe packet. If server still has Handshake key, it will respond with Handshake packet. If not, then it will send HANDSHAKE_DONE frame in Short packet, and client confirms handshake completion. Sending Short probe with Handshake packet might reduce the time before client reacts the lost Short packet, but might be discarded if server does not have appropriate key.
In this case, I believe alternating is the right thing to do, because the server either has Handshake receive keys or 1-RTT receive keys, and the client doesn't know which. Because of HANDSHAKEDONE, the client will eventually complete the handshake and drop the Handshake PN space as long as the client's Handshake packet(containing the finished) gets to the server. However, this case is an excellent motivation for this text: This text was written prior to HANDSHAKEDONE, but I think there may be other reasons to use handshake complete and not confirmed in this case, so I would be very hesitant to change it without a lot of consideration. Is there a part of mozilla/neqo I should focus on? The text talks a lot about packets being marked as lost when the PTO timer fires, which makes me very confused, since QUIC doesn't mark packets as lost when the PTO fires.
Leaving aside that recommendation for the moment (which I agree is close to mandatory), the experience is pretty poor if there is only one key. You just moved to a 4x exponent on the PTO timer rather than a 2x timer. I guess that's why we have the recommendation. (For reference, neqo tells the frame sending apparatus that it lost some data when a PTO timer fires. That causes those older frames to be sent again rather than trying to find fresh data to send. That's on the principle that older data is more important. But that isn't relevant to this discussion. The main comment to look at is , which shows how the interleaving can be a little janky.) I realize that we haven't reconciled this with the requirement not to arm the application data PTO when there are handshake/initial packets outstanding. That might limit this to the one case of interleaving Initial and Handshake.
Yes, you move to 4x instead of 2x, but it's worth understanding what packets need to be lost for the 2x vs 4x to come into play. 1) The client's Handshake packet is lost twice in a row. Not that unlikely, but it's known the Client Finished blocks the server from processing 1-RTT packets, so I think there's an understanding of its importance. 2) (case from this issue) The client's Handshake packet is delivered, but the ACK is lost so the client retransmits it then that packet is lost AND both the first HANDSHAKEDONE packet and its retransmission are lost. I'm sure there are many other permutations of these, but the first case seems a lot more likely than the second case. In this case, the HANDSHAKEDONE does a nice job of mitigating some potential issues.
LGTM

Discussion:
In addition to sending data in the packet number space for which the timer expired, the sender SHOULD send ack-eliciting packets from other packet number spaces with in-flight data, coalescing packets if <del> possible. </del> <ins> possible.  This is particularly valuable when the server has both Initial and Handshake data in-flight or the client has both Handshake and ApplicationData in-flight, because the peer might only have receive keys for one of the two packet number spaces. </ins> If the sender wants to elicit a faster acknowledgement on PTO, it can skip a packet number to eliminate the ack delay.