···1515 "github.com/bluesky-social/indigo/events"
1616 lexutil "github.com/bluesky-social/indigo/lex/util"
1717 "github.com/bluesky-social/indigo/models"
1818- "github.com/bluesky-social/indigo/mst"
1918 "github.com/bluesky-social/indigo/repo"
2019 "github.com/ipfs/go-cid"
2120 "github.com/ipfs/go-datastore"
2221 blockstore "github.com/ipfs/go-ipfs-blockstore"
2222+ ipld "github.com/ipfs/go-ipld-format"
2323 logging "github.com/ipfs/go-log/v2"
2424 "github.com/ipld/go-car"
2525 cbg "github.com/whyrusleeping/cbor-gen"
···64646565type RepoEvent struct {
6666 User uint
6767- OldRoot cid.Cid
6767+ OldRoot *cid.Cid
6868 NewRoot cid.Cid
6969 RepoSlice []byte
7070 PDS uint
···211211 if rm.events != nil {
212212 rm.events(ctx, &RepoEvent{
213213 User: user,
214214- OldRoot: head,
214214+ OldRoot: &head,
215215 NewRoot: nroot,
216216 Ops: []RepoOp{{
217217 Kind: EvtKindCreateRecord,
···273273 if rm.events != nil {
274274 rm.events(ctx, &RepoEvent{
275275 User: user,
276276- OldRoot: head,
276276+ OldRoot: &head,
277277 NewRoot: nroot,
278278 Ops: []RepoOp{{
279279 Kind: EvtKindUpdateRecord,
···334334 if rm.events != nil {
335335 rm.events(ctx, &RepoEvent{
336336 User: user,
337337- OldRoot: head,
337337+ OldRoot: &head,
338338 NewRoot: nroot,
339339 Ops: []RepoOp{{
340340 Kind: EvtKindDeleteRecord,
···500500func (rm *RepoManager) HandleExternalUserEvent(ctx context.Context, pdsid uint, uid uint, prev *cid.Cid, ops []*events.RepoOp, carslice []byte) error {
501501 ctx, span := otel.Tracer("repoman").Start(ctx, "HandleExternalUserEvent")
502502 defer span.End()
503503+504504+ log.Infof("HandleExternalUserEvent: %d %d %s", pdsid, uid, prev)
503505504506 unlock := rm.lockUser(ctx, uid)
505507 defer unlock()
···578580579581 if rm.events != nil {
580582 rm.events(ctx, &RepoEvent{
581581- User: uid,
582582- //OldRoot: head,
583583+ User: uid,
584584+ OldRoot: prev,
583585 NewRoot: root,
584586 Ops: evtops,
585587 RepoSlice: rslice,
···691693 if rm.events != nil {
692694 rm.events(ctx, &RepoEvent{
693695 User: user,
694694- OldRoot: head,
696696+ OldRoot: &head,
695697 NewRoot: nroot,
696698 RepoSlice: rslice,
697699 Ops: ops,
···701703 return nil
702704}
703705704704-func (rm *RepoManager) ImportNewRepo(ctx context.Context, user uint, r io.Reader) error {
706706+func (rm *RepoManager) ImportNewRepo(ctx context.Context, user uint, r io.Reader, oldest cid.Cid) error {
705707 ctx, span := otel.Tracer("repoman").Start(ctx, "ImportNewRepo")
706708 defer span.End()
707709708710 unlock := rm.lockUser(ctx, user)
709711 defer unlock()
710712711711- _, err := rm.getUserRepoHead(ctx, user)
712712- if err == nil {
713713- // i predict this will get awkward
714714- return fmt.Errorf("cannot import new repo for user we already know about")
713713+ head, err := rm.getUserRepoHead(ctx, user)
714714+ if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
715715+ return err
715716 }
716717717717- if !errors.Is(err, gorm.ErrRecordNotFound) {
718718- return err
718718+ if head != oldest {
719719+ // TODO: we could probably just deal with this
720720+ return fmt.Errorf("ImportNewRepo called with incorrect base")
719721 }
720722721721- err = rm.processNewRepo(ctx, user, r, func(ctx context.Context, old, nu cid.Cid, slice []byte, bs blockstore.Blockstore) error {
723723+ err = rm.processNewRepo(ctx, user, r, head, func(ctx context.Context, old, nu cid.Cid, slice []byte, bs blockstore.Blockstore) error {
724724+ r, err := repo.OpenRepo(ctx, bs, nu)
725725+ if err != nil {
726726+ return fmt.Errorf("opening new repo: %w", err)
727727+ }
722728723723- diffops, err := mst.DiffTrees(ctx, bs, old, nu)
729729+ diffops, err := r.DiffSince(ctx, old)
724730 if err != nil {
725731 return fmt.Errorf("diff trees: %w", err)
726732 }
···769775 }
770776 }
771777778778+ if err := rm.updateUserRepoHead(ctx, user, nu); err != nil {
779779+ return err
780780+ }
781781+772782 if rm.events != nil {
773783 rm.events(ctx, &RepoEvent{
774784 User: user,
775775- OldRoot: old,
785785+ OldRoot: &old,
776786 NewRoot: nu,
777787 RepoSlice: slice,
778788 Ops: ops,
···782792 return nil
783793 })
784794 if err != nil {
785785- return err
795795+ return fmt.Errorf("process new repo: %w:", err)
786796 }
787797788798 return nil
789799}
790800791791-func (rm *RepoManager) processNewRepo(ctx context.Context, user uint, r io.Reader, cb func(ctx context.Context, old, nu cid.Cid, slice []byte, bs blockstore.Blockstore) error) error {
801801+func (rm *RepoManager) processNewRepo(ctx context.Context, user uint, r io.Reader, until cid.Cid, cb func(ctx context.Context, old, nu cid.Cid, slice []byte, bs blockstore.Blockstore) error) error {
792802 ctx, span := otel.Tracer("repoman").Start(ctx, "ImportNewRepo")
793803 defer span.End()
794804···820830 head := &carr.Header.Roots[0]
821831822832 var commits []cid.Cid
823823- for head != nil {
833833+ for head != nil && *head != until {
824834825835 commits = append(commits, *head)
826836 rep, err := repo.OpenRepo(ctx, membs, *head)
827837 if err != nil {
828828- return err
838838+ return fmt.Errorf("opening repo for backwalk: %w", err)
829839 }
830840831841 prev, err := rep.PrevCommit(ctx)
832842 if err != nil {
833833- return err
843843+ return fmt.Errorf("prevCommit: %w", err)
834844 }
835845836846 head = prev
···840850841851 seen := make(map[cid.Cid]bool)
842852843843- var prev cid.Cid
853853+ if until.Defined() {
854854+ seen[until] = true
855855+ }
856856+857857+ prev := until
844858 for i := len(commits) - 1; i >= 0; i-- {
845859 root := commits[i]
846846- cids, err := walkTree(ctx, seen, root, membs)
860860+ cids, err := walkTree(ctx, seen, root, membs, true)
847861 if err != nil {
848862 return fmt.Errorf("walkTree: %w", err)
849863 }
850864851865 ds, err := rm.cs.NewDeltaSession(ctx, user, &prev)
852866 if err != nil {
853853- return fmt.Errorf("opening delta session: %w", err)
867867+ return fmt.Errorf("opening delta session (%d / %d): %w", i, len(commits)-1, err)
854868 }
855869856870 for _, c := range cids {
857871 blk, err := membs.Get(ctx, c)
858872 if err != nil {
859859- return err
873873+ return fmt.Errorf("copying walked cids to carstore: %w", err)
860874 }
861875862876 if err := ds.Put(ctx, blk); err != nil {
···870884 }
871885872886 if err := cb(ctx, prev, root, carslice, membs); err != nil {
873873- return err
887887+ return fmt.Errorf("cb errored: %w", err)
874888 }
875889876890 prev = root
···879893 return nil
880894}
881895882882-func walkTree(ctx context.Context, skip map[cid.Cid]bool, root cid.Cid, bs blockstore.Blockstore) ([]cid.Cid, error) {
883883-896896+// walkTree returns all cids linked recursively by the root, skipping any cids
897897+// in the 'skip' map, and not erroring on 'not found' if prevMissing is set
898898+func walkTree(ctx context.Context, skip map[cid.Cid]bool, root cid.Cid, bs blockstore.Blockstore, prevMissing bool) ([]cid.Cid, error) {
884899 // TODO: what if someone puts non-cbor links in their repo?
885900 if root.Prefix().Codec != cid.DagCBOR {
886901 return nil, fmt.Errorf("can only handle dag-cbor objects in repos (%s is %d)", root, root.Prefix().Codec)
···891906 return nil, err
892907 }
893908894894- out := []cid.Cid{root}
895895- skip[root] = true
896896-909909+ var links []cid.Cid
897910 if err := cbg.ScanForLinks(bytes.NewReader(blk.RawData()), func(c cid.Cid) {
898911 if skip[c] {
899912 return
900913 }
901914902902- out = append(out, c)
915915+ links = append(links, c)
903916 skip[c] = true
904917905918 return
···907920 return nil, err
908921 }
909922923923+ out := []cid.Cid{root}
924924+ skip[root] = true
925925+910926 // TODO: should do this non-recursive since i expect these may get deep
911911- for _, c := range out[1:] {
912912- sub, err := walkTree(ctx, skip, c, bs)
927927+ for _, c := range links {
928928+ sub, err := walkTree(ctx, skip, c, bs, prevMissing)
913929 if err != nil {
914914- return nil, err
930930+ if prevMissing && !ipld.IsNotFound(err) {
931931+ return nil, err
932932+ }
915933 }
916934917935 out = append(out, sub...)
+64-8
testing/integ_test.go
···8080 }
8181}
82828383-func socialSim(t *testing.T, users []*testUser) []*atproto.RepoStrongRef {
8383+func socialSim(t *testing.T, users []*testUser, postiter, likeiter int) []*atproto.RepoStrongRef {
8484 var posts []*atproto.RepoStrongRef
8585- for i := 0; i < 10; i++ {
8585+ for i := 0; i < postiter; i++ {
8686 for _, u := range users {
8787 posts = append(posts, u.Post(t, makeRandomPost()))
8888 }
8989 }
90909191- for i := 0; i < 5; i++ {
9191+ for i := 0; i < likeiter; i++ {
9292 for _, u := range users {
9393 u.Like(t, posts[rand.Intn(len(posts))])
9494 }
···9898}
9999100100func TestBGSMultiPDS(t *testing.T) {
101101- t.Skip()
102101 assert := assert.New(t)
103102 _ = assert
104103 didr := testPLC(t)
···120119 }
121120122121 randomFollows(t, users)
123123- socialSim(t, users)
122122+ socialSim(t, users, 10, 10)
124123125124 var users2 []*testUser
126125 for i := 0; i < 5; i++ {
···128127 }
129128130129 randomFollows(t, users2)
131131- p2posts := socialSim(t, users2)
130130+ p2posts := socialSim(t, users2, 10, 10)
132131133132 randomFollows(t, append(users, users2...))
134133···136135137136 // now if we make posts on pds 2, the bgs will not hear about those new posts
138137139139- p2posts2 := socialSim(t, users2)
138138+ p2posts2 := socialSim(t, users2, 10, 10)
140139141140 time.Sleep(time.Second)
142141···144143 time.Sleep(time.Millisecond * 50)
145144146145 // Now, the bgs will discover a gap, and have to catch up somehow
147147- socialSim(t, users2)
146146+ socialSim(t, users2, 1, 0)
147147+148148+ time.Sleep(time.Second)
148149149150 // we expect the bgs to learn about posts that it didnt directly see from
150151 // repos its already partially scraped, as long as its seen *something* after the missing post
···154155 if err != nil {
155156 t.Fatal(err)
156157 }
158158+}
159159+160160+func TestBGSMultiGap(t *testing.T) {
161161+ assert := assert.New(t)
162162+ _ = assert
163163+ didr := testPLC(t)
164164+ p1 := mustSetupPDS(t, "localhost:5195", ".pdsuno", didr)
165165+ p1.Run(t)
166166+167167+ p2 := mustSetupPDS(t, "localhost:5196", ".pdsdos", didr)
168168+ p2.Run(t)
169169+170170+ b1 := mustSetupBGS(t, "localhost:8291", didr)
171171+ b1.Run(t)
172172+173173+ p1.RequestScraping(t, b1)
174174+ time.Sleep(time.Millisecond * 50)
175175+176176+ users := []*testUser{p1.MustNewUser(t, usernames[0]+".pdsuno")}
157177178178+ socialSim(t, users, 10, 0)
179179+180180+ users2 := []*testUser{p2.MustNewUser(t, usernames[1]+".pdsdos")}
181181+182182+ p2posts := socialSim(t, users2, 10, 0)
183183+184184+ users[0].Reply(t, p2posts[0], p2posts[0], "what a wonderful life")
185185+ time.Sleep(time.Millisecond * 50)
186186+187187+ ctx := context.Background()
188188+ _, err := b1.bgs.Index.GetPost(ctx, p2posts[3].Uri)
189189+ if err != nil {
190190+ t.Fatal(err)
191191+ }
192192+193193+ // now if we make posts on pds 2, the bgs will not hear about those new posts
194194+195195+ p2posts2 := socialSim(t, users2, 10, 0)
196196+197197+ time.Sleep(time.Second)
198198+199199+ p2.RequestScraping(t, b1)
200200+ time.Sleep(time.Millisecond * 50)
201201+202202+ // Now, the bgs will discover a gap, and have to catch up somehow
203203+ socialSim(t, users2, 1, 0)
204204+205205+ time.Sleep(time.Second)
206206+207207+ // we expect the bgs to learn about posts that it didnt directly see from
208208+ // repos its already partially scraped, as long as its seen *something* after the missing post
209209+ // this is the 'catchup' process
210210+ _, err = b1.bgs.Index.GetPost(ctx, p2posts2[4].Uri)
211211+ if err != nil {
212212+ t.Fatal(err)
213213+ }
158214}