Skip to content

Commit

Permalink
Apply GCPair to TreeNode, TextNode (#866)
Browse files Browse the repository at this point in the history
This commit is a follow-up task of #864, applying GCPair for TextNode
and TreeNode. Additionally, when generating a document from the root,
this commit registers GCPair in the cache.
  • Loading branch information
hackerwins authored May 17, 2024
1 parent bd46419 commit 28cbd37
Show file tree
Hide file tree
Showing 21 changed files with 633 additions and 559 deletions.
3 changes: 2 additions & 1 deletion api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -658,10 +658,11 @@ func fromTreeNode(pbNode *api.TreeNode) (*crdt.TreeNode, error) {
}
}

node.RemovedAt, err = fromTimeTicket(pbNode.RemovedAt)
removedAt, err := fromTimeTicket(pbNode.RemovedAt)
if err != nil {
return nil, err
}
node.SetRemovedAt(removedAt)

return node, nil
}
Expand Down
4 changes: 2 additions & 2 deletions api/converter/to_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,10 @@ func toTreeNode(treeNode *crdt.TreeNode, depth int) *api.TreeNode {
}

pbNode := &api.TreeNode{
Id: toTreeNodeID(treeNode.ID),
Id: toTreeNodeID(treeNode.ID()),
Type: treeNode.Type(),
Value: treeNode.Value,
RemovedAt: ToTimeTicket(treeNode.RemovedAt),
RemovedAt: ToTimeTicket(treeNode.RemovedAt()),
Depth: int32(depth),
Attributes: attrs,
}
Expand Down
5 changes: 0 additions & 5 deletions pkg/document/change/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,6 @@ func (c *Context) RegisterRemovedElementPair(parent crdt.Container, deleted crdt
c.root.RegisterRemovedElementPair(parent, deleted)
}

// RegisterElementHasRemovedNodes register the given text element with garbage to hash table.
func (c *Context) RegisterElementHasRemovedNodes(element crdt.GCElement) {
c.root.RegisterElementHasRemovedNodes(element)
}

// RegisterGCPair registers the given GC pair to the root.
func (c *Context) RegisterGCPair(pair crdt.GCPair) {
c.root.RegisterGCPair(pair)
Expand Down
7 changes: 0 additions & 7 deletions pkg/document/crdt/element.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ type Container interface {
DeleteByCreatedAt(createdAt *time.Ticket, deletedAt *time.Ticket) (Element, error)
}

// GCElement represents Element which has GC.
type GCElement interface {
Element
removedNodesLen() int
purgeRemovedNodesBefore(ticket *time.Ticket) (int, error)
}

// Element represents JSON element.
type Element interface {
// Marshal returns the JSON encoding of this element.
Expand Down
2 changes: 1 addition & 1 deletion pkg/document/crdt/gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ type GCParent interface {

// GCChild is an interface for the child of the garbage collection target.
type GCChild interface {
ID() string
IDString() string
RemovedAt() *time.Ticket
}
66 changes: 28 additions & 38 deletions pkg/document/crdt/rga_tree_split.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ func (s *RGATreeSplitNode[V]) ID() *RGATreeSplitNodeID {
return s.id
}

// IDString returns the string representation of the ID.
func (s *RGATreeSplitNode[V]) IDString() string {
return s.id.key()
}

// InsPrevID returns previous node ID at the time of this node insertion.
func (s *RGATreeSplitNode[V]) InsPrevID() *RGATreeSplitNodeID {
if s.insPrev == nil {
Expand Down Expand Up @@ -254,11 +259,14 @@ func (s *RGATreeSplitNode[V]) toTestString() string {
// Remove removes this node if it created before the time of deletion are
// deleted. It only marks the deleted time (tombstone).
func (s *RGATreeSplitNode[V]) Remove(removedAt *time.Ticket, maxCreatedAt *time.Ticket) bool {
justRemoved := s.removedAt == nil

if !s.createdAt().After(maxCreatedAt) &&
(s.removedAt == nil || removedAt.After(s.removedAt)) {
s.removedAt = removedAt
return true
return justRemoved
}

return false
}

Expand All @@ -281,10 +289,6 @@ type RGATreeSplit[V RGATreeSplitValue] struct {
initialHead *RGATreeSplitNode[V]
treeByIndex *splay.Tree[*RGATreeSplitNode[V]]
treeByID *llrb.Tree[*RGATreeSplitNodeID, *RGATreeSplitNode[V]]

// removedNodeMap is a map to store removed nodes. It is used to
// delete the node physically when the garbage collection is executed.
removedNodeMap map[string]*RGATreeSplitNode[V]
}

// NewRGATreeSplit creates a new instance of RGATreeSplit.
Expand All @@ -294,10 +298,9 @@ func NewRGATreeSplit[V RGATreeSplitValue](initialHead *RGATreeSplitNode[V]) *RGA
treeByID.Put(initialHead.ID(), initialHead)

return &RGATreeSplit[V]{
initialHead: initialHead,
treeByIndex: treeByIndex,
treeByID: treeByID,
removedNodeMap: make(map[string]*RGATreeSplitNode[V]),
initialHead: initialHead,
treeByIndex: treeByIndex,
treeByID: treeByID,
}
}

Expand Down Expand Up @@ -448,20 +451,20 @@ func (s *RGATreeSplit[V]) edit(
maxCreatedAtMapByActor map[string]*time.Ticket,
content V,
editedAt *time.Ticket,
) (*RGATreeSplitNodePos, map[string]*time.Ticket, error) {
) (*RGATreeSplitNodePos, map[string]*time.Ticket, []GCPair, error) {
// 01. Split nodes with from and to
toLeft, toRight, err := s.findNodeWithSplit(to, editedAt)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
fromLeft, fromRight, err := s.findNodeWithSplit(from, editedAt)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

// 02. delete between from and to
nodesToDelete := s.findBetween(fromRight, toRight)
maxCreatedAtMap, removedNodeMapByNodeKey := s.deleteNodes(nodesToDelete, maxCreatedAtMapByActor, editedAt)
maxCreatedAtMap, removedNodes := s.deleteNodes(nodesToDelete, maxCreatedAtMapByActor, editedAt)

var caretID *RGATreeSplitNodeID
if toRight == nil {
Expand All @@ -478,11 +481,15 @@ func (s *RGATreeSplit[V]) edit(
}

// 04. add removed node
for key, removedNode := range removedNodeMapByNodeKey {
s.removedNodeMap[key] = removedNode
var pairs []GCPair
for _, removedNode := range removedNodes {
pairs = append(pairs, GCPair{
Parent: s,
Child: removedNode,
})
}

return caretPos, maxCreatedAtMap, nil
return caretPos, maxCreatedAtMap, pairs, nil
}

func (s *RGATreeSplit[V]) findBetween(from, to *RGATreeSplitNode[V]) []*RGATreeSplitNode[V] {
Expand Down Expand Up @@ -617,29 +624,10 @@ func (s *RGATreeSplit[V]) ToTestString() string {
return builder.String()
}

// removedNodesLen returns length of removed nodes
func (s *RGATreeSplit[V]) removedNodesLen() int {
return len(s.removedNodeMap)
}

// purgeRemovedNodesBefore physically purges nodes that have been removed.
func (s *RGATreeSplit[V]) purgeRemovedNodesBefore(ticket *time.Ticket) (int, error) {
count := 0
for _, node := range s.removedNodeMap {
if node.removedAt != nil && ticket.Compare(node.removedAt) >= 0 {
s.treeByIndex.Delete(node.indexNode)
s.purge(node)
s.treeByID.Remove(node.id)
delete(s.removedNodeMap, node.id.key())
count++
}
}
// Purge physically purge the given node from RGATreeSplit.
func (s *RGATreeSplit[V]) Purge(child GCChild) error {
node := child.(*RGATreeSplitNode[V])

return count, nil
}

// purge physically purge the given node from RGATreeSplit.
func (s *RGATreeSplit[V]) purge(node *RGATreeSplitNode[V]) {
node.prev.next = node.next
if node.next != nil {
node.next.prev = node.prev
Expand All @@ -653,4 +641,6 @@ func (s *RGATreeSplit[V]) purge(node *RGATreeSplitNode[V]) {
node.insNext.insPrev = node.insPrev
}
node.insPrev, node.insNext = nil, nil

return nil
}
8 changes: 4 additions & 4 deletions pkg/document/crdt/rht.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func newRHTNode(key, val string, updatedAt *time.Ticket, isRemoved bool) *RHTNod
}
}

// ID returns the ID of this node.
func (n *RHTNode) ID() string {
// IDString returns the string representation of this node.
func (n *RHTNode) IDString() string {
return n.updatedAt.Key() + ":" + n.key
}

Expand Down Expand Up @@ -231,8 +231,8 @@ func (rht *RHT) Marshal() string {

// Purge purges the given child node.
func (rht *RHT) Purge(child *RHTNode) error {
if node, ok := rht.nodeMapByKey[child.key]; !ok || node.ID() != child.ID() {
//return ErrChildNotFound
if node, ok := rht.nodeMapByKey[child.key]; !ok || node.IDString() != child.IDString() {
// TODO(hackerwins): Should we return an error when the child is not found?
return nil
}

Expand Down
Loading

0 comments on commit 28cbd37

Please sign in to comment.