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:
parent
869943723e
commit
ab758062df
12 changed files with 470 additions and 13 deletions
95
commonspace/sync/headupdate.go
Normal file
95
commonspace/sync/headupdate.go
Normal 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
|
||||
}
|
11
commonspace/sync/headupdatehandler.go
Normal file
11
commonspace/sync/headupdatehandler.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"storj.io/drpc"
|
||||
)
|
||||
|
||||
type HeadUpdateHandler interface {
|
||||
HandleHeadUpdate(ctx context.Context, headUpdate drpc.Message) (Request, error)
|
||||
}
|
12
commonspace/sync/headupdatesender.go
Normal file
12
commonspace/sync/headupdatesender.go
Normal 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
|
||||
}
|
125
commonspace/sync/requestmanager.go
Normal file
125
commonspace/sync/requestmanager.go
Normal 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}, "-")
|
||||
}
|
5
commonspace/sync/requestpool.go
Normal file
5
commonspace/sync/requestpool.go
Normal 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
84
commonspace/sync/sync.go
Normal 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{}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue