Commit d85bb82c authored by Kirill Smelkov's avatar Kirill Smelkov

ΔFtail concurrency

See changes in δftail.go for overview.

* t2+ΔFtail-concurrency: (39 commits)
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  .
  X zdata: Switch SliceByFileRev not to clone Zinblk
  .
  .
  .
  .
  .
  ...
parents 54b623ba 3207c0ad
...@@ -137,31 +137,55 @@ func (t *T) Head() *Commit { ...@@ -137,31 +137,55 @@ func (t *T) Head() *Commit {
// XGetCommit finds and returns Commit created with revision at. // XGetCommit finds and returns Commit created with revision at.
func (t *T) XGetCommit(at zodb.Tid) *Commit { func (t *T) XGetCommit(at zodb.Tid) *Commit {
commit, _, _ := t.getCommit(at)
if commit == nil {
panicf("no commit corresponding to @%s", at)
}
return commit
}
func (t *T) getCommit(at zodb.Tid) (commit, cprev, cnext *Commit) {
l := len(t.commitv) l := len(t.commitv)
i := sort.Search(l, func(i int) bool { i := sort.Search(l, func(i int) bool {
return at <= t.commitv[i].At return at <= t.commitv[i].At
}) })
var commit *Commit
if i < l { if i < l {
commit = t.commitv[i] commit = t.commitv[i]
if commit.At != at { if commit.At != at {
cnext = commit
commit = nil commit = nil
} else if i+1 < l {
cnext = t.commitv[i+1]
} }
} }
if commit == nil { if i > 0 {
panicf("no commit corresponding to @%s", at) cprev = t.commitv[i-1]
} }
if commit.idx != i { if commit != nil && commit.idx != i {
panicf("BUG: commit.idx (%d) != i (%d)", commit.idx, i) panicf("BUG: commit.idx (%d) != i (%d)", commit.idx, i)
} }
return commit return commit, cprev, cnext
} }
// AtSymb returns symbolic representation of at, for example "at3". // AtSymb returns symbolic representation of at, for example "at3".
// //
// at must correspond to a Commit. // at should correspond to a Commit.
func (t *T) AtSymb(at zodb.Tid) string { func (t *T) AtSymb(at zodb.Tid) string {
return t.XGetCommit(at).AtSymb() commit, cprev, cnext := t.getCommit(at)
if commit != nil {
return commit.AtSymb()
}
// at does not correspond to commit - return something like ~at2<xxxx>at3
s := "~"
if cprev != nil {
s += cprev.AtSymb() + "<"
}
s += at.String()
if cnext != nil {
s += ">" + cnext.AtSymb()
}
return s
} }
// AtSymb returns symbolic representation of c.At, for example "at3". // AtSymb returns symbolic representation of c.At, for example "at3".
......
...@@ -69,7 +69,9 @@ package xbtree ...@@ -69,7 +69,9 @@ package xbtree
// Concurrency // Concurrency
// //
// In order to allow multiple Track and queries requests to be served in // In order to allow multiple Track and queries requests to be served in
// parallel ΔBtail employs special organization of vδT rebuild process: // parallel ΔBtail employs special organization of vδT rebuild process where
// complexity of concurrency is reduced to math on merging updates to vδT and
// trackSet, and on key range lookup:
// //
// 1. vδT is managed under read-copy-update (RCU) discipline: before making // 1. vδT is managed under read-copy-update (RCU) discipline: before making
// any vδT change the mutator atomically clones whole vδT and applies its // any vδT change the mutator atomically clones whole vδT and applies its
...@@ -117,13 +119,21 @@ package xbtree ...@@ -117,13 +119,21 @@ package xbtree
// //
// vδT/(T₁∪T₂) = vδT/T₁ | vδT/T₂ // vδT/(T₁∪T₂) = vδT/T₁ | vδT/T₂
// //
// i.e. vδT computed for tracked set being union of T₁ and T₂ is the same // ( i.e. vδT computed for tracked set being union of T₁ and T₂ is the
// as merge of vδT computed for tracked set T₁ and vδT computed for tracked // same as merge of vδT computed for tracked set T₁ and vδT computed
// set T₂. // for tracked set T₂ )
// //
// this merge property allows to run computation for δ(vδT) independently // and that
// and with ΔBtail unlocked, which in turn enables running several //
// Track/queries in parallel. // trackSet | (δPP₁|δPP₂) = (trackSet|δPP₁) | (trackSet|δPP₂)
//
// ( i.e. tracking set updated for union of δPP₁ and δPP₂ is the same
// as union of tracking set updated with δPP₁ and tracking set updated
// with δPP₂ )
//
// these merge properties allow to run computation for δ(vδT) and δ(trackSet)
// independently and with ΔBtail unlocked, which in turn enables running
// several Track/queries in parallel.
// //
// 4. while vδT rebuild is being run, krebuildJobs keeps corresponding keycov // 4. while vδT rebuild is being run, krebuildJobs keeps corresponding keycov
// entry to indicate in-progress rebuild. Should a query need vδT for keys // entry to indicate in-progress rebuild. Should a query need vδT for keys
...@@ -247,9 +257,9 @@ type _ΔTtail struct { ...@@ -247,9 +257,9 @@ type _ΔTtail struct {
vδT []ΔTree vδT []ΔTree
// set of keys that were requested to be tracked in this tree, // set of keys that were requested to be tracked in this tree,
// but for which vδT rebuild was not yet started // but for which vδT rebuild was not yet started as of @head
ktrackNew blib.RangedKeySet // {keycov} ktrackNew blib.RangedKeySet // {keycov}
// set of nodes corresponding to ktrackNew // set of nodes corresponding to ktrackNew as of @head
trackNew blib.PPTreeSubSet // PP{nodes} trackNew blib.PPTreeSubSet // PP{nodes}
// set of keys(nodes) for which rebuild is in progress // set of keys(nodes) for which rebuild is in progress
...@@ -672,13 +682,13 @@ func (δTtail *_ΔTtail) __rebuild(root zodb.Oid, δBtail *ΔBtail, releaseLock ...@@ -672,13 +682,13 @@ func (δTtail *_ΔTtail) __rebuild(root zodb.Oid, δBtail *ΔBtail, releaseLock
// //
// TODO optionally accept zconnOld/zconnNew from client // TODO optionally accept zconnOld/zconnNew from client
func (δBtail *ΔBtail) Update(δZ *zodb.EventCommit) (_ ΔB, err error) { func (δBtail *ΔBtail) Update(δZ *zodb.EventCommit) (_ ΔB, err error) {
headOld := δBtail.Head()
defer xerr.Contextf(&err, "ΔBtail.Update %s -> %s", headOld, δZ.Tid)
δBtail.mu.Lock() δBtail.mu.Lock()
defer δBtail.mu.Unlock() defer δBtail.mu.Unlock()
// TODO verify that there is no in-progress readers/writers // TODO verify that there is no in-progress readers/writers
headOld := δBtail.Head()
defer xerr.Contextf(&err, "ΔBtail.Update %s -> %s", headOld, δZ.Tid)
δB1, err := δBtail._Update1(δZ) δB1, err := δBtail._Update1(δZ)
δB := ΔB{Rev: δZ.Tid, ByRoot: make(map[zodb.Oid]map[Key]ΔValue)} δB := ΔB{Rev: δZ.Tid, ByRoot: make(map[zodb.Oid]map[Key]ΔValue)}
...@@ -997,7 +1007,7 @@ func (δBtail *ΔBtail) GetAt(root zodb.Oid, key Key, at zodb.Tid) (value Value, ...@@ -997,7 +1007,7 @@ func (δBtail *ΔBtail) GetAt(root zodb.Oid, key Key, at zodb.Tid) (value Value,
// Only tracked keys are guaranteed to be present. // Only tracked keys are guaranteed to be present.
// //
// Note: contrary to regular go slicing, low is exclusive while high is inclusive. // Note: contrary to regular go slicing, low is exclusive while high is inclusive.
func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readonly*/vδT []ΔTree) { func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readonly*/vδT []ΔTree, err error) {
xtail.AssertSlice(δBtail, lo, hi) xtail.AssertSlice(δBtail, lo, hi)
if traceΔBtail { if traceΔBtail {
...@@ -1008,22 +1018,22 @@ func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readon ...@@ -1008,22 +1018,22 @@ func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readon
} }
// retrieve vδT snapshot that is rebuilt to take all previous Track requests into account // retrieve vδT snapshot that is rebuilt to take all previous Track requests into account
vδT, err := δBtail.vδTSnapForTracked(root) vδT, err = δBtail.vδTSnapForTracked(root)
if err != nil { if err != nil {
panic(err) // XXX return nil, err
} }
debugfΔBtail(" vδT: %v\n", vδT) debugfΔBtail(" vδT: %v\n", vδT)
l := len(vδT) l := len(vδT)
if l == 0 { if l == 0 {
return nil return nil, nil
} }
// find max j : [j].rev ≤ hi linear scan -> TODO binary search // find max j : [j].rev ≤ hi linear scan -> TODO binary search
j := l - 1 j := l - 1
for ; j >= 0 && vδT[j].Rev > hi; j-- {} for ; j >= 0 && vδT[j].Rev > hi; j-- {}
if j < 0 { if j < 0 {
return nil // ø return nil, nil // ø
} }
// find max i : [i].rev > lo linear scan -> TODO binary search // find max i : [i].rev > lo linear scan -> TODO binary search
...@@ -1035,7 +1045,7 @@ func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readon ...@@ -1035,7 +1045,7 @@ func (δBtail *ΔBtail) SliceByRootRev(root zodb.Oid, lo, hi zodb.Tid) (/*readon
// modified via RCU: i.e. _ΔTtail.rebuild clones vδT before modifying it. // modified via RCU: i.e. _ΔTtail.rebuild clones vδT before modifying it.
// This way the data we return to caller will stay unchanged even if // This way the data we return to caller will stay unchanged even if
// rebuild is running simultaneously. // rebuild is running simultaneously.
return vδT[i:j+1] return vδT[i:j+1], nil
} }
......
...@@ -1246,11 +1246,11 @@ func TestΔBtailSliceByRootRev(t_ *testing.T) { ...@@ -1246,11 +1246,11 @@ func TestΔBtailSliceByRootRev(t_ *testing.T) {
t.Errorf("%s:\nhave: %s\nwant: %s", subj, have, want) t.Errorf("%s:\nhave: %s\nwant: %s", subj, have, want)
} }
s00 := δbtail.SliceByRootRev(t.Root(), t0.At, t0.At) s00, err := δbtail.SliceByRootRev(t.Root(), t0.At, t0.At); X(err)
s01 := δbtail.SliceByRootRev(t.Root(), t0.At, t1.At) s01, err := δbtail.SliceByRootRev(t.Root(), t0.At, t1.At); X(err)
s02 := δbtail.SliceByRootRev(t.Root(), t0.At, t2.At) s02, err := δbtail.SliceByRootRev(t.Root(), t0.At, t2.At); X(err)
s12 := δbtail.SliceByRootRev(t.Root(), t1.At, t2.At) s12, err := δbtail.SliceByRootRev(t.Root(), t1.At, t2.At); X(err)
s22 := δbtail.SliceByRootRev(t.Root(), t2.At, t2.At) s22, err := δbtail.SliceByRootRev(t.Root(), t2.At, t2.At); X(err)
vδT := δttail.vδT vδT := δttail.vδT
assertvδT("t2.vδT", vδT, ΔT{t1.At, δ{2:{f,g}}}, ΔT{t2.At, δ{2:{g,h}}}) assertvδT("t2.vδT", vδT, ΔT{t1.At, δ{2:{f,g}}}, ΔT{t2.At, δ{2:{g,h}}})
...@@ -1286,11 +1286,11 @@ func TestΔBtailSliceByRootRev(t_ *testing.T) { ...@@ -1286,11 +1286,11 @@ func TestΔBtailSliceByRootRev(t_ *testing.T) {
trackKeys(δbtail, t2, _1) trackKeys(δbtail, t2, _1)
err = δbtail._rebuildAll(); X(err) err = δbtail._rebuildAll(); X(err)
s00_ := δbtail.SliceByRootRev(t.Root(), t0.At, t0.At) s00_, err := δbtail.SliceByRootRev(t.Root(), t0.At, t0.At); X(err)
s01_ := δbtail.SliceByRootRev(t.Root(), t0.At, t1.At) s01_, err := δbtail.SliceByRootRev(t.Root(), t0.At, t1.At); X(err)
s02_ := δbtail.SliceByRootRev(t.Root(), t0.At, t2.At) s02_, err := δbtail.SliceByRootRev(t.Root(), t0.At, t2.At); X(err)
s12_ := δbtail.SliceByRootRev(t.Root(), t1.At, t2.At) s12_, err := δbtail.SliceByRootRev(t.Root(), t1.At, t2.At); X(err)
s22_ := δbtail.SliceByRootRev(t.Root(), t2.At, t2.At) s22_, err := δbtail.SliceByRootRev(t.Root(), t2.At, t2.At); X(err)
vδT = δttail.vδT vδT = δttail.vδT
assertvδT("t12.vδT", vδT, ΔT{t1.At, δ{1:{a,b},2:{f,g}}}, ΔT{t2.At, δ{1:{b,c},2:{g,h}}}) assertvδT("t12.vδT", vδT, ΔT{t1.At, δ{1:{a,b},2:{f,g}}}, ΔT{t2.At, δ{1:{b,c},2:{g,h}}})
......
...@@ -58,12 +58,44 @@ package zdata ...@@ -58,12 +58,44 @@ package zdata
// tracked and is thus easy to maintain. It also can be maintained only in // tracked and is thus easy to maintain. It also can be maintained only in
// ΔFtail because ΔBtail and ΔZtail does not "know" anything about ZBigFile. // ΔFtail because ΔBtail and ΔZtail does not "know" anything about ZBigFile.
// //
// XXX concurrency //
// Concurrency
//
// In order to allow multiple Track and queries requests to be served in
// parallel, ΔFtail bases its concurrency promise on ΔBtail guarantees +
// snapshot-style access for vδE and ztrackInBlk in queries:
//
// 1. Track calls ΔBtail.Track and quickly updates .byFile, .byRoot and
// _RootTrack indices under a lock.
//
// 2. BlkRevAt queries ΔBtail.GetAt and then combines retrieved information
// about zblk with vδE and δZ.
//
// 3. SliceByFileRev queries ΔBtail.SliceByRootRev and then merges retrieved
// vδT data with vδZ, vδE and ztrackInBlk.
//
// 4. In queries vδE is retrieved/built in snapshot style similarly to how vδT
// is built in ΔBtail. Note that vδE needs to be built only the first time,
// and does not need to be further rebuilt, so the logic in ΔFtail is simpler
// compared to ΔBtail.
//
// 5. for ztrackInBlk - that is used by SliceByFileRev query - an atomic
// snapshot is retrieved for objects of interest. This allows to hold
// δFtail.mu lock for relatively brief time without blocking other parallel
// Track/queries requests for long.
//
// Combined this organization allows non-overlapping queries/track-requests
// to run simultaneously. This property is essential to WCFS because otherwise
// WCFS would not be able to serve several non-overlapping READ requests to one
// file in parallel.
//
// See also "Concurrency" in ΔBtail organization for more details.
import ( import (
"context" "context"
"fmt" "fmt"
"sort" "sort"
"sync"
"lab.nexedi.com/kirr/go123/xerr" "lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/transaction" "lab.nexedi.com/kirr/neo/go/transaction"
...@@ -116,26 +148,52 @@ type setOid = set.Oid ...@@ -116,26 +148,52 @@ type setOid = set.Oid
// .rev↑ // .rev↑
// {}blk | EPOCH // {}blk | EPOCH
// //
// XXX concurrent use.
//
// See also zodb.ΔTail and xbtree.ΔBtail // See also zodb.ΔTail and xbtree.ΔBtail
//
//
// Concurrency
//
// ΔFtail is safe to use in single-writer / multiple-readers mode. That is at
// any time there should be either only sole writer, or, potentially several
// simultaneous readers. The table below classifies operations:
//
// Writers: Update, ForgetPast
// Readers: Track + all queries (SliceByRev, SliceByFileRev, BlkRevAt)
//
// Note that, in particular, it is correct to run multiple Track and queries
// requests simultaneously.
type ΔFtail struct { type ΔFtail struct {
// ΔFtail merges ΔBtail with history of ZBlk // ΔFtail merges ΔBtail with history of ZBlk
δBtail *xbtree.ΔBtail δBtail *xbtree.ΔBtail
byFile map[zodb.Oid]*_ΔFileTail // file -> vδf tail
filesByRoot map[zodb.Oid]setOid // tree-root -> {} ZBigFile<oid> as of @head // mu protects ΔFtail data _and_ all _ΔFileTail/_RootTrack data for all files and roots.
//
// NOTE: even though this lock is global it is used only for brief periods of time. In
// particular working with retrieved vδE and Zinblk snapshot does not need to hold the lock.
mu sync.Mutex
byFile map[zodb.Oid]*_ΔFileTail // file -> vδf tail
byRoot map[zodb.Oid]*_RootTrack // tree-root -> ({foid}, Zinblk) as of @head
// set of files, which are newly tracked and for which byFile[foid].vδE was not yet rebuilt // set of files, which are newly tracked and for which byFile[foid].vδE was not yet rebuilt
trackNew setOid // {}foid ftrackNew setOid // {}foid
// set of tracked ZBlk objects reverse-mapped to trees and block numbers // set of tracked ZBlk objects mapped to tree roots as of @head
trackSetZBlk map[zodb.Oid]*zblkTrack // zblk -> {} root -> {}blk as of @head ztrackInRoot map[zodb.Oid]setOid // {} zblk -> {}root
}
// _RootTrack represents tracking information about one particular tree as of @head.
type _RootTrack struct {
ftrackSet setOid // {}foid which ZBigFiles refer to this tree
ztrackInBlk map[zodb.Oid]setI64 // {} zblk -> {}blk which blocks map to zblk
} }
// _ΔFileTail represents tail of revisional changes to one file. // _ΔFileTail represents tail of revisional changes to one file.
type _ΔFileTail struct { type _ΔFileTail struct {
root zodb.Oid // .blktab as of @head root zodb.Oid // .blktab as of @head
vδE []_ΔFileEpoch // epochs (changes to ZBigFile object itself) ; nil if not yet rebuilt vδE []_ΔFileEpoch // epochs (changes to ZBigFile object itself) ; nil if not yet rebuilt
rebuildJob *_RebuildJob // !nil if vδE rebuild is currently in-progress
} }
// _ΔFileEpoch represent a change to ZBigFile object. // _ΔFileEpoch represent a change to ZBigFile object.
...@@ -146,13 +204,14 @@ type _ΔFileEpoch struct { ...@@ -146,13 +204,14 @@ type _ΔFileEpoch struct {
oldBlkSize int64 // .blksize was oldBlkSize ; -1 if ZBigFile deleted oldBlkSize int64 // .blksize was oldBlkSize ; -1 if ZBigFile deleted
newBlkSize int64 // .blksize was changed to newBlkSize ; ----//---- newBlkSize int64 // .blksize was changed to newBlkSize ; ----//----
// snapshot of trackSetZBlk for this file right before this epoch // snapshot of ztrackInBlk for this file right before this epoch
oldTrackSetZBlk map[zodb.Oid]setI64 // {} zblk -> {}blk oldZinblk map[zodb.Oid]setI64 // {} zblk -> {}blk
} }
// zblkTrack keeps information in which root/blocks ZBlk is present as of @head. // _RebuildJob represents currently in-progress vδE rebuilding job.
type zblkTrack struct { type _RebuildJob struct {
inroot map[zodb.Oid]setI64 // {} root -> {}blk ready chan struct{} // closed when job completes
err error
} }
...@@ -180,11 +239,11 @@ type ΔFile struct { ...@@ -180,11 +239,11 @@ type ΔFile struct {
// ZODB when needed. // ZODB when needed.
func NewΔFtail(at0 zodb.Tid, db *zodb.DB) *ΔFtail { func NewΔFtail(at0 zodb.Tid, db *zodb.DB) *ΔFtail {
return &ΔFtail{ return &ΔFtail{
δBtail: xbtree.NewΔBtail(at0, db), δBtail: xbtree.NewΔBtail(at0, db),
byFile: map[zodb.Oid]*_ΔFileTail{}, byFile: map[zodb.Oid]*_ΔFileTail{},
filesByRoot: map[zodb.Oid]setOid{}, byRoot: map[zodb.Oid]*_RootTrack{},
trackNew: setOid{}, ftrackNew: setOid{},
trackSetZBlk: map[zodb.Oid]*zblkTrack{}, ztrackInRoot: map[zodb.Oid]setOid{},
} }
} }
...@@ -205,8 +264,6 @@ func (δFtail *ΔFtail) Tail() zodb.Tid { return δFtail.δBtail.Tail() } ...@@ -205,8 +264,6 @@ func (δFtail *ΔFtail) Tail() zodb.Tid { return δFtail.δBtail.Tail() }
// //
// Objects in path and zblk must be with .PJar().At() == .head // Objects in path and zblk must be with .PJar().At() == .head
func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, blkcov btree.LKeyRange, zblk ZBlk) { func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, blkcov btree.LKeyRange, zblk ZBlk) {
// XXX locking
head := δFtail.Head() head := δFtail.Head()
fileAt := file.PJar().At() fileAt := file.PJar().At()
...@@ -226,18 +283,25 @@ func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, bl ...@@ -226,18 +283,25 @@ func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, bl
rootObj := path[0].(*btree.LOBTree) rootObj := path[0].(*btree.LOBTree)
root := rootObj.POid() root := rootObj.POid()
files, ok := δFtail.filesByRoot[root]
δFtail.mu.Lock()
defer δFtail.mu.Unlock()
rt, ok := δFtail.byRoot[root]
if !ok { if !ok {
files = setOid{} rt = &_RootTrack{
δFtail.filesByRoot[root] = files ftrackSet: setOid{},
ztrackInBlk: map[zodb.Oid]setI64{},
}
δFtail.byRoot[root] = rt
} }
files.Add(foid) rt.ftrackSet.Add(foid)
δftail, ok := δFtail.byFile[foid] δftail, ok := δFtail.byFile[foid]
if !ok { if !ok {
δftail = &_ΔFileTail{root: root, vδE: nil /*will need to be rebuilt to past till tail*/} δftail = &_ΔFileTail{root: root, vδE: nil /*will need to be rebuilt to past till tail*/}
δFtail.byFile[foid] = δftail δFtail.byFile[foid] = δftail
δFtail.trackNew.Add(foid) δFtail.ftrackNew.Add(foid)
} }
if δftail.root != root { if δftail.root != root {
// .root can change during epochs, but in between them it must be stable // .root can change during epochs, but in between them it must be stable
...@@ -248,109 +312,96 @@ func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, bl ...@@ -248,109 +312,96 @@ func (δFtail *ΔFtail) Track(file *ZBigFile, blk int64, path []btree.LONode, bl
// associate zblk with root, if it was not hole // associate zblk with root, if it was not hole
if zblk != nil { if zblk != nil {
zoid := zblk.POid() zoid := zblk.POid()
zt, ok := δFtail.trackSetZBlk[zoid]
inroot, ok := δFtail.ztrackInRoot[zoid]
if !ok { if !ok {
zt = &zblkTrack{} inroot = make(setOid, 1)
δFtail.trackSetZBlk[zoid] = zt δFtail.ztrackInRoot[zoid] = inroot
} }
inroot.Add(root)
inblk, ok := zt.inroot[root] inblk, ok := rt.ztrackInBlk[zoid]
if !ok { if !ok {
inblk = make(setI64, 1) inblk = make(setI64, 1)
if zt.inroot == nil { rt.ztrackInBlk[zoid] = inblk
zt.inroot = make(map[zodb.Oid]setI64)
}
zt.inroot[root] = inblk
} }
inblk.Add(blk) inblk.Add(blk)
} }
} }
// rebuildAll rebuilds vδE for all files from trackNew requests. // vδEForFile returns vδE and current root for specified file.
func (δFtail *ΔFtail) rebuildAll() (err error) { //
defer xerr.Contextf(&err, "ΔFtail rebuildAll") // It builds vδE for that file if there is such need.
// XXX locking // The only case when vδE actually needs to be built is when the file just started to be tracked.
func (δFtail *ΔFtail) vδEForFile(foid zodb.Oid) (vδE []_ΔFileEpoch, headRoot zodb.Oid, err error) {
δFtail.mu.Lock() // TODO verify that there is no in-progress writers
defer δFtail.mu.Unlock()
δBtail := δFtail.δBtail δftail := δFtail.byFile[foid]
δZtail := δBtail.ΔZtail() root := δftail.root
db := δBtail.DB() vδE = δftail.vδE
for foid := range δFtail.trackNew { if vδE != nil {
δFtail.trackNew.Del(foid) return vδE, root, nil
δftail := δFtail.byFile[foid] }
err := δftail.rebuild1(foid, δZtail, db)
if err != nil { // vδE needs to be built
return err job := δftail.rebuildJob
// rebuild is currently in-progress -> wait for corresponding job to complete
if job != nil {
δFtail.mu.Unlock()
<-job.ready
if job.err == nil {
δFtail.mu.Lock()
vδE = δftail.vδE
} }
return vδE, root, job.err
} }
return nil // we become responsible to build vδE
} // release the lock while building to allow simultaneous access to other files
job = &_RebuildJob{ready: make(chan struct{})}
δftail.rebuildJob = job
δFtail.ftrackNew.Del(foid)
δBtail := δFtail.δBtail
// rebuild1IfNeeded rebuilds vδE if there is such need. δFtail.mu.Unlock()
// vδE, err = vδEBuild(foid, δBtail.ΔZtail(), δBtail.DB())
// it returns corresponding δftail for convenience. δFtail.mu.Lock()
// the only case when vδE actually needs to be rebuilt is when the file just started to be tracked.
func (δFtail *ΔFtail) rebuild1IfNeeded(foid zodb.Oid) (_ *_ΔFileTail, err error) {
// XXX locking
δftail := δFtail.byFile[foid] if err == nil {
if δftail.vδE != nil { δftail.vδE = vδE
err = nil
} else { } else {
δFtail.trackNew.Del(foid) δFtail.ftrackNew.Add(foid)
δBtail := δFtail.δBtail
err = δftail.rebuild1(foid, δBtail.ΔZtail(), δBtail.DB())
} }
return δftail, err
}
// rebuild1 rebuilds vδE. δftail.rebuildJob = nil
func (δftail *_ΔFileTail) rebuild1(foid zodb.Oid, δZtail *zodb.ΔTail, db *zodb.DB) (err error) { job.err = err
defer xerr.Contextf(&err, "file<%s>: rebuild", foid) close(job.ready)
// XXX locking return vδE, root, err
if δftail.vδE != nil { }
panic("rebuild1: vδE != nil")
}
vδE := []_ΔFileEpoch{}
vδZ := δZtail.Data()
atPrev := δZtail.Tail()
for i := 0; i < len(vδZ); i++ {
δZ := vδZ[i]
fchanged := false // _rebuildAll rebuilds vδE for all files from ftrackNew requests.
for _, oid := range δZ.Changev { //
if oid == foid { // must be called with δFtail.mu locked.
fchanged = true func (δFtail *ΔFtail) _rebuildAll() (err error) {
break defer xerr.Contextf(&err, "ΔFtail rebuildAll")
}
}
if !fchanged {
continue
}
δ, err := zfilediff(db, foid, atPrev, δZ.Rev) δBtail := δFtail.δBtail
δZtail := δBtail.ΔZtail()
db := δBtail.DB()
for foid := range δFtail.ftrackNew {
δFtail.ftrackNew.Del(foid)
δftail := δFtail.byFile[foid]
// no need to set δftail.rebuildJob - we are under lock
δftail.vδE, err = vδEBuild(foid, δZtail, db)
if err != nil { if err != nil {
δFtail.ftrackNew.Add(foid)
return err return err
} }
if δ != nil {
δE := _ΔFileEpoch{
Rev: δZ.Rev,
oldRoot: δ.blktabOld,
newRoot: δ.blktabNew,
oldBlkSize: δ.blksizeOld,
newBlkSize: δ.blksizeNew,
oldTrackSetZBlk: nil, // nothing was tracked
}
vδE = append(vδE, δE)
}
atPrev = δZ.Rev
} }
δftail.vδE = vδE
return nil return nil
} }
...@@ -360,17 +411,19 @@ func (δftail *_ΔFileTail) rebuild1(foid zodb.Oid, δZtail *zodb.ΔTail, db *zo ...@@ -360,17 +411,19 @@ func (δftail *_ΔFileTail) rebuild1(foid zodb.Oid, δZtail *zodb.ΔTail, db *zo
// //
// δZ should include all objects changed by ZODB transaction. // δZ should include all objects changed by ZODB transaction.
func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
defer xerr.Contextf(&err, "ΔFtail update %s -> %s", δFtail.Head(), δZ.Tid) headOld := δFtail.Head()
defer xerr.Contextf(&err, "ΔFtail update %s -> %s", headOld, δZ.Tid)
// XXX locking δFtail.mu.Lock()
defer δFtail.mu.Unlock()
// TODO verify that there is no in-progress readers/writers
// rebuild vδE for newly tracked files // rebuild vδE for newly tracked files
err = δFtail.rebuildAll() err = δFtail._rebuildAll()
if err != nil { if err != nil {
return ΔF{}, err return ΔF{}, err
} }
headOld := δFtail.Head()
δB, err := δFtail.δBtail.Update(δZ) δB, err := δFtail.δBtail.Update(δZ)
if err != nil { if err != nil {
return ΔF{}, err return ΔF{}, err
...@@ -394,23 +447,31 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -394,23 +447,31 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
if δ != nil { if δ != nil {
δzfile[oid] = δ δzfile[oid] = δ
δE := _ΔFileEpoch{ δE := _ΔFileEpoch{
Rev: δZ.Tid, Rev: δZ.Tid,
oldRoot: δ.blktabOld, oldRoot: δ.blktabOld,
newRoot: δ.blktabNew, newRoot: δ.blktabNew,
oldBlkSize: δ.blksizeOld, oldBlkSize: δ.blksizeOld,
newBlkSize: δ.blksizeNew, newBlkSize: δ.blksizeNew,
oldTrackSetZBlk: map[zodb.Oid]setI64{}, oldZinblk: map[zodb.Oid]setI64{},
} }
for oid, zt := range δFtail.trackSetZBlk { rt, ok := δFtail.byRoot[δftail.root]
inblk, ok := zt.inroot[δftail.root] if ok {
if ok { for zoid, inblk := range rt.ztrackInBlk {
δE.oldTrackSetZBlk[oid] = inblk δE.oldZinblk[zoid] = inblk.Clone()
delete(zt.inroot, δftail.root) inroot, ok := δFtail.ztrackInRoot[zoid]
if ok {
inroot.Del(δftail.root)
if len(inroot) == 0 {
delete(δFtail.ztrackInRoot, zoid)
}
}
} }
} }
δftail.root = δE.newRoot δftail.root = δE.newRoot
// NOTE no need to clone vδE: we are writer, vδE is never returned to
// outside, append does not invalidate previous vδE retrievals.
δftail.vδE = append(δftail.vδE, δE) δftail.vδE = append(δftail.vδE, δE)
} }
} }
...@@ -419,11 +480,15 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -419,11 +480,15 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
//fmt.Printf("δB.ByRoot: %v\n", δB.ByRoot) //fmt.Printf("δB.ByRoot: %v\n", δB.ByRoot)
for root, δt := range δB.ByRoot { for root, δt := range δB.ByRoot {
//fmt.Printf("root: %v δt: %v\n", root, δt) //fmt.Printf("root: %v δt: %v\n", root, δt)
files := δFtail.filesByRoot[root] rt, ok := δFtail.byRoot[root]
// NOTE files might be empty e.g. if a zfile was tracked, then // NOTE rt might be nil e.g. if a zfile was tracked, then
// deleted, but the tree referenced by zfile.blktab is still // deleted, but the tree referenced by zfile.blktab is still
// not-deleted, remains tracked and is changed. // not-deleted, remains tracked and is changed.
for file := range files { if !ok {
continue
}
for file := range rt.ftrackSet {
δfile, ok := δF.ByFile[file] δfile, ok := δF.ByFile[file]
if !ok { if !ok {
δfile = &ΔFile{Rev: δF.Rev, Blocks: make(setI64)} δfile = &ΔFile{Rev: δF.Rev, Blocks: make(setI64)}
...@@ -440,31 +505,34 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -440,31 +505,34 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
δfile.Size = true δfile.Size = true
} }
// update trackSetZBlk according to btree changes // update ztrackInBlk according to btree changes
for blk, δzblk := range δt { for blk, δzblk := range δt {
if δzblk.Old != xbtree.VDEL { if δzblk.Old != xbtree.VDEL {
ztOld, ok := δFtail.trackSetZBlk[δzblk.Old] inblk, ok := rt.ztrackInBlk[δzblk.Old]
if ok { if ok {
inblk, ok := ztOld.inroot[root] inblk.Del(blk)
if ok { if len(inblk) == 0 {
inblk.Del(blk) delete(rt.ztrackInBlk, δzblk.Old)
inroot := δFtail.ztrackInRoot[δzblk.Old]
inroot.Del(root)
if len(inroot) == 0 {
delete(δFtail.ztrackInRoot, δzblk.Old)
}
} }
} }
} }
if δzblk.New != xbtree.VDEL { if δzblk.New != xbtree.VDEL {
ztNew, ok := δFtail.trackSetZBlk[δzblk.New] inblk, ok := rt.ztrackInBlk[δzblk.New]
if !ok {
ztNew = &zblkTrack{}
δFtail.trackSetZBlk[δzblk.New] = ztNew
}
inblk, ok := ztNew.inroot[root]
if !ok { if !ok {
inblk = make(setI64, 1) inblk = make(setI64, 1)
if ztNew.inroot == nil { rt.ztrackInBlk[δzblk.New] = inblk
ztNew.inroot = make(map[zodb.Oid]setI64) inroot, ok := δFtail.ztrackInRoot[δzblk.New]
if !ok {
inroot = make(setOid, 1)
δFtail.ztrackInRoot[δzblk.New] = inroot
} }
ztNew.inroot[root] = inblk inroot.Add(root)
} }
inblk.Add(blk) inblk.Add(blk)
} }
...@@ -473,18 +541,19 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -473,18 +541,19 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
// take zblk changes into account // take zblk changes into account
for _, oid := range δZ.Changev { for _, oid := range δZ.Changev {
zt, ok := δFtail.trackSetZBlk[oid] inroot, ok := δFtail.ztrackInRoot[oid]
if !ok { if !ok {
continue // not tracked continue // not tracked
} }
for root, inblk := range zt.inroot { for root := range inroot {
if len(inblk) == 0 { rt := δFtail.byRoot[root] // must be there
inblk, ok := rt.ztrackInBlk[oid]
if !ok || len(inblk) == 0 {
continue continue
} }
//fmt.Printf("root: %v inblk: %v\n", root, inblk) //fmt.Printf("root: %v inblk: %v\n", root, inblk)
files := δFtail.filesByRoot[root] for file := range rt.ftrackSet {
for file := range files {
δfile, ok := δF.ByFile[file] δfile, ok := δF.ByFile[file]
if !ok { if !ok {
δfile = &ΔFile{Rev: δF.Rev, Blocks: make(setI64)} δfile = &ΔFile{Rev: δF.Rev, Blocks: make(setI64)}
...@@ -509,23 +578,36 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -509,23 +578,36 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
//fmt.Printf("δZBigFile: %v\n", δ) //fmt.Printf("δZBigFile: %v\n", δ)
// update .filesByRoot // update .byRoot
if δ.blktabOld != xbtree.VDEL { if δ.blktabOld != xbtree.VDEL {
files, ok := δFtail.filesByRoot[δ.blktabOld] rt, ok := δFtail.byRoot[δ.blktabOld]
if ok { if ok {
files.Del(foid) rt.ftrackSet.Del(foid)
if len(files) == 0 { if len(rt.ftrackSet) == 0 {
delete(δFtail.filesByRoot, δ.blktabOld) delete(δFtail.byRoot, δ.blktabOld)
// Zinroot -= δ.blktabNew
for zoid := range rt.ztrackInBlk {
inroot, ok := δFtail.ztrackInRoot[zoid]
if ok {
inroot.Del(δ.blktabOld)
if len(inroot) == 0 {
delete(δFtail.ztrackInRoot, zoid)
}
}
}
} }
} }
} }
if δ.blktabNew != xbtree.VDEL { if δ.blktabNew != xbtree.VDEL {
files, ok := δFtail.filesByRoot[δ.blktabNew] rt, ok := δFtail.byRoot[δ.blktabNew]
if !ok { if !ok {
files = setOid{} rt = &_RootTrack{
δFtail.filesByRoot[δ.blktabNew] = files ftrackSet: setOid{},
ztrackInBlk: map[zodb.Oid]setI64{},
}
δFtail.byRoot[δ.blktabNew] = rt
} }
files.Add(foid) rt.ftrackSet.Add(foid)
} }
} }
...@@ -536,19 +618,20 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) { ...@@ -536,19 +618,20 @@ func (δFtail *ΔFtail) Update(δZ *zodb.EventCommit) (_ ΔF, err error) {
// ForgetPast discards all δFtail entries with rev ≤ revCut. // ForgetPast discards all δFtail entries with rev ≤ revCut.
func (δFtail *ΔFtail) ForgetPast(revCut zodb.Tid) { func (δFtail *ΔFtail) ForgetPast(revCut zodb.Tid) {
δFtail.δBtail.ForgetPast(revCut) δFtail.mu.Lock()
defer δFtail.mu.Unlock()
// TODO verify that there is no in-progress readers/writers
// XXX locking δFtail.δBtail.ForgetPast(revCut)
// TODO keep index which file changed epoch where (similarly to ΔBtail), // TODO keep index which file changed epoch where (similarly to ΔBtail),
// and, instead of scanning all files, trim vδE only on files that is really necessary. // and, instead of scanning all files, trim vδE only on files that is really necessary.
for _, δftail := range δFtail.byFile { for _, δftail := range δFtail.byFile {
δftail.forgetPast(revCut) δftail._forgetPast(revCut)
} }
} }
func (δftail *_ΔFileTail) forgetPast(revCut zodb.Tid) { func (δftail *_ΔFileTail) _forgetPast(revCut zodb.Tid) {
// XXX locking
icut := 0 icut := 0
for ; icut < len(δftail.vδE); icut++ { for ; icut < len(δftail.vδE); icut++ {
if δftail.vδE[icut].Rev > revCut { if δftail.vδE[icut].Rev > revCut {
...@@ -567,6 +650,18 @@ func (δftail *_ΔFileTail) forgetPast(revCut zodb.Tid) { ...@@ -567,6 +650,18 @@ func (δftail *_ΔFileTail) forgetPast(revCut zodb.Tid) {
// TODO if needed // TODO if needed
// func (δFtail *ΔFtail) SliceByRev(lo, hi zodb.Tid) /*readonly*/ []ΔF // func (δFtail *ΔFtail) SliceByRev(lo, hi zodb.Tid) /*readonly*/ []ΔF
// _ZinblkOverlay is used by SliceByFileRev.
// It combines read-only Zinblk base with read-write adjustment.
// It provides the following operations:
//
// - Get(zblk) -> {blk},
// - AddBlk(zblk, blk),
// - DelBlk(zblk, blk)
type _ZinblkOverlay struct {
Base map[zodb.Oid]setI64 // taken from _RootTrack.ztrackInBlk or _ΔFileEpoch.oldZinblk
Adj map[zodb.Oid]setI64 // adjustment over base; blk<0 represents whiteout
}
// SliceByFileRev returns history of file changes in (lo, hi] range. // SliceByFileRev returns history of file changes in (lo, hi] range.
// //
// it must be called with the following condition: // it must be called with the following condition:
...@@ -578,12 +673,18 @@ func (δftail *_ΔFileTail) forgetPast(revCut zodb.Tid) { ...@@ -578,12 +673,18 @@ func (δftail *_ΔFileTail) forgetPast(revCut zodb.Tid) {
// Only tracked blocks are guaranteed to be present. // Only tracked blocks are guaranteed to be present.
// //
// Note: contrary to regular go slicing, low is exclusive while high is inclusive. // Note: contrary to regular go slicing, low is exclusive while high is inclusive.
func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*readonly*/[]*ΔFile { func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) (/*readonly*/[]*ΔFile, error) {
foid := zfile.POid() foid := zfile.POid()
//fmt.Printf("\nslice f<%s> (@%s,@%s]\n", foid, lo, hi) //fmt.Printf("\nslice f<%s> (@%s,@%s]\n", foid, lo, hi)
xtail.AssertSlice(δFtail, lo, hi) vδf, err := δFtail._SliceByFileRev(foid, lo, hi)
if err != nil {
err = fmt.Errorf("slice f<%s> (@%s,@%s]: %e", foid, lo, hi, err)
}
return vδf, err
}
// XXX locking func (δFtail *ΔFtail) _SliceByFileRev(foid zodb.Oid, lo, hi zodb.Tid) (/*readonly*/[]*ΔFile, error) {
xtail.AssertSlice(δFtail, lo, hi)
// query .δBtail.SliceByRootRev(file.blktab, lo, hi) + // query .δBtail.SliceByRootRev(file.blktab, lo, hi) +
// merge δZBlk history with that. // merge δZBlk history with that.
...@@ -602,9 +703,9 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -602,9 +703,9 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
// δFile ────────o───────o──────x─────x──────────────────────── // δFile ────────o───────o──────x─────x────────────────────────
δftail, err := δFtail.rebuild1IfNeeded(foid) vδE, headRoot, err := δFtail.vδEForFile(foid)
if err != nil { if err != nil {
panic(err) // XXX return nil, err
} }
var vδf []*ΔFile var vδf []*ΔFile
...@@ -617,7 +718,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -617,7 +718,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
return δfTail return δfTail
} }
if !(tail < δfTail.Rev) { if !(tail < δfTail.Rev) {
panic("tail not ↓") panic("BUG: tail not ↓")
} }
} }
...@@ -630,7 +731,6 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -630,7 +731,6 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
iz := len(vδZ) - 1 iz := len(vδZ) - 1
// find epoch that covers hi // find epoch that covers hi
vδE := δftail.vδE
le := len(vδE) le := len(vδE)
ie := sort.Search(le, func(i int) bool { ie := sort.Search(le, func(i int) bool {
return hi < vδE[i].Rev return hi < vδE[i].Rev
...@@ -658,32 +758,64 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -658,32 +758,64 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
var head zodb.Tid // head] of current epoch coverage var head zodb.Tid // head] of current epoch coverage
// state of Zinblk as we are scanning ← current epoch // state of Zinblk as we are scanning ← current epoch
// initially corresponds to head of the epoch (= @head for latest epoch) // initially corresponds to head of the epoch (= @head for latest epoch)
Zinblk := map[zodb.Oid]setI64{} // zblk -> which #blk refers to it Zinblk := _ZinblkOverlay{} // zblk -> which #blk refers to it
var ZinblkAt zodb.Tid // Zinblk covers [ZinblkAt,<next δT>) var ZinblkAt zodb.Tid // Zinblk covers [ZinblkAt,<next δT>)
if ie+1 == le { if ie+1 == le {
// head // head
root = δftail.root root = headRoot
head = δFtail.Head() head = δFtail.Head()
for zblk, zt := range δFtail.trackSetZBlk {
inblk, ok := zt.inroot[root] // take atomic Zinblk snapshot that covers vδZ
if ok { //
Zinblk[zblk] = inblk.Clone() // - the reason we take atomic snapshot is because simultaneous Track
// requests might change Zinblk concurrently, and without snapshotting
// this might result in changes to a block being not uniformly present in
// the returned vδf (some revision indicates change to that block, while
// another one - where the block is too actually changed - does not
// indicate change to that block).
//
// - the reason we limit snapshot to vδZ is to reduce amount of under-lock
// copying, because original Zinblk is potentially very large.
//
// NOTE the other approach could be to keep blocks in _RootTrack.Zinblk with
// serial (!= zodb serial), and work with that _RootTrack.Zinblk snapshot by
// ignoring all blocks with serial > serial of snapshot view. Do not kill
// _ZinblkOverlay yet because we keep this approach in mind for the future.
ZinblkSnap := map[zodb.Oid]setI64{}
δZAllOid := setOid{}
for _, δZ := range vδZ {
for _, oid := range δZ.Changev {
δZAllOid.Add(oid)
}
}
δFtail.mu.Lock()
rt, ok := δFtail.byRoot[root]
if ok {
for oid := range δZAllOid {
inblk, ok := rt.ztrackInBlk[oid]
if ok {
ZinblkSnap[oid] = inblk.Clone()
}
} }
} }
δFtail.mu.Unlock()
Zinblk.Base = ZinblkSnap
} else { } else {
δE := vδE[ie+1] δE := vδE[ie+1]
root = δE.oldRoot root = δE.oldRoot
head = δE.Rev - 1 // TODO better set to exact revision coming before δE.Rev head = δE.Rev - 1 // TODO better set to exact revision coming before δE.Rev
for zblk, inblk := range δE.oldTrackSetZBlk { Zinblk.Base = δE.oldZinblk
Zinblk[zblk] = inblk.Clone()
}
} }
//fmt.Printf("Zinblk: %v\n", Zinblk) //fmt.Printf("Zinblk: %v\n", Zinblk)
// vδT for current epoch // vδT for current epoch
var vδT []xbtree.ΔTree var vδT []xbtree.ΔTree
if root != xbtree.VDEL { if root != xbtree.VDEL {
vδT = δFtail.δBtail.SliceByRootRev(root, epoch, head) // NOTE @head, not hi vδT, err = δFtail.δBtail.SliceByRootRev(root, epoch, head) // NOTE @head, not hi
if err != nil {
return nil, err
}
} }
it := len(vδT) - 1 it := len(vδT) - 1
if it >= 0 { if it >= 0 {
...@@ -707,33 +839,25 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -707,33 +839,25 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
// also all present @at2, @at3 and @at4 - because @at2 both 0 // also all present @at2, @at3 and @at4 - because @at2 both 0
// and 1 are changed in the same tracked bucket. Note that // and 1 are changed in the same tracked bucket. Note that
// changes to 2 should not be present at all. // changes to 2 should not be present at all.
ZinblkAdj := map[zodb.Oid]setI64{} Zinblk.Adj = map[zodb.Oid]setI64{}
for _, δT := range vδT { for _, δT := range vδT {
for blk, δzblk := range δT.KV { for blk, δzblk := range δT.KV {
if δzblk.Old != xbtree.VDEL { if δzblk.Old != xbtree.VDEL {
inblk, ok := ZinblkAdj[δzblk.Old] inblk, ok := Zinblk.Adj[δzblk.Old]
if ok { if ok {
inblk.Del(blk) inblk.Del(blk)
} }
} }
if δzblk.New != xbtree.VDEL { if δzblk.New != xbtree.VDEL {
inblk, ok := ZinblkAdj[δzblk.New] inblk, ok := Zinblk.Adj[δzblk.New]
if !ok { if !ok {
inblk = setI64{} inblk = setI64{}
ZinblkAdj[δzblk.New] = inblk Zinblk.Adj[δzblk.New] = inblk
} }
inblk.Add(blk) inblk.Add(blk)
} }
} }
} }
for zblk, inblkAdj := range ZinblkAdj {
inblk, ok := Zinblk[zblk]
if !ok {
Zinblk[zblk] = inblkAdj
} else {
inblk.Update(inblkAdj)
}
}
// merge vδZ and vδT of current epoch // merge vδZ and vδT of current epoch
for ((iz >= 0 && vδZ[iz].Rev > epoch) || it >= 0) { for ((iz >= 0 && vδZ[iz].Rev > epoch) || it >= 0) {
...@@ -744,7 +868,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -744,7 +868,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
if ZinblkAt <= δZ.Rev { if ZinblkAt <= δZ.Rev {
//fmt.Printf("δZ @%s\n", δZ.Rev) //fmt.Printf("δZ @%s\n", δZ.Rev)
for _, oid := range δZ.Changev { for _, oid := range δZ.Changev {
inblk, ok := Zinblk[oid] inblk, ok := Zinblk.Get_(oid)
if ok && len(inblk) != 0 { if ok && len(inblk) != 0 {
δf := vδfTail(δZ.Rev) δf := vδfTail(δZ.Rev)
δf.Blocks.Update(inblk) δf.Blocks.Update(inblk)
...@@ -762,18 +886,10 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -762,18 +886,10 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
for blk, δzblk := range δT.KV { for blk, δzblk := range δT.KV {
// apply in reverse as we go ← // apply in reverse as we go ←
if δzblk.New != xbtree.VDEL { if δzblk.New != xbtree.VDEL {
inblk, ok := Zinblk[δzblk.New] Zinblk.DelBlk(δzblk.New, blk)
if ok {
inblk.Del(blk)
}
} }
if δzblk.Old != xbtree.VDEL { if δzblk.Old != xbtree.VDEL {
inblk, ok := Zinblk[δzblk.Old] Zinblk.AddBlk(δzblk.Old, blk)
if !ok {
inblk = setI64{}
Zinblk[δzblk.Old] = inblk
}
inblk.Add(blk)
} }
if δT.Rev <= hi { if δT.Rev <= hi {
...@@ -810,9 +926,70 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -810,9 +926,70 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
vδf[i], vδf[j] = vδf[j], vδf[i] vδf[i], vδf[j] = vδf[j], vδf[i]
} }
return vδf return vδf, nil
}
// ZinblkOverlay
// Get_ returns set(blk) for o[zoid].
func (o *_ZinblkOverlay) Get_(zoid zodb.Oid) (inblk /*readonly*/setI64, ok bool) {
base, bok := o.Base[zoid]
adj, aok := o.Adj[zoid]
if !aok {
return base, bok
}
// combine base + adj
if bok {
inblk = base.Clone()
} else {
inblk = make(setI64, len(adj))
}
for blk := range adj {
if blk < 0 { // whiteout
inblk.Del(flipsign(blk))
} else {
inblk.Add(blk)
}
}
if len(inblk) == 0 {
return nil, false
}
return inblk, true
}
// DelBlk removes blk from o[zoid].
func (o *_ZinblkOverlay) DelBlk(zoid zodb.Oid, blk int64) {
if blk < 0 {
panic("blk < 0")
}
o._AddBlk(zoid, flipsign(blk))
}
// AddBlk adds blk to o[zoid].
func (o *_ZinblkOverlay) AddBlk(zoid zodb.Oid, blk int64) {
if blk < 0 {
panic("blk < 0")
}
o._AddBlk(zoid, blk)
}
func (o *_ZinblkOverlay) _AddBlk(zoid zodb.Oid, blk int64) {
adj, ok := o.Adj[zoid]
if !ok {
adj = make(setI64, 1)
o.Adj[zoid] = adj
}
adj.Add(blk)
adj.Del(flipsign(blk))
}
// flipsign returns x with sign bit flipped.
func flipsign(x int64) int64 {
return int64(uint64(x) ^ (1<<63))
} }
// BlkRevAt returns last revision that changed file[blk] as of @at database state. // BlkRevAt returns last revision that changed file[blk] as of @at database state.
// //
// if exact=False - what is returned is only an upper bound for last block revision. // if exact=False - what is returned is only an upper bound for last block revision.
...@@ -820,14 +997,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado ...@@ -820,14 +997,7 @@ func (δFtail *ΔFtail) SliceByFileRev(zfile *ZBigFile, lo, hi zodb.Tid) /*reado
// zfile must be any checkout from (tail, head] // zfile must be any checkout from (tail, head]
// at must ∈ (tail, head] // at must ∈ (tail, head]
// blk must be tracked // blk must be tracked
func (δFtail *ΔFtail) BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int64, at zodb.Tid) (_ zodb.Tid, exact bool) { func (δFtail *ΔFtail) BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int64, at zodb.Tid) (_ zodb.Tid, exact bool, err error) {
rev, exact, err := δFtail._BlkRevAt(ctx, zfile, blk, at)
if err != nil {
panic(err) // XXX
}
return rev, exact
}
func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int64, at zodb.Tid) (_ zodb.Tid, exact bool, err error) {
foid := zfile.POid() foid := zfile.POid()
defer xerr.Contextf(&err, "blkrev f<%s> #%d @%s", foid, blk, at) defer xerr.Contextf(&err, "blkrev f<%s> #%d @%s", foid, blk, at)
...@@ -847,15 +1017,12 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6 ...@@ -847,15 +1017,12 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6
panicf("zconn.at out of bounds: zconn.at: @%s, (tail, head] = (@%s, @%s]", zconnAt, tail, head) panicf("zconn.at out of bounds: zconn.at: @%s, (tail, head] = (@%s, @%s]", zconnAt, tail, head)
} }
// XXX locking vδE, headRoot, err := δFtail.vδEForFile(foid)
δftail, err := δFtail.rebuild1IfNeeded(foid)
if err != nil { if err != nil {
return zodb.InvalidTid, false, err return zodb.InvalidTid, false, err
} }
// find epoch that covers at and associated blktab root/object // find epoch that covers at and associated blktab root/object
vδE := δftail.vδE
//fmt.Printf(" vδE: %v\n", vδE) //fmt.Printf(" vδE: %v\n", vδE)
l := len(vδE) l := len(vδE)
i := sort.Search(l, func(i int) bool { i := sort.Search(l, func(i int) bool {
...@@ -867,7 +1034,7 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6 ...@@ -867,7 +1034,7 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6
// root // root
var root zodb.Oid var root zodb.Oid
if i == l { if i == l {
root = δftail.root root = headRoot
} else { } else {
root = vδE[i].oldRoot root = vδE[i].oldRoot
} }
...@@ -877,7 +1044,7 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6 ...@@ -877,7 +1044,7 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6
i-- i--
if i < 0 { if i < 0 {
// i<0 - first epoch (no explicit start) - use δFtail.tail as lo // i<0 - first epoch (no explicit start) - use δFtail.tail as lo
epoch = δFtail.Tail() epoch = tail
} else { } else {
epoch = vδE[i].Rev epoch = vδE[i].Rev
} }
...@@ -944,8 +1111,51 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6 ...@@ -944,8 +1111,51 @@ func (δFtail *ΔFtail) _BlkRevAt(ctx context.Context, zfile *ZBigFile, blk int6
} }
// ---------------------------------------- // ---- vδEBuild (vδE rebuild core) ----
// vδEBuild builds vδE for file from vδZ.
func vδEBuild(foid zodb.Oid, δZtail *zodb.ΔTail, db *zodb.DB) (vδE []_ΔFileEpoch, err error) {
defer xerr.Contextf(&err, "file<%s>: build vδE", foid)
vδE = []_ΔFileEpoch{}
vδZ := δZtail.Data()
atPrev := δZtail.Tail()
for i := 0; i < len(vδZ); i++ {
δZ := vδZ[i]
fchanged := false
for _, oid := range δZ.Changev {
if oid == foid {
fchanged = true
break
}
}
if !fchanged {
continue
}
δ, err := zfilediff(db, foid, atPrev, δZ.Rev)
if err != nil {
return nil, err
}
if δ != nil {
δE := _ΔFileEpoch{
Rev: δZ.Rev,
oldRoot: δ.blktabOld,
newRoot: δ.blktabNew,
oldBlkSize: δ.blksizeOld,
newBlkSize: δ.blksizeNew,
oldZinblk: nil, // nothing was tracked
}
vδE = append(vδE, δE)
}
atPrev = δZ.Rev
}
return vδE, nil
}
// zfilediff returns direct difference for ZBigFile<foid> old..new . // zfilediff returns direct difference for ZBigFile<foid> old..new .
type _ΔZBigFile struct { type _ΔZBigFile struct {
...@@ -1006,7 +1216,6 @@ func diffF(ctx context.Context, a, b *ZBigFile) (δ *_ΔZBigFile, err error) { ...@@ -1006,7 +1216,6 @@ func diffF(ctx context.Context, a, b *ZBigFile) (δ *_ΔZBigFile, err error) {
return δ, nil return δ, nil
} }
// zgetFileOrNil returns ZBigFile corresponding to zconn.Get(oid) . // zgetFileOrNil returns ZBigFile corresponding to zconn.Get(oid) .
// if the file does not exist, (nil, ok) is returned. // if the file does not exist, (nil, ok) is returned.
func zgetFileOrNil(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (zfile *ZBigFile, err error) { func zgetFileOrNil(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (zfile *ZBigFile, err error) {
......
...@@ -242,12 +242,12 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -242,12 +242,12 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
// update vδf + co for t1 // update vδf + co for t1
vδf = append(vδf, &ΔFile{Rev: t1.At, Epoch: true}) vδf = append(vδf, &ΔFile{Rev: t1.At, Epoch: true})
vδE = append(vδE, _ΔFileEpoch{ vδE = append(vδE, _ΔFileEpoch{
Rev: t1.At, Rev: t1.At,
oldRoot: zodb.InvalidOid, oldRoot: zodb.InvalidOid,
newRoot: t.Root(), newRoot: t.Root(),
oldBlkSize: -1, oldBlkSize: -1,
newBlkSize: blksize, newBlkSize: blksize,
oldTrackSetZBlk: nil, oldZinblk: nil,
}) })
epochv = append(epochv, t1.At) epochv = append(epochv, t1.At)
for blk, zblk := range δt1 { for blk, zblk := range δt1 {
...@@ -305,7 +305,11 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -305,7 +305,11 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
for blk, zblk := range test.δblkTab { for blk, zblk := range test.δblkTab {
zprev, ok := blkTab[blk] zprev, ok := blkTab[blk]
if ok { if ok {
delete(Zinblk[zprev], blk) inblk := Zinblk[zprev]
inblk.Del(blk)
if len(inblk) == 0 {
delete(Zinblk, zprev)
}
} else { } else {
zprev = ø zprev = ø
} }
...@@ -423,12 +427,12 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -423,12 +427,12 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
δE.oldBlkSize = -1 δE.oldBlkSize = -1
δE.newBlkSize = blksize δE.newBlkSize = blksize
} }
oldTrackSetZBlk := map[zodb.Oid]setI64{} oldZinblk := map[zodb.Oid]setI64{}
for zblk, inblk := range ZinblkPrev { for zblk, inblk := range ZinblkPrev {
oid, _ := commit.XGetBlkByName(zblk) oid, _ := commit.XGetBlkByName(zblk)
oldTrackSetZBlk[oid] = inblk oldZinblk[oid] = inblk
} }
δE.oldTrackSetZBlk = oldTrackSetZBlk δE.oldZinblk = oldZinblk
vδE = append(vδE, δE) vδE = append(vδE, δE)
} }
...@@ -445,26 +449,60 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -445,26 +449,60 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
retrackAll() retrackAll()
} }
// verify δFtail.trackSetZBlk // verify byRoot
trackZinblk := map[string]setI64{} trackRfiles := map[zodb.Oid]setOid{}
for oid, zt := range δFtail.trackSetZBlk { for root, rt := range δFtail.byRoot {
zblki := commit.ZBlkTab[oid] trackRfiles[root] = rt.ftrackSet
for root, blocks := range zt.inroot { }
if root != t.Root() { filesOK := setOid{}
t.Errorf(".trackSetZBlk: zblk %s points to unexpected blktab %s", zblki.Name, t.Root()) if !delfile {
continue filesOK.Add(foid)
} }
RfilesOK := map[zodb.Oid]setOid{}
if len(filesOK) != 0 {
RfilesOK[t.Root()] = filesOK
}
if !reflect.DeepEqual(trackRfiles, RfilesOK) {
t.Errorf("Rfiles:\nhave: %v\nwant: %v", trackRfiles, RfilesOK)
}
inblk, ok := trackZinblk[zblki.Name] // verify Zinroot
if !ok { trackZinroot := map[string]setOid{}
inblk = setI64{} for zoid, inroot := range δFtail.ztrackInRoot {
trackZinblk[zblki.Name] = inblk zblki := commit.ZBlkTab[zoid]
trackZinroot[zblki.Name] = inroot.Clone() // XXX clone needed?
}
Zinroot := map[string]setOid{}
for zblk := range Zinblk {
inroot := setOid{}; inroot.Add(t.Root())
Zinroot[zblk] = inroot
}
if !reflect.DeepEqual(trackZinroot, Zinroot) {
t.Errorf("Zinroot:\nhave: %v\nwant: %v", trackZinroot, Zinroot)
}
// verify Zinblk
trackZinblk := map[string]setI64{}
switch {
case len(δFtail.byRoot) == 0:
// ok
case len(δFtail.byRoot) == 1:
rt, ok := δFtail.byRoot[t.Root()]
if !ok {
t.Errorf(".byRoot points to unexpected blktab")
} else {
for zoid, inblk := range rt.ztrackInBlk {
zblki := commit.ZBlkTab[zoid]
trackZinblk[zblki.Name] = inblk.Clone() // XXX clone needed?
} }
inblk.Update(blocks)
} }
default:
t.Errorf("len(.byRoot) != (0,1) ; byRoot: %v", δFtail.byRoot)
} }
if !reflect.DeepEqual(trackZinblk, Zinblk) { if !reflect.DeepEqual(trackZinblk, Zinblk) {
t.Errorf(".trackSetZBlk:\n~have: %v\n want: %v", trackZinblk, Zinblk) t.Errorf("Zinblk:\nhave: %v\nwant: %v", trackZinblk, Zinblk)
} }
// ForgetPast configured threshold // ForgetPast configured threshold
...@@ -485,16 +523,6 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -485,16 +523,6 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
vδE = vδE[icut:] vδE = vδE[icut:]
} }
// verify δFtail.filesByRoot
filesByRootOK := map[zodb.Oid]setOid{}
if !delfile {
__ := setOid{}; __.Add(foid)
filesByRootOK[t.Root()] = __
}
if !reflect.DeepEqual(δFtail.filesByRoot, filesByRootOK) {
t.Errorf("filesByRoot:\nhave: %v\nwant: %v", δFtail.filesByRoot, filesByRootOK)
}
// verify δftail.root // verify δftail.root
δftail := δFtail.byFile[foid] δftail := δFtail.byFile[foid]
rootOK := t.Root() rootOK := t.Root()
...@@ -523,7 +551,7 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -523,7 +551,7 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
hi := vδf[k].Rev hi := vδf[k].Rev
vδf_ok := vδf[j:k+1] // [j,k] vδf_ok := vδf[j:k+1] // [j,k]
vδf_ := δFtail.SliceByFileRev(zfile, lo, hi) vδf_, err := δFtail.SliceByFileRev(zfile, lo, hi); X(err)
if !reflect.DeepEqual(vδf_, vδf_ok) { if !reflect.DeepEqual(vδf_, vδf_ok) {
t.Errorf("slice (@%s,@%s]:\nhave: %v\nwant: %v", t.AtSymb(lo), t.AtSymb(hi), t.vδfstr(vδf_), t.vδfstr(vδf_ok)) t.Errorf("slice (@%s,@%s]:\nhave: %v\nwant: %v", t.AtSymb(lo), t.AtSymb(hi), t.vδfstr(vδf_), t.vδfstr(vδf_ok))
} }
...@@ -547,7 +575,7 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) { ...@@ -547,7 +575,7 @@ func testΔFtail(t_ *testing.T, testq chan ΔFTestEntry) {
at := vδf[j].Rev at := vδf[j].Rev
blkRev := blkRevAt[at] blkRev := blkRevAt[at]
for _, blk := range blkv { for _, blk := range blkv {
rev, exact := δFtail.BlkRevAt(ctx, zfile, blk, at) rev, exact, err := δFtail.BlkRevAt(ctx, zfile, blk, at); X(err)
revOK, ok := blkRev[blk] revOK, ok := blkRev[blk]
if !ok { if !ok {
k := len(epochv) - 1 k := len(epochv) - 1
...@@ -626,7 +654,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) { ...@@ -626,7 +654,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) {
// (at1, at4] -> changes to both 0 and 1, because they both are changed in the same bucket @at2 // (at1, at4] -> changes to both 0 and 1, because they both are changed in the same bucket @at2
lo := t1.At lo := t1.At
hi := t4.At hi := t4.At
vδf := δFtail.SliceByFileRev(zfile, lo, hi) vδf, err := δFtail.SliceByFileRev(zfile, lo, hi); X(err)
vδf_ok := []*ΔFile{ vδf_ok := []*ΔFile{
&ΔFile{Rev: t2.At, Blocks: b(0,1), Size: true}, &ΔFile{Rev: t2.At, Blocks: b(0,1), Size: true},
&ΔFile{Rev: t3.At, Blocks: b(0,1), Size: false}, &ΔFile{Rev: t3.At, Blocks: b(0,1), Size: false},
...@@ -639,7 +667,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) { ...@@ -639,7 +667,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) {
// (at2, at4] -> changes to only 0, because there is no change to 2 via blktab // (at2, at4] -> changes to only 0, because there is no change to 2 via blktab
lo = t2.At lo = t2.At
vδf = δFtail.SliceByFileRev(zfile, lo, hi) vδf, err = δFtail.SliceByFileRev(zfile, lo, hi); X(err)
vδf_ok = []*ΔFile{ vδf_ok = []*ΔFile{
&ΔFile{Rev: t3.At, Blocks: b(0), Size: false}, &ΔFile{Rev: t3.At, Blocks: b(0), Size: false},
} }
...@@ -649,7 +677,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) { ...@@ -649,7 +677,7 @@ func TestΔFtailSliceUntrackedUniform(t_ *testing.T) {
// (at3, at4] -> changes to only 0, ----/---- // (at3, at4] -> changes to only 0, ----/----
lo = t3.At lo = t3.At
vδf = δFtail.SliceByFileRev(zfile, lo, hi) vδf, err = δFtail.SliceByFileRev(zfile, lo, hi); X(err)
vδf_ok = []*ΔFile(nil) vδf_ok = []*ΔFile(nil)
if !reflect.DeepEqual(vδf, vδf_ok) { if !reflect.DeepEqual(vδf, vδf_ok) {
t.Errorf("slice (@%s,@%s]:\nhave: %v\nwant: %v", t.AtSymb(lo), t.AtSymb(hi), t.vδfstr(vδf), t.vδfstr(vδf_ok)) t.Errorf("slice (@%s,@%s]:\nhave: %v\nwant: %v", t.AtSymb(lo), t.AtSymb(hi), t.vδfstr(vδf), t.vδfstr(vδf_ok))
......
...@@ -1085,22 +1085,29 @@ func (f *BigFile) invalidateBlk(ctx context.Context, blk int64) (err error) { ...@@ -1085,22 +1085,29 @@ func (f *BigFile) invalidateBlk(ctx context.Context, blk int64) (err error) {
// //
// if we have the data - preserve it under @revX/bigfile/file[blk]. // if we have the data - preserve it under @revX/bigfile/file[blk].
if int64(len(blkdata)) == blksize { if int64(len(blkdata)) == blksize {
func() { err := func() error {
// store retrieved data back to OS cache for file @<rev>/file[blk] // store retrieved data back to OS cache for file @<rev>/file[blk]
δFtail := f.head.bfdir.δFtail δFtail := f.head.bfdir.δFtail
blkrev, _ := δFtail.BlkRevAt(ctx, f.zfile, blk, f.head.zconn.At()) blkrev, _, err := δFtail.BlkRevAt(ctx, f.zfile, blk, f.head.zconn.At())
if err != nil {
return err
}
frev, funlock, err := groot.lockRevFile(blkrev, f.zfile.POid()) frev, funlock, err := groot.lockRevFile(blkrev, f.zfile.POid())
if err != nil { if err != nil {
log.Errorf("BUG: %s: invalidate blk #%d: %s (ignoring, but reading @revX/bigfile will be slow)", f.path(), blk, err) return fmt.Errorf("BUG: %s", err)
return
} }
defer funlock() defer funlock()
st := fsconn.FileNotifyStoreCache(frev.Inode(), off, blkdata) st := fsconn.FileNotifyStoreCache(frev.Inode(), off, blkdata)
if st != fuse.OK { if st != fuse.OK {
log.Errorf("BUG: %s: invalidate blk #%d: %s: store cache: %s (ignoring, but reading @revX/bigfile will be slow)", f.path(), blk, frev.path(), st) return fmt.Errorf("BUG: %s: store cache: %s", frev.path(), st)
} }
return nil
}() }()
if err != nil {
log.Errorf("%s: invalidate blk #%d: %s (ignoring, but reading @revX/bigfile will be slow)", f.path(), blk, err)
}
} }
// invalidate file/head/data[blk] in OS file cache. // invalidate file/head/data[blk] in OS file cache.
...@@ -1566,7 +1573,11 @@ func (f *BigFile) readPinWatchers(ctx context.Context, blk int64, treepath []btr ...@@ -1566,7 +1573,11 @@ func (f *BigFile) readPinWatchers(ctx context.Context, blk int64, treepath []btr
// we'll relock atMu again and recheck blkrev vs w.at after. // we'll relock atMu again and recheck blkrev vs w.at after.
w.atMu.RUnlock() w.atMu.RUnlock()
blkrev, _ = δFtail.BlkRevAt(ctx, f.zfile, blk, f.head.zconn.At()) var err error
blkrev, _, err = δFtail.BlkRevAt(ctx, f.zfile, blk, f.head.zconn.At())
if err != nil {
panic(err) // XXX
}
blkrevRough = false blkrevRough = false
w.atMu.RLock() w.atMu.RLock()
...@@ -1582,8 +1593,11 @@ func (f *BigFile) readPinWatchers(ctx context.Context, blk int64, treepath []btr ...@@ -1582,8 +1593,11 @@ func (f *BigFile) readPinWatchers(ctx context.Context, blk int64, treepath []btr
// and most of them would be on different w.at - cache of the file will // and most of them would be on different w.at - cache of the file will
// be lost. Via pinning to particular block revision, we make sure the // be lost. Via pinning to particular block revision, we make sure the
// revision to pin is the same on all clients, and so file cache is shared. // revision to pin is the same on all clients, and so file cache is shared.
pinrev, _ := δFtail.BlkRevAt(ctx, w.file.zfile, blk, w.at) // XXX move into go? pinrev, _, err := δFtail.BlkRevAt(ctx, w.file.zfile, blk, w.at) // XXX move into go?
// XXX ^^^ w.file vs f ? // XXX ^^^ w.file vs f ?
if err != nil {
panic(err) // XXX
}
//fmt.Printf("S: read #%d: watch @%s: pin -> @%s\n", blk, w.at, pinrev) //fmt.Printf("S: read #%d: watch @%s: pin -> @%s\n", blk, w.at, pinrev)
wg.Go(func(ctx context.Context) error { wg.Go(func(ctx context.Context) error {
...@@ -1728,7 +1742,11 @@ func (wlink *WatchLink) setupWatch(ctx context.Context, foid zodb.Oid, at zodb.T ...@@ -1728,7 +1742,11 @@ func (wlink *WatchLink) setupWatch(ctx context.Context, foid zodb.Oid, at zodb.T
toPin := map[int64]zodb.Tid{} // blk -> @rev toPin := map[int64]zodb.Tid{} // blk -> @rev
δFtail := bfdir.δFtail δFtail := bfdir.δFtail
for _, δfile := range δFtail.SliceByFileRev(f.zfile, at, headAt) { // XXX locking δFtail vδf, err := δFtail.SliceByFileRev(f.zfile, at, headAt) // XXX locking δFtail
if err != nil {
panic(err) // XXX
}
for _, δfile := range vδf {
if δfile.Epoch { if δfile.Epoch {
// file epochs are currently forbidden (see watcher), so the only // file epochs are currently forbidden (see watcher), so the only
// case when we could see an epoch here is creation of // case when we could see an epoch here is creation of
...@@ -1764,7 +1782,10 @@ func (wlink *WatchLink) setupWatch(ctx context.Context, foid zodb.Oid, at zodb.T ...@@ -1764,7 +1782,10 @@ func (wlink *WatchLink) setupWatch(ctx context.Context, foid zodb.Oid, at zodb.T
continue continue
} }
toPin[blk], _ = δFtail.BlkRevAt(ctx, f.zfile, blk, at) // XXX err toPin[blk], _, err = δFtail.BlkRevAt(ctx, f.zfile, blk, at)
if err != nil {
panic(err) // XXX
}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment