feat: sync multilayer td integration
This commit is contained in:
parent
20a310f6ca
commit
766c5db992
491 changed files with 26235 additions and 35340 deletions
|
|
@ -148,7 +148,14 @@ func TestGetStateDoesNotConfirmUnfetchedEvents(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("GetState after difference: %v", err)
|
||||
}
|
||||
if st.Pts != 2 {
|
||||
t.Fatalf("GetState after difference pts=%d, want confirmed pts=2", st.Pts)
|
||||
if st.Pts != 1 {
|
||||
t.Fatalf("GetState before delivery commit pts=%d, want confirmed pts=1", st.Pts)
|
||||
}
|
||||
if err := svc.CommitDeliveredState(ctx, authKeyID, userID, diff.State, domain.UpdateStateCommitDeliveredOnly); err != nil {
|
||||
t.Fatalf("CommitDeliveredState: %v", err)
|
||||
}
|
||||
st, err = svc.GetState(ctx, authKeyID, userID)
|
||||
if err != nil || st.Pts != 2 {
|
||||
t.Fatalf("GetState after delivery commit = %+v err=%v, want pts=2", st, err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,27 +135,34 @@ func (s *Service) ConfirmEvent(ctx context.Context, authKeyID [8]byte, userID in
|
|||
return s.saveConfirmedState(ctx, authKeyID, userID, domain.UpdateState{Pts: event.Pts, Date: date, Seq: 0})
|
||||
}
|
||||
|
||||
// AcknowledgeCurrentState 返回账号当前最大连续状态,并把该设备的确认水位推进到此。
|
||||
//
|
||||
// 供 updates.getState 使用:协议语义是客户端宣告「从现在开始同步」,启动期的
|
||||
// 离线数据由 getDialogs 快照承载(TDesktop 不持久化 pts,每次启动都走此路径)。
|
||||
// 若改为返回设备旧确认水位,客户端会在 getDialogs 最新快照之上再重放历史差分,
|
||||
// 造成未读重复累计、dialog 预览被旧消息抢占。持久化 pts 的客户端(Android)
|
||||
// 启动时直接带本地 pts 调 getDifference,不经过 getState,不受影响。
|
||||
func (s *Service) AcknowledgeCurrentState(ctx context.Context, authKeyID [8]byte, userID int64) (domain.UpdateState, error) {
|
||||
st, err := s.currentState(ctx, userID)
|
||||
// ObserveDifferenceRequest records only the cursor a client carried into this
|
||||
// request. It is deliberately independent from response delivery: even when
|
||||
// encoding or the socket write later fails, the request still proves the client
|
||||
// already owned this (clamped) cursor before contacting us.
|
||||
func (s *Service) ObserveDifferenceRequest(ctx context.Context, authKeyID [8]byte, userID int64, from domain.UpdateState) (domain.UpdateState, error) {
|
||||
current, err := s.currentState(ctx, userID)
|
||||
if err != nil {
|
||||
return domain.UpdateState{}, err
|
||||
}
|
||||
if err := s.saveConfirmedState(ctx, authKeyID, userID, st); err != nil {
|
||||
from = clampDifferenceState(from, current)
|
||||
if err := s.observeClientState(ctx, authKeyID, userID, from); err != nil {
|
||||
return domain.UpdateState{}, err
|
||||
}
|
||||
// getState 明确建立“从当前快照开始同步”的 baseline;即使响应丢失,客户端也会
|
||||
// 重试 getState/重新拉 snapshot,而不会依赖 baseline 之前的 durable event。
|
||||
if err := s.observeClientState(ctx, authKeyID, userID, st); err != nil {
|
||||
return domain.UpdateState{}, err
|
||||
return from, nil
|
||||
}
|
||||
|
||||
// CommitDeliveredState persists the exact cursor justified by a physically
|
||||
// delivered RPC result. The store owns the atomic/monotonic invariant because
|
||||
// delivery callbacks from different responses may complete out of order.
|
||||
func (s *Service) CommitDeliveredState(ctx context.Context, authKeyID [8]byte, userID int64, st domain.UpdateState, mode domain.UpdateStateCommitMode) error {
|
||||
if s.states == nil {
|
||||
return nil
|
||||
}
|
||||
return st, nil
|
||||
if mode != domain.UpdateStateCommitDeliveredOnly && mode != domain.UpdateStateCommitDeliveredAndObservedBaseline {
|
||||
return fmt.Errorf("invalid delivered update state commit mode %d", mode)
|
||||
}
|
||||
st.Seq = 0
|
||||
return s.states.CommitDeliveredState(ctx, authKeyID, userID, st, mode)
|
||||
}
|
||||
|
||||
// getDifferenceLimit 是单次 getDifference 返回的最大连续事件数;超出置 Partial 让客户端翻页。
|
||||
|
|
@ -171,19 +178,9 @@ func (s *Service) GetDifference(ctx context.Context, authKeyID [8]byte, userID i
|
|||
if err != nil {
|
||||
return domain.UpdateDifference{}, err
|
||||
}
|
||||
// 只把客户端在本次请求中实际带回的 cursor 记为 observed。绝不能把本次将要
|
||||
// 返回的 State 当确认:响应可能在 socket/进程故障中丢失。恶意/损坏客户端带来的
|
||||
// 超前 pts 钳到账号当前连续水位,避免把 retention 安全边界推过 durable truth。
|
||||
observed := from
|
||||
if observed.Pts < 0 {
|
||||
observed.Pts = 0
|
||||
}
|
||||
if observed.Pts > st.Pts {
|
||||
observed.Pts = st.Pts
|
||||
}
|
||||
if err := s.observeClientState(ctx, authKeyID, userID, observed); err != nil {
|
||||
return domain.UpdateDifference{}, err
|
||||
}
|
||||
// Computation is pure with respect to device confirmed/observed state. The
|
||||
// request observer and physical-delivery commit are explicit caller phases.
|
||||
from = clampDifferenceState(from, st)
|
||||
// TDesktop 不支持账号级 updates.differenceTooLong。retention 只能删除所有授权
|
||||
// 设备都已确认的共同前缀;当前设备若仍带更旧 pts,用一个空的普通
|
||||
// differenceSlice 把 IntermediateState 推进到已确认 checkpoint,再从 live tail 续拉。
|
||||
|
|
@ -196,9 +193,6 @@ func (s *Service) GetDifference(ctx context.Context, authKeyID [8]byte, userID i
|
|||
if from.Date != 0 {
|
||||
st.Date = from.Date
|
||||
}
|
||||
if err := s.saveConfirmedState(ctx, authKeyID, userID, st); err != nil {
|
||||
return domain.UpdateDifference{}, err
|
||||
}
|
||||
return domain.UpdateDifference{State: st}, nil
|
||||
}
|
||||
events, err := s.events.ListAfter(ctx, userID, from.Pts, getDifferenceLimit)
|
||||
|
|
@ -246,9 +240,6 @@ func (s *Service) GetDifference(ctx context.Context, authKeyID [8]byte, userID i
|
|||
if len(contiguous) > 0 {
|
||||
out.Date = contiguous[len(contiguous)-1].Date
|
||||
}
|
||||
if err := s.saveConfirmedState(ctx, authKeyID, userID, out); err != nil {
|
||||
return domain.UpdateDifference{}, err
|
||||
}
|
||||
return domain.UpdateDifference{
|
||||
State: out,
|
||||
Events: contiguous,
|
||||
|
|
@ -276,12 +267,20 @@ func (s *Service) retainedPrefixCheckpoint(ctx context.Context, authKeyID [8]byt
|
|||
} else if checkpoint.Date == 0 {
|
||||
checkpoint.Date = current.Date
|
||||
}
|
||||
if err := s.saveConfirmedState(ctx, authKeyID, userID, checkpoint); err != nil {
|
||||
return domain.UpdateDifference{}, false, err
|
||||
}
|
||||
return domain.UpdateDifference{State: checkpoint, Partial: true}, true, nil
|
||||
}
|
||||
|
||||
func clampDifferenceState(from, current domain.UpdateState) domain.UpdateState {
|
||||
if from.Pts < 0 {
|
||||
from.Pts = 0
|
||||
}
|
||||
if from.Pts > current.Pts {
|
||||
from.Pts = current.Pts
|
||||
}
|
||||
from.Seq = 0
|
||||
return from
|
||||
}
|
||||
|
||||
func (s *Service) currentState(ctx context.Context, userID int64) (domain.UpdateState, error) {
|
||||
current, err := s.currentPts(ctx, userID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -490,11 +490,10 @@ func TestDeleteMessagesPtsRangeFeedsGetDifference(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestAcknowledgeCurrentStateAdvancesConfirmedWatermark 验证 updates.getState
|
||||
// 的语义:返回账号当前最新连续 pts(而非设备旧确认水位),并把确认水位推进
|
||||
// 到此——TDesktop 不持久化 pts,启动靠 getState+getDialogs 快照对齐,返回旧
|
||||
// 水位会诱导其重放快照前差分(未读重复累计、dialog 预览被旧消息抢占)。
|
||||
func TestAcknowledgeCurrentStateAdvancesConfirmedWatermark(t *testing.T) {
|
||||
// TestCurrentStateCommitsAuditedBaselineOnlyAfterDelivery verifies that
|
||||
// computing a getState result is side-effect free and that its physically
|
||||
// delivered baseline advances confirmed+observed atomically.
|
||||
func TestCurrentStateCommitsAuditedBaselineOnlyAfterDelivery(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var authKeyID [8]byte
|
||||
authKeyID[0] = 11
|
||||
|
|
@ -509,8 +508,8 @@ func TestAcknowledgeCurrentStateAdvancesConfirmedWatermark(t *testing.T) {
|
|||
t.Fatalf("append: %v", err)
|
||||
}
|
||||
// 设备确认水位停在 pts=1 后账号又推进两格。
|
||||
if _, err := svc.GetDifference(ctx, authKeyID, userID, domain.UpdateState{Pts: 1}); err != nil {
|
||||
t.Fatalf("GetDifference: %v", err)
|
||||
if err := states.Save(ctx, authKeyID, userID, domain.UpdateState{Pts: 1, Date: 1700000001}); err != nil {
|
||||
t.Fatalf("seed confirmed state: %v", err)
|
||||
}
|
||||
for pts := 2; pts <= 3; pts++ {
|
||||
if err := events.Append(ctx, userID, domain.UpdateEvent{
|
||||
|
|
@ -521,23 +520,33 @@ func TestAcknowledgeCurrentStateAdvancesConfirmedWatermark(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
st, err := svc.AcknowledgeCurrentState(ctx, authKeyID, userID)
|
||||
st, err := svc.CurrentState(ctx, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("AcknowledgeCurrentState: %v", err)
|
||||
t.Fatalf("CurrentState: %v", err)
|
||||
}
|
||||
if st.Pts != 3 {
|
||||
t.Fatalf("acknowledged state pts = %d, want account current 3", st.Pts)
|
||||
t.Fatalf("current state pts = %d, want account current 3", st.Pts)
|
||||
}
|
||||
confirmed, err := svc.GetState(ctx, authKeyID, userID)
|
||||
confirmed, _, err := svc.ConfirmedState(ctx, authKeyID, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetState after acknowledge: %v", err)
|
||||
t.Fatalf("ConfirmedState before delivery: %v", err)
|
||||
}
|
||||
if confirmed.Pts != 3 {
|
||||
t.Fatalf("confirmed watermark = %d, want advanced to 3", confirmed.Pts)
|
||||
if confirmed.Pts != 1 {
|
||||
t.Fatalf("confirmed before delivery = %d, want 1", confirmed.Pts)
|
||||
}
|
||||
if _, ok := states.ObservedClientState(authKeyID, userID); ok {
|
||||
t.Fatal("computed getState unexpectedly advanced observed")
|
||||
}
|
||||
if err := svc.CommitDeliveredState(ctx, authKeyID, userID, st, domain.UpdateStateCommitDeliveredAndObservedBaseline); err != nil {
|
||||
t.Fatalf("CommitDeliveredState: %v", err)
|
||||
}
|
||||
confirmed, _, err = svc.ConfirmedState(ctx, authKeyID, userID)
|
||||
if err != nil || confirmed.Pts != 3 {
|
||||
t.Fatalf("confirmed after delivery = %+v err=%v, want pts=3", confirmed, err)
|
||||
}
|
||||
observed, ok := states.ObservedClientState(authKeyID, userID)
|
||||
if !ok || observed.Pts != 3 {
|
||||
t.Fatalf("getState observed watermark = %+v/%v, want pts=3", observed, ok)
|
||||
t.Fatalf("observed after delivered baseline = %+v/%v, want pts=3", observed, ok)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -559,7 +568,11 @@ func TestGetDifferenceRetainsOnlyClientObservedInputCursor(t *testing.T) {
|
|||
|
||||
// 服务端把 pts=1..2 放进 response,并不证明客户端收到了 response;observed 只能
|
||||
// 保持在本次 request 实际携带的 pts=0。
|
||||
diff, err := svc.GetDifference(ctx, authKeyID, userID, domain.UpdateState{Pts: 0, Date: 1700000100})
|
||||
from, err := svc.ObserveDifferenceRequest(ctx, authKeyID, userID, domain.UpdateState{Pts: 0, Date: 1700000100})
|
||||
if err != nil {
|
||||
t.Fatalf("observe first request: %v", err)
|
||||
}
|
||||
diff, err := svc.GetDifference(ctx, authKeyID, userID, from)
|
||||
if err != nil {
|
||||
t.Fatalf("first difference: %v", err)
|
||||
}
|
||||
|
|
@ -570,10 +583,23 @@ func TestGetDifferenceRetainsOnlyClientObservedInputCursor(t *testing.T) {
|
|||
if !ok || observed.Pts != 0 {
|
||||
t.Fatalf("observed after merely sending response = %+v/%v, want pts=0", observed, ok)
|
||||
}
|
||||
if _, found, err := svc.ConfirmedState(ctx, authKeyID, userID); err != nil || found {
|
||||
t.Fatalf("computed response advanced confirmed: found=%v err=%v", found, err)
|
||||
}
|
||||
if err := svc.CommitDeliveredState(ctx, authKeyID, userID, diff.State, domain.UpdateStateCommitDeliveredOnly); err != nil {
|
||||
t.Fatalf("commit delivered difference: %v", err)
|
||||
}
|
||||
if confirmed, found, err := svc.ConfirmedState(ctx, authKeyID, userID); err != nil || !found || confirmed.Pts != 2 {
|
||||
t.Fatalf("confirmed after delivery = %+v/%v err=%v, want pts=2", confirmed, found, err)
|
||||
}
|
||||
observed, _ = states.ObservedClientState(authKeyID, userID)
|
||||
if observed.Pts != 0 {
|
||||
t.Fatalf("delivered difference advanced observed to %d, want 0", observed.Pts)
|
||||
}
|
||||
|
||||
// 客户端下一次明确带回 pts=2 后,才允许 retention 把共同安全水位推进到 2。
|
||||
if _, err := svc.GetDifference(ctx, authKeyID, userID, domain.UpdateState{Pts: 2, Date: 1700000102}); err != nil {
|
||||
t.Fatalf("confirming difference: %v", err)
|
||||
if _, err := svc.ObserveDifferenceRequest(ctx, authKeyID, userID, domain.UpdateState{Pts: 2, Date: 1700000102}); err != nil {
|
||||
t.Fatalf("observing next request: %v", err)
|
||||
}
|
||||
observed, ok = states.ObservedClientState(authKeyID, userID)
|
||||
if !ok || observed.Pts != 2 {
|
||||
|
|
@ -624,6 +650,15 @@ func TestGetDifferenceBelowRetainedFloorUsesEmptySliceCheckpoint(t *testing.T) {
|
|||
if !checkpoint.Partial || len(checkpoint.Events) != 0 || checkpoint.State.Pts != 2 || checkpoint.State.Date != 1700000202 {
|
||||
t.Fatalf("checkpoint difference = %+v, want empty differenceSlice at pts/date 2/1700000202", checkpoint)
|
||||
}
|
||||
if _, found, err := svc.ConfirmedState(ctx, authKeyID, userID); err != nil || found {
|
||||
t.Fatalf("computed checkpoint advanced confirmed: found=%v err=%v", found, err)
|
||||
}
|
||||
if err := svc.CommitDeliveredState(ctx, authKeyID, userID, checkpoint.State, domain.UpdateStateCommitDeliveredOnly); err != nil {
|
||||
t.Fatalf("commit delivered checkpoint: %v", err)
|
||||
}
|
||||
if confirmed, found, err := svc.ConfirmedState(ctx, authKeyID, userID); err != nil || !found || confirmed.Pts != 2 {
|
||||
t.Fatalf("confirmed checkpoint = %+v/%v err=%v, want pts=2", confirmed, found, err)
|
||||
}
|
||||
|
||||
tail, err := svc.GetDifference(ctx, authKeyID, userID, checkpoint.State)
|
||||
if err != nil {
|
||||
|
|
@ -722,6 +757,10 @@ func (s *captureStateStore) Save(_ context.Context, authKeyID [8]byte, userID in
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *captureStateStore) CommitDeliveredState(ctx context.Context, authKeyID [8]byte, userID int64, state domain.UpdateState, _ domain.UpdateStateCommitMode) error {
|
||||
return s.Save(ctx, authKeyID, userID, state)
|
||||
}
|
||||
|
||||
func (s *captureStateStore) ObserveClientState(_ context.Context, _ [8]byte, _ int64, _ domain.UpdateState) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue