1
0
Fork 0
mirror of https://github.com/anyproto/any-sync.git synced 2025-06-09 09:35:03 +09:00

WIP add some sync stuff

This commit is contained in:
mcrakhman 2024-05-23 09:30:15 +02:00
parent 869943723e
commit ab758062df
No known key found for this signature in database
GPG key ID: DED12CFEF5B8396B
12 changed files with 470 additions and 13 deletions

View file

@ -0,0 +1,95 @@
package sync
import (
"fmt"
"slices"
"github.com/gogo/protobuf/proto"
"github.com/anyproto/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anyproto/any-sync/commonspace/spacesyncproto"
)
type HeadUpdate struct {
peerId string
objectId string
spaceId string
heads []string
changes []*treechangeproto.RawTreeChangeWithId
snapshotPath []string
root *treechangeproto.RawTreeChangeWithId
opts BroadcastOptions
}
func (h *HeadUpdate) SetPeerId(peerId string) {
h.peerId = peerId
}
func (h *HeadUpdate) SetProtoMessage(message proto.Message) error {
var (
msg *spacesyncproto.ObjectSyncMessage
ok bool
)
if msg, ok = message.(*spacesyncproto.ObjectSyncMessage); !ok {
return fmt.Errorf("unexpected message type: %T", message)
}
treeMsg := &treechangeproto.TreeSyncMessage{}
err := proto.Unmarshal(msg.Payload, treeMsg)
if err != nil {
return err
}
h.root = treeMsg.RootChange
headMsg := treeMsg.GetContent().GetHeadUpdate()
if headMsg == nil {
return fmt.Errorf("unexpected message type: %T", treeMsg.GetContent())
}
h.heads = headMsg.Heads
h.changes = headMsg.Changes
h.snapshotPath = headMsg.SnapshotPath
h.spaceId = msg.SpaceId
h.objectId = msg.ObjectId
return nil
}
func (h *HeadUpdate) ProtoMessage() (proto.Message, error) {
if h.heads != nil {
return h.SyncMessage()
}
return &spacesyncproto.ObjectSyncMessage{}, nil
}
func (h *HeadUpdate) PeerId() string {
return h.peerId
}
func (h *HeadUpdate) ObjectId() string {
return h.objectId
}
func (h *HeadUpdate) ShallowCopy() *HeadUpdate {
return &HeadUpdate{
peerId: h.peerId,
objectId: h.objectId,
heads: h.heads,
changes: h.changes,
snapshotPath: h.snapshotPath,
root: h.root,
}
}
func (h *HeadUpdate) SyncMessage() (*spacesyncproto.ObjectSyncMessage, error) {
changes := h.changes
if slices.Contains(h.opts.EmptyPeers, h.peerId) {
changes = nil
}
treeMsg := treechangeproto.WrapHeadUpdate(&treechangeproto.TreeHeadUpdate{
Heads: h.heads,
SnapshotPath: h.snapshotPath,
Changes: changes,
}, h.root)
return spacesyncproto.MarshallSyncMessage(treeMsg, h.spaceId, h.objectId)
}
func (h *HeadUpdate) RemoveChanges() {
h.changes = nil
}

View file

@ -0,0 +1,11 @@
package sync
import (
"context"
"storj.io/drpc"
)
type HeadUpdateHandler interface {
HandleHeadUpdate(ctx context.Context, headUpdate drpc.Message) (Request, error)
}

View file

@ -0,0 +1,12 @@
package sync
import "context"
type BroadcastOptions struct {
EmptyPeers []string
}
type HeadUpdateSender interface {
SendHeadUpdate(ctx context.Context, peerId string, headUpdate *HeadUpdate) error
BroadcastHeadUpdate(ctx context.Context, opts BroadcastOptions, headUpdate *HeadUpdate) error
}

View file

@ -0,0 +1,125 @@
package sync
import (
"context"
"strings"
"sync"
"github.com/gogo/protobuf/proto"
"storj.io/drpc"
"github.com/anyproto/any-sync/net/streampool"
)
type Request interface {
//heads []string
//changes []*treechangeproto.RawTreeChangeWithId
//root *treechangeproto.RawTreeChangeWithId
}
type Response interface {
//heads []string
//changes []*treechangeproto.RawTreeChangeWithId
//root *treechangeproto.RawTreeChangeWithId
}
type RequestAccepter func(ctx context.Context, resp Response) error
type RequestManager interface {
QueueRequest(peerId, objectId string, rq Request) error
HandleRequest(peerId, objectId string, rq Request, accept RequestAccepter) error
HandleStreamRequest(peerId, objectId string, rq Request, stream drpc.Stream) error
}
type RequestHandler interface {
HandleRequest(peerId, objectId string, rq Request, accept RequestAccepter) error
HandleStreamRequest(peerId, objectId string, rq Request, send func(resp proto.Message) error) error
}
type StreamResponse struct {
Stream drpc.Stream
Connection drpc.Conn
}
type RequestSender interface {
SendRequest(peerId, objectId string, rq Request) (resp Response, err error)
SendStreamRequest(peerId, objectId string, rq Request, receive func(stream drpc.Stream) error) (err error)
}
type ResponseHandler interface {
NewResponse() Response
HandleResponse(peerId, objectId string, resp Response) error
}
type requestManager struct {
requestPool RequestPool
requestHandler RequestHandler
responseHandler ResponseHandler
requestSender RequestSender
currentRequests map[string]struct{}
mx sync.Mutex
ctx context.Context
cancel context.CancelFunc
wait chan struct{}
}
func (r *requestManager) QueueRequest(peerId, objectId string, rq Request) error {
return r.requestPool.QueueRequestAction(peerId, objectId, func() {
r.requestSender.SendStreamRequest(peerId, objectId, rq, func(stream drpc.Stream) error {
for {
resp := r.responseHandler.NewResponse()
err := stream.MsgRecv(resp, streampool.EncodingProto)
if err != nil {
return err
}
err = r.responseHandler.HandleResponse(peerId, objectId, resp)
if err != nil {
return err
}
}
return nil
})
})
}
func (r *requestManager) HandleRequest(peerId, objectId string, rq Request, accept RequestAccepter) error {
id := fullId(peerId, objectId)
r.mx.Lock()
if _, ok := r.currentRequests[id]; ok {
r.mx.Unlock()
return nil
}
r.currentRequests[id] = struct{}{}
r.mx.Unlock()
defer func() {
r.mx.Lock()
delete(r.currentRequests, id)
r.mx.Unlock()
}()
return r.requestHandler.HandleRequest(peerId, objectId, rq, accept)
}
func (r *requestManager) HandleStreamRequest(peerId, objectId string, rq Request, stream drpc.Stream) error {
id := fullId(peerId, objectId)
r.mx.Lock()
if _, ok := r.currentRequests[id]; ok {
r.mx.Unlock()
return nil
}
r.currentRequests[id] = struct{}{}
r.mx.Unlock()
defer func() {
r.mx.Lock()
delete(r.currentRequests, id)
r.mx.Unlock()
}()
err := r.requestHandler.HandleStreamRequest(peerId, objectId, rq, func(resp proto.Message) error {
return stream.MsgSend(resp, streampool.EncodingProto)
})
return err
}
func fullId(peerId, objectId string) string {
return strings.Join([]string{peerId, objectId}, "-")
}

View file

@ -0,0 +1,5 @@
package sync
type RequestPool interface {
QueueRequestAction(peerId, objectId string, action func()) (err error)
}

84
commonspace/sync/sync.go Normal file
View file

@ -0,0 +1,84 @@
package sync
import (
"context"
"github.com/cheggaaa/mb/v3"
"go.uber.org/zap"
"storj.io/drpc"
"github.com/anyproto/any-sync/app/logger"
"github.com/anyproto/any-sync/util/multiqueue"
)
const CName = "common.commonspace.sync"
var log = logger.NewNamed("sync")
type SyncService interface {
GetQueueProvider() multiqueue.QueueProvider[drpc.Message]
}
type MergeFilterFunc func(ctx context.Context, msg drpc.Message, q *mb.MB[drpc.Message]) error
type syncService struct {
// sendQueue is a multiqueue: peerId -> queue
// this queue exists for sending head updates
sendQueueProvider multiqueue.QueueProvider[drpc.Message]
// receiveQueue is a multiqueue: objectId -> queue
// this queue exists for receiving head updates
receiveQueue multiqueue.MultiQueue[drpc.Message]
// manager is a Request manager which works with both incoming and outgoing requests
manager RequestManager
// handler checks if head update is relevant and then queues Request intent if necessary
handler HeadUpdateHandler
// sender sends head updates to peers
sender HeadUpdateSender
mergeFilter MergeFilterFunc
ctx context.Context
cancel context.CancelFunc
}
func NewSyncService() SyncService {
s := &syncService{}
s.ctx, s.cancel = context.WithCancel(context.Background())
s.sendQueueProvider = multiqueue.NewQueueProvider[drpc.Message](100, s.handleOutgoingMessage)
s.receiveQueue = multiqueue.New[drpc.Message](s.handleIncomingMessage, 100)
return s
}
func (s *syncService) handleOutgoingMessage(id string, msg drpc.Message, q *mb.MB[drpc.Message]) error {
//headUpdate := msg.(*HeadUpdate)
//cp := headUpdate.ShallowCopy()
//cp.SetPeerId(id)
//// TODO: add some merging/filtering logic if needed
//// for example we can filter empty messages for the same peer
//// or we can merge the messages together
return s.mergeFilter(s.ctx, msg, q)
}
func (s *syncService) handleIncomingMessage(msg drpc.Message) {
req, err := s.handler.HandleHeadUpdate(s.ctx, msg)
if err != nil {
log.Error("failed to handle head update", zap.Error(err))
}
if req == nil {
return
}
err = s.manager.QueueRequest("", "", req)
if err != nil {
log.Error("failed to queue request", zap.Error(err))
}
}
func (s *syncService) GetQueueProvider() multiqueue.QueueProvider[drpc.Message] {
return s.sendQueueProvider
}
func (s *syncService) HandleMessage(ctx context.Context, peerId string, msg drpc.Message) error {
return s.receiveQueue.Add(ctx, peerId, msg.(*HeadUpdate))
}
func (s *syncService) NewReadMessage() drpc.Message {
return &HeadUpdate{}
}