Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (sd *safeDoc) Perform(in InPacket, handleID, clientID int) (ok bool, err er
sd.lock.Lock()
defer sd.lock.Unlock()

if !sd.doc.Barrier(in.TargetVersion, in.Barrier) {
if !sd.doc.Barrier(handleID, in.TargetVersion, in.Barrier) {
// not an error - failed to pass barrier
// TODO: we don't explicitly announce this - expect client to know
return
Expand Down
23 changes: 7 additions & 16 deletions server/pkg/ylayer/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,14 @@ func (d *docImpl) Read() (dw *DocWork) {
return
}

// inoToVersion returns the high version for the specific inode.
// This is for the caller to check whether work can be applied (check barrier).
func (d *docImpl) inoToVersion(ino string) (version int) {
nh := d.byIno[ino]
if nh == nil {
return 0
}
_, version = nh.versionRange()
return
}

func (d *docImpl) Barrier(targetVersion int, inos []string) (ok bool) {
if targetVersion <= 0 {
return true
}
func (d *docImpl) Barrier(handle, targetVersion int, inos []string) (ok bool) {
for _, ino := range inos {
if d.inoToVersion(ino) > targetVersion {
nh := d.byIno[ino]
if nh == nil {
continue
}

if !nh.allowBarrier(handle, targetVersion) {
return false
}
}
Expand Down
35 changes: 29 additions & 6 deletions server/pkg/ylayer/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ type nodeHolder struct {
dataAt int // when was data last flattened
data GumnutDataArray // data at dataAt

prune bool // if history is not exhaustive
history []historyEntry // history over time
length int // current length
}

func (fn *nodeHolder) versionRange() (lo, hi int) {
if len(fn.history) == 0 {
return 0, 0
func (fn *nodeHolder) versionRange() (prune bool, lo, hi int) {
if len(fn.history) != 0 {
lo, hi = fn.history[0].version, fn.history[len(fn.history)-1].version
}
return fn.history[0].version, fn.history[len(fn.history)-1].version
prune = fn.prune
return
}

// read moves the current "live" version here forward to the given version number.
Expand Down Expand Up @@ -97,6 +99,27 @@ func (nh *nodeHolder) clearHandle(handle int) (change bool) {
return
}

// allowBarrier checks that the handle with the given target is "as they see it".
// i.e., there are only ops past the target which the handle created.
func (n *nodeHolder) allowBarrier(handle, targetVersion int) (allow bool) {
prune, lo, _ := n.versionRange()
if prune && targetVersion < lo {
return false
}

// look for the first entry that is > the known version
idx := sort.Search(len(n.history), func(i int) (match bool) {
return n.history[i].version > targetVersion
})

for _, entry := range n.history[idx:] {
if handle != entry.handle {
return false
}
}
return true
}

func (nh *nodeHolder) historyFrom(version, handle int) (out []ymodel.PriorOpSelf) {
// look for the first entry that is > the known version
idx := sort.Search(len(nh.history), func(i int) (match bool) {
Expand Down Expand Up @@ -128,8 +151,8 @@ type prepareResult struct {
// prepare generates the updated state we should apply to this nodeHolder after this Patch.
// This is so doc can apply ops on many nodes at once.
func (nh *nodeHolder) prepare(arg prepareArg, p *Patch) (res prepareResult, err error) {
lo, hi := nh.versionRange()
if arg.targetVersion > 0 && arg.targetVersion < lo {
prune, lo, hi := nh.versionRange()
if prune && arg.targetVersion > 0 && arg.targetVersion < lo {
err = ErrVersion
return // must be within known range - but we don't check hi, assume host has
}
Expand Down
4 changes: 2 additions & 2 deletions server/pkg/ylayer/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ type Doc interface {
// Internally, this may apply ops forward: we don't always keep a "live" copy around.
Read() (dw *DocWork)

// Barrier returns true if the given client can pass the barrier with the given inos and their known target version.
// Barrier returns true if the given handle can pass the barrier with the given inos and their known target version.
// This is effectively checking all the target inos have not changed since ...
Barrier(targetVersion int, inos []string) (ok bool)
Barrier(handle, targetVersion int, inos []string) (ok bool)

// ClearHandle removes the given handle from known history.
ClearHandle(handleID int) (change bool)
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ test('data', async (t) => {

data1.root().set('abc', 'hello');
data1.root().set('abc', 'hello123');
// data1.root().set('def', data1.root()); // FIXME: this is failing to apply over itself - seq3
data1.root().set('def', data1.root());

await checkCond(() => {
const root = data2.root();

assert.strictEqual(root.get('abc'), 'hello123');
// assert.strictEqual(root.get('def'), root);
assert.strictEqual(root.get('def'), root);
});
});
Loading