Repository: apple/swift-nio
File: Sources/NIOCore/ChannelHandlers.swift (Swift)

Code before:
                if let scheduled = self.scheduledRead {
                    scheduled.cancel()
                    scheduleRead(at: self.nextReadDeadlineNS!, context: context)
                }
            }
        } else {
            context.fireErrorCaught(error)
        }
    }

    public func channelInactive(context: ChannelHandlerContext) {
        if let scheduled = self.scheduledRead {
            scheduled.cancel()
            self.scheduledRead = nil
        }
        self.nextReadDeadlineNS = nil
        context.fireChannelInactive()
    }

    public func handlerRemoved(context: ChannelHandlerContext) {
        if let scheduled = self.scheduledRead {
            // Cancel the previous scheduled read and trigger a read directly. This is needed as otherwise we may never read again.
            scheduled.cancel()
            self.scheduledRead = nil
            context.read()
        }
        self.nextReadDeadlineNS = nil
    }

    private func scheduleRead(at: NIODeadline, context: ChannelHandlerContext) {
        self.scheduledRead = context.eventLoop.assumeIsolated().scheduleTask(deadline: at) {
            self.doRead(context)
        }
    }

    private func doRead(_ context: ChannelHandlerContext) {
        // Reset the backoff time and read.
        self.nextReadDeadlineNS = nil
        self.scheduledRead = nil
        context.read()
    }
}

@available(*, unavailable)
extension AcceptBackoffHandler: Sendable {}

/**
 ChannelHandler implementation which enforces back-pressure by stopping to read from the remote peer when it cannot write back fast enough.
 It will start reading again once pending data was written.
*/
public final class BackPressureHandler: ChannelDuplexHandler, RemovableChannelHandler {

Review comment (suggestion):
I'm inclined to make this behaviour configurable. Users may be relying on the existing reporting, which makes sense as a default behaviour, but for the use-case we have it may well be sensible to suppress the error when it is "handled".

Code after:
            if now >= deadline {
                // The backoff already expired, just do a read.
                doRead(context)
            } else {
                // Schedule the read to be executed after the backoff time elapsed.
                scheduleRead(at: deadline, context: context)
            }
        } else {
            context.read()
        }
    }

    public func errorCaught(context: ChannelHandlerContext, error: Error) {
        guard let ioError = error as? IOError else {
            context.fireErrorCaught(error)
            return
        }

        if let amount = backoffProvider(ioError) {
            self.nextReadDeadlineNS = .now() + amount
            if let scheduled = self.scheduledRead {
                scheduled.cancel()
                scheduleRead(at: self.nextReadDeadlineNS!, context: context)
            }
        }

        if self.shouldForwardIOErrorCaught {
            context.fireErrorCaught(error)
        }
    }

    public func channelInactive(context: ChannelHandlerContext) {
        if let scheduled = self.scheduledRead {
            scheduled.cancel()
            self.scheduledRead = nil
        }
        self.nextReadDeadlineNS = nil
        context.fireChannelInactive()
    }

    public func handlerRemoved(context: ChannelHandlerContext) {
        if let scheduled = self.scheduledRead {
            // Cancel the previous scheduled read and trigger a read directly. This is needed as otherwise we may never read again.
            scheduled.cancel()
            self.scheduledRead = nil
            context.read()
        }
        self.nextReadDeadlineNS = nil
    }

    private func scheduleRead(at: NIODeadline, context: ChannelHandlerContext) {