on master help sign in
Remove generation field from /poll protocol
commitbd17e0a341b96cd3a8a9cee61754efcf30111231 authoriko <mail@iko.soy> dateFri 8 May 2026, 13:34 (5 months ago) parent52569b5
4 files changed+26−42
client/ApiClient.fs+3−3 client/App.fs+8−10 client/AppTypes.fs+2−3 server.knot+13−26
client/ApiClient.fs+3−3
@@ -17,7 +17,7 @@ module ApiClient =
1717 type PollEvent = { EncryptedBlob: string }
1818
1919 [<CLIMutable>]
20− type PollResponse = { Cursor: int64; Events: PollEvent array; Generation: int64 }
20+ type PollResponse = { Cursor: int64; Events: PollEvent array }
2121
2222 /// Awaits a Task without converting TaskCanceledException to F# async
2323 /// cancellation (which bypasses try...with). Re-raises it as a regular exception.
@@ -95,7 +95,7 @@ module ApiClient =
9595 return raise (ServerRejected "Message rejected by server")
9696 }
9797
98− let poll (serverUrl: string) (token: string) (cursor: int64) (generation: int64) =
99− let body = JsonSerializer.Serialize({| cursor = cursor; generation = generation |})
98+ let poll (serverUrl: string) (token: string) (cursor: int64) =
99+ let body = JsonSerializer.Serialize({| cursor = cursor |})
100100 postJson<PollResponse> pollClient $"{serverUrl}/poll" body (Some token)
101101
client/App.fs+8−10
@@ -132,7 +132,6 @@ module App =
132132 Messages = Map.empty
133133 Error = None
134134 PollCursor = 0L
135− ServerGeneration = 0L
136135 Profile = None
137136 FlushingOutbox = false
138137 PollRetries = 0
@@ -182,7 +181,7 @@ module App =
182181 Error = None
183182 PollStatus = "polling..."
184183 FlushingOutbox = true },
185− [ CmdPoll(session, model.PollCursor, model.ServerGeneration, 0); CmdFlushOutbox session ]
184+ [ CmdPoll(session, model.PollCursor, 0); CmdFlushOutbox session ]
186185 | _ -> model, []
187186
188187 | AuthErr err ->
@@ -262,7 +261,7 @@ module App =
262261 else
263262 { model with FlushingOutbox = false }, []
264263
265− | PollResult(events, status, newCursor, newGeneration) ->
264+ | PollResult(events, status, newCursor) ->
266265 let retries =
267266 if events.IsEmpty && status.StartsWith("poll error") then
268267 model.PollRetries + 1
@@ -276,12 +275,11 @@ module App =
276275 { model with
277276 PollStatus = status
278277 PollCursor = newCursor
279− ServerGeneration = newGeneration
280278 PollRetries = retries }
281279
282280 model',
283281 [ match trySession model' with
284− | Some session -> CmdPoll(session, model'.PollCursor, model'.ServerGeneration, model'.PollRetries)
282+ | Some session -> CmdPoll(session, model'.PollCursor, model'.PollRetries)
285283 | None -> ()
286284 if not events.IsEmpty then
287285 saveCmdMsg model' ]
@@ -593,13 +591,13 @@ module App =
593591 }
594592 |> Async.Start)
595593
596− | CmdPoll(session, cursor, generation, retries) ->
594+ | CmdPoll(session, cursor, retries) ->
597595 Cmd.ofEffect (fun dispatch ->
598596 async {
599597 log $"poll start cursor={cursor}"
600598
601599 try
602− let! response = poll session.Url session.Token cursor generation
600+ let! response = poll session.Url session.Token cursor
603601 log $"poll response: {response.Events.Length} events, cursor={response.Cursor}"
604602
605603 let results =
@@ -647,11 +645,11 @@ module App =
647645 | Some e -> $" [{e}]"
648646 | None -> "")
649647
650− dispatch (PollResult(events, status, response.Cursor, response.Generation))
648+ dispatch (PollResult(events, status, response.Cursor))
651649 with
652650 | :? TimeoutException ->
653651 log "poll timeout"
654− dispatch (PollResult([], "polling...", cursor, generation))
652+ dispatch (PollResult([], "polling...", cursor))
655653 | Unauthorized ->
656654 log "poll unauthorized, reauthenticating"
657655 dispatch TokenExpired
@@ -659,7 +657,7 @@ module App =
659657 log $"poll error: {ex.Message}"
660658 let delay = min (Constants.pollRetryBaseMs * (pown 2 retries)) 30000
661659 do! Async.Sleep delay
662− dispatch (PollResult([], $"poll error: {ex.Message}", cursor, generation))
660+ dispatch (PollResult([], $"poll error: {ex.Message}", cursor))
663661 }
664662 |> Async.Start)
665663
client/AppTypes.fs+2−3
@@ -57,7 +57,6 @@ module AppTypes =
5757 Messages: Map<string, ChatMessage list>
5858 Error: string option
5959 PollCursor: int64
60− ServerGeneration: int64
6160 Profile: Profile option
6261 FlushingOutbox: bool
6362 PollRetries: int
@@ -77,7 +76,7 @@ module AppTypes =
7776 | DoDisconnect
7877 | DoSend
7978 | DoSaveContact
80− | PollResult of events: IncomingEvent list * status: string * cursor: int64 * generation: int64
79+ | PollResult of events: IncomingEvent list * status: string * cursor: int64
8180 | CopyPubKey
8281 | DismissError
8382 | StateLoaded of Identity option * Data option * Profile option
@@ -99,7 +98,7 @@ module AppTypes =
9998 | CmdConnect of url: string * identity: Identity
10099 | CmdEnqueue of recipientHex: string * envelope: Envelope
101100 | CmdFlushOutbox of session: Session
102− | CmdPoll of session: Session * cursor: int64 * generation: int64 * retries: int
101+ | CmdPoll of session: Session * cursor: int64 * retries: int
103102 | CmdCopyToClipboard of string
104103 | CmdLoadState
105104 | CmdSaveIdentity of Identity
server.knot+13−26
@@ -305,9 +305,6 @@ type RateEntry = {key: Text, windowStart: Timestamp, count: Int}
305305*lastPresenceRefresh : Timestamp
306306-- Wall time of process start; gates /ready (must be ≤ lastPrunedAt).
307307*startedAt : Timestamp
308−-- Random 64-bit-ish identity stamped at startup. Clients echo it back on /poll;
309−-- any mismatch means the server restarted and the client must reset its cursor.
310−*serverGeneration : Int
311308
312309-- ============================================
313310-- Routes
@@ -326,7 +323,7 @@ route AuthApi where
326323 POST {pubkey: PubkeyHex, challenge: Text, signature: SignatureHex, revokeOthers: Bool} /auth/verify headers {clientIp: Text} -> {token: Text, expiresAt: Timestamp} = VerifyAuth
327324
328325route ClientApi where
329− POST {cursor: Timestamp, generation: Int} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp, generation: Int} = Poll
326+ POST {cursor: Timestamp} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp} = Poll
330327 POST {messages: [{to: PubkeyHex, encryptedBlob: BlobHex}]} /messages headers {authorization: Text, clientIp: Text} -> {} = SendMessages
331328
332329route FederationApi where
@@ -913,33 +910,26 @@ handleVerifyAuth = \pubkey challenge signature revokeOthers clientIp -> do
913910-- ============================================
914911-- Handler: poll
915912-- ============================================
916−-- Long-poll. The client supplies a cursor (last-seen `receivedAt`) and the
917−-- server's last-known `generation` (server identity for cursor validity). We:
918−-- * If the generation mismatches, reset the cursor and signal `cursorReset`.
913+-- Long-poll. The client supplies a cursor (last-seen `receivedAt`). We:
919914-- * Ack-and-drop everything <= the cursor (treating poll progress as ack).
920915-- * Wait on `pollWakeup` (bumped by send/heartbeat) and return new events
921916-- plus piggybacked sender-side expirations/deliveries.
922917
923−-- POST /poll: top-level dispatch. Validates the global rate gate, detects a
924−-- generation mismatch (server restart) to force a cursor reset, then hands
925−-- off to `pollOnce` for the actual long-poll body. Clients detect a server
926−-- restart by comparing the `generation` they sent to the one returned.
927−handlePoll : Int -> Int -> Text -> Text -> IO {r *pollWakeup, r *serverGeneration, r *sessions, rw *forwards, rw *globalRateCount, rw *globalRateWindowStart, rw *messages, clock} (Result HttpError {cursor: Timestamp, events: [PollEvent], generation: Int})
928−handlePoll = \cursor generation authorization clientIp -> do
918+-- POST /poll: top-level dispatch. Validates the global rate gate, then hands
919+-- off to `pollOnce` for the actual long-poll body.
920+handlePoll : Int -> Text -> Text -> IO {r *pollWakeup, r *sessions, rw *forwards, rw *globalRateCount, rw *globalRateWindowStart, rw *messages, clock} (Result HttpError {cursor: Timestamp, events: [PollEvent]})
921+handlePoll = \cursor authorization clientIp -> do
929922 t <- now
930− serverGen <- *serverGeneration
931923 g <- checkGlobalRate t
932924 if g
933925 then yield (Err {error: {status: 429, message: "rate_limited"}})
934− else do
935− let cursorWasReset = generation != serverGen && cursor > 0
936− pollOnce authorization (if cursorWasReset then 0 else cursor) clientIp serverGen t
926+ else pollOnce authorization cursor clientIp t
937927
938928-- Body of a single poll. Treats the cursor advance as an implicit ack
939929-- (`ackPolledMessages`), then runs `doPoll` to collect events (or block
940930-- until wakeup) and assembles the response.
941−pollOnce : Text -> Int -> Text -> Int -> Int<Ms> -> IO {r *pollWakeup, r *sessions, rw *forwards, rw *messages, clock} (Result HttpError {cursor: Timestamp, events: [PollEvent], generation: Int})
942−pollOnce = \authorization cursor clientIp serverGen t -> do
931+pollOnce : Text -> Int -> Text -> Int<Ms> -> IO {r *pollWakeup, r *sessions, rw *forwards, rw *messages, clock} (Result HttpError {cursor: Timestamp, events: [PollEvent]})
932+pollOnce = \authorization cursor clientIp t -> do
943933 setup <- atomic withSessionAuth authorization t clientIp (yield (Nothing {})) (\pubkey -> do
944934 let clampedCursor = clamp 0 t cursor
945935 when (clampedCursor > 0) (ackPolledMessages pubkey clampedCursor)
@@ -950,7 +940,7 @@ pollOnce = \authorization cursor clientIp serverGen t -> do
950940 Just {value: s} -> do
951941 pollResult <- doPoll s t
952942 yield (Ok {
953− value: {pollResult.events, pollResult.cursor, generation: serverGen}
943+ value: {pollResult.events, pollResult.cursor}
954944 })
955945
956946-- One pass: collect events at `t`. If empty, retry-wait on `pollWakeup`
@@ -1230,7 +1220,7 @@ api : Server Api _
12301220api = serve Api where
12311221 ReqChallenge = \{pubkey, clientIp} -> handleChallenge pubkey clientIp
12321222 VerifyAuth = \{pubkey, challenge, signature, revokeOthers, clientIp} -> handleVerifyAuth pubkey challenge signature revokeOthers clientIp
1233− Poll = \{cursor, generation, authorization, clientIp} -> handlePoll cursor generation authorization clientIp
1223+ Poll = \{cursor, authorization, clientIp} -> handlePoll cursor authorization clientIp
12341224 SendMessages = \{messages, authorization, clientIp} -> handleSendMessages messages authorization clientIp
12351225 RecvGossip = \{events, fromServer} -> handleRecvGossip events fromServer
12361226 ForwardMessage = \{toKey, encryptedBlob, fromServer} -> handleForwardMessage toKey encryptedBlob fromServer
@@ -1265,17 +1255,14 @@ pollHeartbeat = do
12651255-- ============================================
12661256-- Entry Point
12671257-- ============================================
1268−-- Stamp `startedAt` and a fresh `serverGeneration` (used by clients to detect
1269−-- server restarts and reset their poll cursor), spawn the two background
1270−-- loops, and start the listener.
1258+-- Stamp `startedAt`, spawn the two background loops, and start the listener.
12711259
12721260-- Entry point: stamp startup metadata, fork the background loops, and start
12731261-- the HTTP listener.
12741262main : IO _ {}
12751263main = do
12761264 t <- now
1277− genJitter <- randomInt 1000000
1278− atomic (do *startedAt = t; *serverGeneration = t * 1000 + genJitter)
1265+ atomic (do *startedAt = t)
12791266 logInfo ("Skrepka relay server starting on " ++ bindHost ++ ":" ++ show listenPort ++ " ...")
12801267 logInfo (" federation: " ++ (if federationEnabled then "enabled" else "disabled"))
12811268 fork backgroundPrune