mirror of
https://github.com/anyproto/any-sync.git
synced 2025-06-07 21:47:02 +09:00
339 lines
9.8 KiB
Go
339 lines
9.8 KiB
Go
//go:generate mockgen -destination mock_synctree/mock_synctree.go github.com/anyproto/any-sync/commonspace/object/tree/synctree SyncTree,HeadNotifiable,SyncClient,RequestFactory
|
|
package synctree
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"slices"
|
|
"time"
|
|
|
|
anystore "github.com/anyproto/any-store"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/anyproto/any-sync/app/logger"
|
|
"github.com/anyproto/any-sync/commonspace/headsync/headstorage"
|
|
"github.com/anyproto/any-sync/commonspace/object/acl/list"
|
|
"github.com/anyproto/any-sync/commonspace/object/tree/objecttree"
|
|
"github.com/anyproto/any-sync/commonspace/object/tree/synctree/updatelistener"
|
|
"github.com/anyproto/any-sync/commonspace/object/tree/treestorage"
|
|
"github.com/anyproto/any-sync/commonspace/spacestorage"
|
|
"github.com/anyproto/any-sync/commonspace/sync/syncdeps"
|
|
"github.com/anyproto/any-sync/commonspace/syncstatus"
|
|
"github.com/anyproto/any-sync/net/peer"
|
|
"github.com/anyproto/any-sync/nodeconf"
|
|
"github.com/anyproto/any-sync/util/slice"
|
|
)
|
|
|
|
var (
|
|
ErrSyncTreeClosed = errors.New("sync tree is closed")
|
|
ErrSyncTreeDeleted = errors.New("sync tree is deleted")
|
|
)
|
|
|
|
type HeadNotifiable interface {
|
|
UpdateHeads(id string, heads []string)
|
|
}
|
|
|
|
type ListenerSetter interface {
|
|
SetListener(listener updatelistener.UpdateListener)
|
|
}
|
|
|
|
type peerSendableObjectTree interface {
|
|
objecttree.ObjectTree
|
|
syncdeps.ObjectSyncHandler
|
|
AddRawChangesFromPeer(ctx context.Context, peerId string, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error)
|
|
}
|
|
|
|
type SyncTree interface {
|
|
peerSendableObjectTree
|
|
ListenerSetter
|
|
SyncWithPeer(ctx context.Context, p peer.Peer) (err error)
|
|
}
|
|
|
|
// SyncTree sends head updates to sync service and also sends new changes to update listener
|
|
type syncTree struct {
|
|
syncdeps.ObjectSyncHandler
|
|
objecttree.ObjectTree
|
|
syncClient SyncClient
|
|
syncStatus syncstatus.StatusUpdater
|
|
listener updatelistener.UpdateListener
|
|
onClose func(id string)
|
|
isClosed bool
|
|
isDeleted bool
|
|
}
|
|
|
|
var log = logger.NewNamed("common.commonspace.synctree")
|
|
|
|
type ResponsiblePeersGetter interface {
|
|
GetResponsiblePeers(ctx context.Context) (peers []peer.Peer, err error)
|
|
}
|
|
|
|
type BuildDeps struct {
|
|
SpaceId string
|
|
SyncClient SyncClient
|
|
Configuration nodeconf.NodeConf
|
|
HeadNotifiable HeadNotifiable
|
|
Listener updatelistener.UpdateListener
|
|
AclList list.AclList
|
|
SpaceStorage spacestorage.SpaceStorage
|
|
TreeStorage objecttree.Storage
|
|
OnClose func(id string)
|
|
SyncStatus syncstatus.StatusUpdater
|
|
PeerGetter ResponsiblePeersGetter
|
|
BuildObjectTree objecttree.BuildObjectTreeFunc
|
|
ValidateObjectTree objecttree.ValidatorFunc
|
|
}
|
|
|
|
var newTreeGetter = func(deps BuildDeps, treeId string) treeGetter {
|
|
return treeRemoteGetter{deps: deps, treeId: treeId}
|
|
}
|
|
|
|
func BuildSyncTreeOrGetRemote(ctx context.Context, id string, deps BuildDeps) (t SyncTree, err error) {
|
|
var (
|
|
remoteGetter = newTreeGetter(deps, id)
|
|
peerId string
|
|
)
|
|
deps.TreeStorage, peerId, err = remoteGetter.getTree(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return buildSyncTree(ctx, peerId, deps)
|
|
}
|
|
|
|
func PutSyncTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, deps BuildDeps) (t SyncTree, err error) {
|
|
err = checkTreeDeleted(ctx, payload.RootRawChange.Id, deps.SpaceStorage)
|
|
if err != nil {
|
|
return
|
|
}
|
|
deps.TreeStorage, err = deps.SpaceStorage.CreateTreeStorage(ctx, payload)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return buildSyncTree(ctx, peer.CtxResponsiblePeers, deps)
|
|
}
|
|
|
|
func buildSyncTree(ctx context.Context, peerId string, deps BuildDeps) (t SyncTree, err error) {
|
|
objTree, err := deps.BuildObjectTree(deps.TreeStorage, deps.AclList)
|
|
if err != nil {
|
|
return
|
|
}
|
|
syncClient := deps.SyncClient
|
|
syncTree := &syncTree{
|
|
ObjectTree: objTree,
|
|
syncClient: syncClient,
|
|
onClose: deps.OnClose,
|
|
listener: deps.Listener,
|
|
syncStatus: deps.SyncStatus,
|
|
}
|
|
syncHandler := NewSyncHandler(syncTree, syncClient, deps.SpaceId)
|
|
syncTree.ObjectSyncHandler = syncHandler
|
|
t = syncTree
|
|
syncTree.Lock()
|
|
syncTree.afterBuild()
|
|
syncTree.Unlock()
|
|
|
|
if peerId != "" && !objecttree.IsEmptyDerivedTree(objTree) {
|
|
headUpdate, err := syncTree.syncClient.CreateHeadUpdate(objTree, "", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Debug("broadcast after build", zap.String("treeId", objTree.Id()), zap.Strings("heads", objTree.Heads()))
|
|
// send to everybody, because everybody should know that the node or client got new tree
|
|
broadcastErr := syncTree.syncClient.Broadcast(ctx, headUpdate)
|
|
if broadcastErr != nil {
|
|
log.Warn("failed to broadcast head update", zap.Error(broadcastErr))
|
|
}
|
|
if peerId != peer.CtxResponsiblePeers {
|
|
deps.SyncStatus.ObjectReceive(peerId, syncTree.Id(), syncTree.Heads())
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) SetListener(listener updatelistener.UpdateListener) {
|
|
// this should be called under lock
|
|
s.listener = listener
|
|
}
|
|
|
|
func (s *syncTree) IterateFrom(id string, convert objecttree.ChangeConvertFunc, iterate objecttree.ChangeIterateFunc) (err error) {
|
|
if err = s.checkAlive(); err != nil {
|
|
return
|
|
}
|
|
return s.ObjectTree.IterateFrom(id, convert, iterate)
|
|
}
|
|
|
|
func (s *syncTree) IterateRoot(convert objecttree.ChangeConvertFunc, iterate objecttree.ChangeIterateFunc) (err error) {
|
|
if err = s.checkAlive(); err != nil {
|
|
return
|
|
}
|
|
return s.ObjectTree.IterateRoot(convert, iterate)
|
|
}
|
|
|
|
func (s *syncTree) AddContent(ctx context.Context, content objecttree.SignableChangeContent) (res objecttree.AddResult, err error) {
|
|
return s.AddContentWithValidator(ctx, content, nil)
|
|
}
|
|
|
|
func (s *syncTree) AddContentWithValidator(ctx context.Context, content objecttree.SignableChangeContent, validate func(change objecttree.StorageChange) error) (res objecttree.AddResult, err error) {
|
|
if err = s.checkAlive(); err != nil {
|
|
return
|
|
}
|
|
res, err = s.ObjectTree.AddContentWithValidator(ctx, content, validate)
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.syncStatus.HeadsChange(s.Id(), res.Heads)
|
|
headUpdate, err := s.syncClient.CreateHeadUpdate(s, "", res.RawChanges())
|
|
if err != nil {
|
|
return
|
|
}
|
|
broadcastErr := s.syncClient.Broadcast(ctx, headUpdate)
|
|
if broadcastErr != nil {
|
|
log.Warn("failed to broadcast head update", zap.Error(broadcastErr))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) AddRawChangesFromPeer(ctx context.Context, peerId string, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error) {
|
|
if s.hasHeads(s, changesPayload.NewHeads) {
|
|
s.syncStatus.HeadsApply(peerId, s.Id(), s.Heads(), true)
|
|
log.Debug("heads already applied", zap.String("treeId", s.Id()), zap.Strings("heads", changesPayload.NewHeads))
|
|
return objecttree.AddResult{
|
|
OldHeads: changesPayload.NewHeads,
|
|
Heads: changesPayload.NewHeads,
|
|
Mode: objecttree.Nothing,
|
|
}, nil
|
|
}
|
|
prevHeads := s.Heads()
|
|
res, err = s.AddRawChanges(ctx, changesPayload)
|
|
if err != nil || res.Mode == objecttree.Nothing {
|
|
return
|
|
}
|
|
headUpdate, err := s.syncClient.CreateHeadUpdate(s, peerId, res.RawChanges())
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
broadcastErr := s.syncClient.Broadcast(ctx, headUpdate)
|
|
if broadcastErr != nil {
|
|
log.Warn("failed to broadcast head update", zap.Error(broadcastErr))
|
|
}
|
|
curHeads := s.Heads()
|
|
allAdded := true
|
|
for _, head := range changesPayload.NewHeads {
|
|
if !slices.Contains(curHeads, head) {
|
|
allAdded = false
|
|
break
|
|
}
|
|
}
|
|
if !slice.UnsortedEquals(prevHeads, curHeads) {
|
|
s.syncStatus.HeadsApply(peerId, s.Id(), curHeads, allAdded)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) hasHeads(ot objecttree.ObjectTree, heads []string) bool {
|
|
return slice.UnsortedEquals(ot.Heads(), heads) || ot.HasChanges(heads...)
|
|
}
|
|
|
|
func (s *syncTree) AddRawChanges(ctx context.Context, changesPayload objecttree.RawChangesPayload) (res objecttree.AddResult, err error) {
|
|
if err = s.checkAlive(); err != nil {
|
|
return
|
|
}
|
|
return s.ObjectTree.AddRawChangesWithUpdater(ctx, changesPayload, func(tree objecttree.ObjectTree, md objecttree.Mode) error {
|
|
if s.listener != nil {
|
|
switch md {
|
|
case objecttree.Nothing:
|
|
return nil
|
|
case objecttree.Append:
|
|
return s.listener.Update(s)
|
|
case objecttree.Rebuild:
|
|
return s.listener.Rebuild(s)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (s *syncTree) Delete() (err error) {
|
|
log.Debug("deleting sync tree", zap.String("id", s.Id()))
|
|
defer func() {
|
|
log.Debug("deleted sync tree", zap.Error(err), zap.String("id", s.Id()))
|
|
}()
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
if err = s.checkAlive(); err != nil {
|
|
return
|
|
}
|
|
err = s.ObjectTree.Delete()
|
|
if err != nil {
|
|
return
|
|
}
|
|
s.isDeleted = true
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) TryClose(objectTTL time.Duration) (bool, error) {
|
|
if !s.TryLock() {
|
|
return false, nil
|
|
}
|
|
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
|
return true, s.close()
|
|
}
|
|
|
|
func (s *syncTree) Close() (err error) {
|
|
log.Debug("closing sync tree", zap.String("id", s.Id()))
|
|
s.Lock()
|
|
return s.close()
|
|
}
|
|
|
|
func (s *syncTree) close() (err error) {
|
|
defer s.Unlock()
|
|
defer func() {
|
|
log.Debug("closed sync tree", zap.Error(err), zap.String("id", s.Id()))
|
|
}()
|
|
if s.isClosed {
|
|
err = ErrSyncTreeClosed
|
|
return
|
|
}
|
|
s.onClose(s.Id())
|
|
s.isClosed = true
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) checkAlive() (err error) {
|
|
if s.isClosed {
|
|
err = ErrSyncTreeClosed
|
|
}
|
|
if s.isDeleted {
|
|
err = ErrSyncTreeDeleted
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *syncTree) SyncWithPeer(ctx context.Context, p peer.Peer) (err error) {
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
req, err := s.syncClient.CreateFullSyncRequest(p.Id(), s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return s.syncClient.QueueRequest(ctx, req)
|
|
}
|
|
|
|
func (s *syncTree) afterBuild() {
|
|
if s.listener != nil {
|
|
s.listener.Rebuild(s)
|
|
}
|
|
}
|
|
|
|
func checkTreeDeleted(ctx context.Context, treeId string, spaceStorage spacestorage.SpaceStorage) error {
|
|
status, err := spaceStorage.HeadStorage().GetEntry(ctx, treeId)
|
|
if err != nil {
|
|
if errors.Is(err, anystore.ErrDocNotFound) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if status.DeletedStatus != headstorage.DeletedStatusNotDeleted {
|
|
return spacestorage.ErrTreeStorageAlreadyDeleted
|
|
}
|
|
return nil
|
|
}
|