mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-29 16:25:30 +00:00
Subslot error on zoning fix - functionality to handle resending SlottedMetaPackets if the client indicates it is missing one (#252)
This commit is contained in:
parent
473d4d14c5
commit
c91b3caa99
1 changed files with 83 additions and 4 deletions
|
|
@ -1,14 +1,17 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
import akka.actor.{Actor, ActorRef, MDCContextAware}
|
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
import scodec.Attempt.{Failure, Successful}
|
import scodec.Attempt.{Failure, Successful}
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
import org.log4s.MDC
|
import org.log4s.MDC
|
||||||
import MDCContextAware.Implicits._
|
import MDCContextAware.Implicits._
|
||||||
|
import net.psforever.objects.DefaultCancellable
|
||||||
import net.psforever.packet.control.{HandleGamePacket, _}
|
import net.psforever.packet.control.{HandleGamePacket, _}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In between the network side and the higher functioning side of the simulation:
|
* In between the network side and the higher functioning side of the simulation:
|
||||||
|
|
@ -39,6 +42,27 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
private var rightRef : ActorRef = ActorRef.noSender
|
private var rightRef : ActorRef = ActorRef.noSender
|
||||||
private[this] val log = org.log4s.getLogger
|
private[this] val log = org.log4s.getLogger
|
||||||
|
|
||||||
|
/*
|
||||||
|
Since the client can indicate missing packets when sending SlottedMetaPackets we should keep a history of them to resend to the client when requested with a RelatedA packet
|
||||||
|
Since the subslot counter can wrap around, we need to use a LinkedHashMap to maintain the order packets are inserted, then we can drop older entries as required
|
||||||
|
For example when a RelatedB packet arrives we can remove any entries to the left of the received ones without risking removing newer entries if the subslot counter wraps around back to 0
|
||||||
|
*/
|
||||||
|
private var slottedPacketLog : mutable.LinkedHashMap[Int, ByteVector] = mutable.LinkedHashMap()
|
||||||
|
|
||||||
|
// Due to the fact the client can send `RelatedA` packets out of order, we need to keep a buffer of which subslots arrived correctly, order them
|
||||||
|
// and then act accordingly to send the missing subslot packet after a specified timeout
|
||||||
|
private var relatedALog : ArrayBuffer[Int] = ArrayBuffer()
|
||||||
|
private var relatedABufferTimeout : Cancellable = DefaultCancellable.obj
|
||||||
|
|
||||||
|
def AddSlottedPacketToLog(subslot: Int, packet : ByteVector): Unit = {
|
||||||
|
val log_limit = 100 // Number of SlottedMetaPackets to keep in history
|
||||||
|
if(slottedPacketLog.size > log_limit) {
|
||||||
|
slottedPacketLog = slottedPacketLog.drop(slottedPacketLog.size - log_limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
slottedPacketLog{subslot} = packet
|
||||||
|
}
|
||||||
|
|
||||||
override def postStop() = {
|
override def postStop() = {
|
||||||
subslotOutbound = 0 //in case this `Actor` restarts
|
subslotOutbound = 0 //in case this `Actor` restarts
|
||||||
super.postStop()
|
super.postStop()
|
||||||
|
|
@ -67,6 +91,41 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
def Established : Receive = {
|
def Established : Receive = {
|
||||||
|
case PacketCodingActor.SubslotResend() => {
|
||||||
|
log.trace("Subslot resend timeout reached")
|
||||||
|
relatedABufferTimeout.cancel()
|
||||||
|
log.trace(s"Client indicated successful subslots ${relatedALog.sortBy(x => x).mkString(" ")}")
|
||||||
|
|
||||||
|
// If a non-contiguous range of RelatedA packets were received we may need to send multiple missing packets, thus split the array into contiguous ranges
|
||||||
|
val sorted_log = relatedALog.sortBy(x => x)
|
||||||
|
|
||||||
|
val split_logs : ArrayBuffer[ArrayBuffer[Int]] = new ArrayBuffer[ArrayBuffer[Int]]()
|
||||||
|
var curr : ArrayBuffer[Int] = ArrayBuffer()
|
||||||
|
for(i <- 0 to sorted_log.size - 1) {
|
||||||
|
if(i == 0 || (sorted_log(i) != sorted_log(i-1)+1)) {
|
||||||
|
curr = new ArrayBuffer()
|
||||||
|
split_logs.append(curr)
|
||||||
|
}
|
||||||
|
curr.append(sorted_log(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(split_logs.size > 1) log.trace(s"Split successful subslots into ${split_logs.size} contiguous chunks")
|
||||||
|
|
||||||
|
for (range <- split_logs) {
|
||||||
|
log.trace(s"Processing chunk ${range.mkString(" ")}")
|
||||||
|
val first_accepted_subslot = range.min
|
||||||
|
val missing_subslot = first_accepted_subslot - 1
|
||||||
|
slottedPacketLog.get(missing_subslot) match {
|
||||||
|
case Some(packet: ByteVector) =>
|
||||||
|
log.info(s"Resending packet with subslot: $missing_subslot to session: ${sessionId}")
|
||||||
|
sendResponseLeft(packet)
|
||||||
|
case None =>
|
||||||
|
log.error(s"Couldn't find packet with subslot: ${missing_subslot} to resend to session ${sessionId}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relatedALog.clear()
|
||||||
|
}
|
||||||
case RawPacket(msg) =>
|
case RawPacket(msg) =>
|
||||||
if(sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
if(sender == rightRef) { //from LSA, WSA, etc., to network - encode
|
||||||
mtuLimit(msg)
|
mtuLimit(msg)
|
||||||
|
|
@ -175,8 +234,10 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
def handleSplitPacket(data : ByteVector) : Unit = {
|
def handleSplitPacket(data : ByteVector) : Unit = {
|
||||||
val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket
|
val lim = PacketCodingActor.MTU_LIMIT_BYTES - 4 //4 bytes is the base size of SlottedMetaPacket
|
||||||
data.grouped(lim).foreach(bvec => {
|
data.grouped(lim).foreach(bvec => {
|
||||||
PacketCoding.EncodePacket(SlottedMetaPacket(4, Subslot, bvec)) match {
|
val subslot = Subslot
|
||||||
|
PacketCoding.EncodePacket(SlottedMetaPacket(4, subslot, bvec)) match {
|
||||||
case Successful(bdata) =>
|
case Successful(bdata) =>
|
||||||
|
AddSlottedPacketToLog(subslot, bdata.toByteVector)
|
||||||
sendResponseLeft(bdata.toByteVector)
|
sendResponseLeft(bdata.toByteVector)
|
||||||
case f : Failure =>
|
case f : Failure =>
|
||||||
log.error(s"$f")
|
log.error(s"$f")
|
||||||
|
|
@ -235,8 +296,10 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
* @param data an encoded packet
|
* @param data an encoded packet
|
||||||
*/
|
*/
|
||||||
def handleBundlePacket(data : ByteVector) : Unit = {
|
def handleBundlePacket(data : ByteVector) : Unit = {
|
||||||
PacketCoding.EncodePacket(SlottedMetaPacket(0, Subslot, data)) match {
|
val subslot = Subslot
|
||||||
|
PacketCoding.EncodePacket(SlottedMetaPacket(0, subslot, data)) match {
|
||||||
case Successful(bdata) =>
|
case Successful(bdata) =>
|
||||||
|
AddSlottedPacketToLog(subslot, bdata.toByteVector)
|
||||||
sendResponseLeft(bdata.toByteVector)
|
sendResponseLeft(bdata.toByteVector)
|
||||||
case Failure(e) =>
|
case Failure(e) =>
|
||||||
log.warn(s"bundling failed on SlottedMetaPacket creation: - $e")
|
log.warn(s"bundling failed on SlottedMetaPacket creation: - $e")
|
||||||
|
|
@ -309,11 +372,25 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
packets.foreach { UnmarshalInnerPacket(_, "the inner packet of a MultiPacketEx") }
|
packets.foreach { UnmarshalInnerPacket(_, "the inner packet of a MultiPacketEx") }
|
||||||
|
|
||||||
case RelatedA(slot, subslot) =>
|
case RelatedA(slot, subslot) =>
|
||||||
log.error(s"result $slot: subslot $subslot was in error")
|
log.trace(s"Client indicated a packet is missing prior to slot: $slot subslot: $subslot")
|
||||||
|
|
||||||
|
relatedALog += subslot
|
||||||
|
|
||||||
|
// (re)start the timeout period, if no more RelatedA packets are sent before the timeout period elapses the missing packet(s) will be resent
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
relatedABufferTimeout.cancel()
|
||||||
|
relatedABufferTimeout = context.system.scheduler.scheduleOnce(100 milliseconds, self, PacketCodingActor.SubslotResend())
|
||||||
|
|
||||||
case RelatedB(slot, subslot) =>
|
case RelatedB(slot, subslot) =>
|
||||||
log.trace(s"result $slot: subslot $subslot accepted")
|
log.trace(s"result $slot: subslot $subslot accepted")
|
||||||
|
|
||||||
|
// The client has indicated it's received up to a certain subslot, that means we can purge the log of any subslots prior to and including the confirmed subslot
|
||||||
|
// Find where this subslot is stored in the packet log (if at all) and drop anything to the left of it, including itself
|
||||||
|
val pos = slottedPacketLog.keySet.toArray.indexOf(subslot)
|
||||||
|
if(pos != -1) {
|
||||||
|
slottedPacketLog = slottedPacketLog.drop(pos+1)
|
||||||
|
log.trace(s"Subslots left in log: ${slottedPacketLog.keySet.toString()}")
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
sendResponseRight(container)
|
sendResponseRight(container)
|
||||||
}
|
}
|
||||||
|
|
@ -397,4 +474,6 @@ class PacketCodingActor extends Actor with MDCContextAware {
|
||||||
|
|
||||||
object PacketCodingActor {
|
object PacketCodingActor {
|
||||||
final val MTU_LIMIT_BYTES : Int = 467
|
final val MTU_LIMIT_BYTES : Int = 467
|
||||||
|
|
||||||
|
private final case class SubslotResend()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue