@@ -28,8 +28,9 @@ import java.time.Instant
2828 * - Response caching for duplicate requests
2929 * - Server-initiated request management with cumulative acknowledgment
3030 * - Session lifecycle coordination
31- * - Cache cleanup driven by client-provided lowestRequestId (inclusive: removes requestId <= lowestRequestId)
32- * - Evicted response detection: if requestId <= highestLowestRequestIdSeen and not in cache, return
31+ * - Cache cleanup driven by client-provided lowestPendingRequestId (exclusive: removes requestId <
32+ * lowestPendingRequestId)
33+ * - Evicted response detection: if requestId < highestLowestPendingRequestIdSeen and not in cache, return
3334 * RequestError.ResponseEvicted
3435 *
3536 * ## Type Parameters
@@ -269,17 +270,17 @@ trait SessionStateMachine[UC <: Command, R, SR, UserSchema <: Tuple]
269270 *
270271 * Implementation of Raft dissertation Chapter 6.3 session management protocol:
271272 * 1. Check cache for (sessionId, requestId) 2. If cache hit, return cached response 3. If cache miss, check if
272- * requestId <= highestLowestRequestIdSeen → response was evicted, return error 4. If cache miss + requestId >
273- * highestLowestRequestIdSeen , execute command and update highestLowestRequestIdSeen
273+ * requestId < highestLowestPendingRequestIdSeen → response was evicted, return error 4. If cache miss +
274+ * requestId ≥ highestLowestPendingRequestIdSeen , execute command and update highestLowestPendingRequestIdSeen
274275 *
275- * This correctly handles out-of-order requests. The lowestRequestId from the client tells us which responses have
276- * been acknowledged (inclusive) and can be evicted . We only update highestLowestRequestIdSeen for requests we
277- * actually process.
276+ * This correctly handles out-of-order requests. The lowestPendingRequestId from the client tells us the lowest
277+ * sequence number without a response; we can evict responses with lower numbers . We only update
278+ * highestLowestPendingRequestIdSeen for requests we actually process.
278279 */
279280 private def handleClientRequest (cmd : SessionCommand .ClientRequest [UC , SR ])
280281 : State [HMap [Schema ], Either [RequestError , (cmd.command.Response , List [ServerRequestEnvelope [SR ]])]] =
281282 for
282- highestLowestSeen <- getHighestLowestRequestIdSeen (cmd.sessionId)
283+ highestLowestSeen <- getHighestLowestPendingRequestIdSeen (cmd.sessionId)
283284 cachedOpt <- getCachedResponse((cmd.sessionId, cmd.requestId))
284285 result <- cachedOpt match
285286 case Some (cachedResponse) =>
@@ -288,17 +289,17 @@ trait SessionStateMachine[UC <: Command, R, SR, UserSchema <: Tuple]
288289
289290 case None =>
290291 // Cache miss - check if response was evicted
291- // If requestId <= highestLowestRequestIdSeen , client has acknowledged receiving this response
292- if cmd.requestId.isLowerOrEqual (highestLowestSeen) then
293- // Client said "I have responses for all requestIds <= highestLowest ", so this was evicted
292+ // If requestId < highestLowestPendingRequestIdSeen , client has acknowledged receiving this response
293+ if cmd.requestId.isLowerThan (highestLowestSeen) then
294+ // Client said "I have responses for all requestIds < highestLowestPending ", so this was evicted
294295 State .succeed(Left (RequestError .ResponseEvicted (cmd.sessionId, cmd.requestId)))
295296 else
296- // requestId >= highestLowestRequestIdSeen
297+ // requestId >= highestLowestPendingRequestIdSeen
297298 // This is a valid request (not yet acknowledged), execute the command
298299 for
299- // Update highestLowestRequestIdSeen ONLY when actually executing a new request
300- _ <- updateHighestLowestRequestIdSeen (cmd.sessionId, cmd.lowestRequestId )
301- _ <- cleanupCache(cmd.sessionId, cmd.lowestRequestId )
300+ // Update highestLowestPendingRequestIdSeen ONLY when actually executing a new request
301+ _ <- updateHighestLowestPendingRequestIdSeen (cmd.sessionId, cmd.lowestPendingRequestId )
302+ _ <- cleanupCache(cmd.sessionId, cmd.lowestPendingRequestId )
302303 (serverRequestsLog, response) <- applyCommand(cmd.command, cmd.createdAt).withLog
303304 assignedRequests <- addServerRequests(cmd.createdAt, serverRequestsLog)
304305 _ <- cacheResponse((cmd.sessionId, cmd.requestId), response)
@@ -318,31 +319,31 @@ trait SessionStateMachine[UC <: Command, R, SR, UserSchema <: Tuple]
318319 ): State [HMap [Schema ], Unit ] =
319320 State .update(_.updated[" cache" ](key, response))
320321
321- /** Get the highest lowestRequestId seen from the client for a session.
322+ /** Get the highest lowestPendingRequestId seen from the client for a session.
322323 *
323- * This tracks the highest value of lowestRequestId that the client has sent, indicating which responses the client
324- * has acknowledged receiving .
324+ * This tracks the highest value of lowestPendingRequestId that the client has sent, indicating below which requestId
325+ * responses have been acknowledged and can be discarded .
325326 *
326- * Returns RequestId.zero if no lowestRequestId has been seen yet (no requests have been acknowledged).
327+ * Returns RequestId.zero if no lowestPendingRequestId has been seen yet (no requests have been acknowledged).
327328 */
328- private def getHighestLowestRequestIdSeen (sessionId : SessionId ): State [HMap [Schema ], RequestId ] =
329- State .get.map(_.get[" highestLowestRequestIdSeen " ](sessionId).getOrElse(RequestId .zero))
329+ private def getHighestLowestPendingRequestIdSeen (sessionId : SessionId ): State [HMap [Schema ], RequestId ] =
330+ State .get.map(_.get[" highestLowestPendingRequestIdSeen " ](sessionId).getOrElse(RequestId .zero))
330331
331- /** Update the highest lowestRequestId seen from the client (only if lowestRequestId > current highest ).
332+ /** Update the highest lowestPendingRequestId seen from the client (only if it increased ).
332333 *
333- * The lowestRequestId from the client indicates "I have received all responses for requestIds <= this value
334- * (inclusive)". We track the highest such value to detect evicted responses.
334+ * The lowestPendingRequestId from the client indicates "I have not yet received a response for this requestId". We
335+ * discard cached responses with lower requestIds and track the highest such value to detect evicted responses.
335336 */
336- private def updateHighestLowestRequestIdSeen (
337+ private def updateHighestLowestPendingRequestIdSeen (
337338 sessionId : SessionId ,
338- lowestRequestId : RequestId
339+ lowestPendingRequestId : RequestId
339340 ): State [HMap [Schema ], Unit ] =
340341 State .update(state =>
341- state.get[" highestLowestRequestIdSeen " ](sessionId) match
342- case Some (current) if ! lowestRequestId .isGreaterThan(current) =>
343- state // Don't update if lowestRequestId is not higher
342+ state.get[" highestLowestPendingRequestIdSeen " ](sessionId) match
343+ case Some (current) if ! lowestPendingRequestId .isGreaterThan(current) =>
344+ state // Don't update if not higher
344345 case _ =>
345- state.updated[" highestLowestRequestIdSeen " ](sessionId, lowestRequestId )
346+ state.updated[" highestLowestPendingRequestIdSeen " ](sessionId, lowestPendingRequestId )
346347 )
347348
348349 /** Handle ServerRequestAck with cumulative acknowledgment.
@@ -512,32 +513,33 @@ trait SessionStateMachine[UC <: Command, R, SR, UserSchema <: Tuple]
512513 .removedAll[" serverRequests" ](serverRequestKeysToRemove)
513514 .removed[" metadata" ](sessionId)
514515 .removed[" lastServerRequestId" ](sessionId)
515- .removed[" highestLowestRequestIdSeen " ](sessionId)
516+ .removed[" highestLowestPendingRequestIdSeen " ](sessionId)
516517 }
517518
518519 // ====================================================================================
519520 // HELPER METHODS - All return State[HMap[Schema], A] for composition
520521 // ====================================================================================
521522
522- /** Clean up cached responses based on lowestRequestId (Lowest Sequence Number Protocol).
523+ /** Clean up cached responses based on lowestPendingRequestId (Lowest Sequence Number Protocol).
523524 *
524- * Removes all cached responses for the session with requestId <= lowestRequestId. This allows the client to control
525- * cache cleanup by telling the server which responses it no longer needs (Chapter 6.3 of Raft dissertation).
525+ * Removes all cached responses for the session with requestId < lowestPendingRequestId. This allows the client to
526+ * control cache cleanup by telling the server the lowest requestId without a response (Chapter 6.3 of Raft
527+ * dissertation).
526528 *
527- * The client sends lowestRequestId to indicate "I have received all responses up to and including this ID".
529+ * The client sends lowestPendingRequestId to indicate "I have not yet received a response for this ID".
528530 *
529531 * Uses range queries to efficiently find and remove old cache entries.
530532 */
531533 private def cleanupCache (
532534 sessionId : SessionId ,
533- lowestRequestId : RequestId
535+ lowestPendingRequestId : RequestId
534536 ): State [HMap [Schema ], Unit ] =
535537 State .update { state =>
536- // Use range to find all cache entries for this session with requestId <= lowestRequestId
537- // Range is [from, until), so to include lowestRequestId, we use lowestRequestId.next as upper bound
538+ // Use range to find all cache entries for this session with requestId < lowestPendingRequestId
539+ // Range is [from, until), so use lowestPendingRequestId as the exclusive upper bound
538540 val keysToRemove = state.range[" cache" ](
539541 (sessionId, RequestId .zero),
540- (sessionId, lowestRequestId.next )
542+ (sessionId, lowestPendingRequestId )
541543 ).map((key, _) => key)
542544
543545 // Remove all old cache entries in one efficient operation
0 commit comments