@@ -205,8 +205,12 @@ init([]) ->
205205handle_call ({put_manifest , StreamId , Manifest , Epoch }, _From , State ) ->
206206 write_manifest (StreamId , Manifest , Epoch ),
207207 {reply , ok , State };
208- handle_call ({sync , StreamId , Seq , Epoch , Manifest , WriterNode }, _From , # state {seqs = Seqs } = State ) ->
209- Seqs1 = maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs ),
208+ handle_call (
209+ {sync , StreamId , Seq , Epoch , Manifest , WriterNode },
210+ _From ,
211+ # state {seqs = Seqs , contexts = Ctxs } = State
212+ ) ->
213+ Seqs1 = maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs , Ctxs ),
210214 {reply , ok , State # state {seqs = Seqs1 }};
211215handle_call (
212216 {apply_edits , StreamId , Edits , Seq , Epoch , WriterNode }, _From , # state {seqs = Seqs } = State
@@ -281,8 +285,11 @@ handle_cast({apply_edit, StreamId, Edit}, State) ->
281285 ok
282286 end ,
283287 {noreply , State };
284- handle_cast ({sync , StreamId , Seq , Epoch , Manifest , WriterNode }, # state {seqs = Seqs } = State ) ->
285- Seqs1 = maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs ),
288+ handle_cast (
289+ {sync , StreamId , Seq , Epoch , Manifest , WriterNode },
290+ # state {seqs = Seqs , contexts = Ctxs } = State
291+ ) ->
292+ Seqs1 = maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs , Ctxs ),
286293 {noreply , State # state {seqs = Seqs1 }};
287294handle_cast ({apply_edits , StreamId , Edits , Seq , Epoch , WriterNode }, # state {seqs = Seqs } = State ) ->
288295 case maps :get (StreamId , Seqs , undefined ) of
@@ -367,7 +374,7 @@ apply_edits_catching(StreamId, Edits, Manifest0) ->
367374% % next gap triggers a re-sync. Drop any sync that is not at least as new as
368375% % what is recorded, comparing epoch first and then sequence so a higher epoch
369376% % always wins regardless of where its sequence restarted.
370- maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs ) ->
377+ maybe_apply_sync (StreamId , Seq , Epoch , Manifest , WriterNode , Seqs , Ctxs ) ->
371378 Recorded = maps :get (StreamId , Seqs , undefined ),
372379 case is_stale_sync (Epoch , Seq , Recorded ) of
373380 true ->
@@ -382,6 +389,7 @@ maybe_apply_sync(StreamId, Seq, Epoch, Manifest, WriterNode, Seqs) ->
382389 Seqs ;
383390 false ->
384391 write_manifest (StreamId , Manifest , Epoch ),
392+ seed_first_offset_counter (StreamId , Manifest , Ctxs ),
385393 Seqs #{StreamId => {Seq , Epoch , WriterNode }}
386394 end .
387395
@@ -475,6 +483,25 @@ maybe_evaluate_retention(
475483) ->
476484 ok .
477485
486+ % % Seed the osiris first-offset and first-timestamp counters from the manifest
487+ % % on sync. Without this, the counters reflect only the local tier until the
488+ % % first retention evaluation or edit arrives, which may never happen on an idle
489+ % % stream.
490+ seed_first_offset_counter (_StreamId , # manifest {entries = <<>>}, _Ctxs ) ->
491+ ok ;
492+ seed_first_offset_counter (
493+ StreamId , # manifest {first_offset = ManifestFirst , first_timestamp = ManifestFirstTs }, Ctxs
494+ ) ->
495+ case maps :get (StreamId , Ctxs , undefined ) of
496+ # replica_ctx {counter = Cnt } ->
497+ LocalFirst = counters :get (Cnt , ? C_OSIRIS_LOG_FIRST_OFFSET ),
498+ counters :put (Cnt , ? C_OSIRIS_LOG_FIRST_OFFSET , min (LocalFirst , ManifestFirst )),
499+ LocalFirstTs = counters :get (Cnt , ? C_OSIRIS_LOG_FIRST_TIMESTAMP ),
500+ counters :put (Cnt , ? C_OSIRIS_LOG_FIRST_TIMESTAMP , min (LocalFirstTs , ManifestFirstTs ));
501+ undefined ->
502+ ok
503+ end .
504+
478505- ifdef (TEST ).
479506- include_lib (" eunit/include/eunit.hrl" ).
480507
0 commit comments