github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/pool.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "errors" 24 "fmt" 25 26 "github.com/snapcore/snapd/asserts/internal" 27 ) 28 29 // A Grouping identifies opaquely a grouping of assertions. 30 // Pool uses it to label the interesection between a set of groups. 31 type Grouping string 32 33 // A pool helps holding and tracking a set of assertions and their 34 // prerequisites as they need to be updated or resolved. The 35 // assertions can be organized in groups. Failure can be tracked 36 // isolated to groups, conversely any error related to a single group 37 // alone will stop any work to resolve it. Independent assertions 38 // should not be grouped. Assertions and prerequisites that are part 39 // of more than one group are tracked properly only once. 40 // 41 // Typical usage involves specifying the initial assertions needing to 42 // be resolved or updated using AddUnresolved and AddToUpdate. At this 43 // point ToResolve can be called to get them organized in groupings 44 // ready for fetching. Fetched assertions can then be provided with 45 // Add or AddBatch. Because these can have prerequisites calling 46 // ToResolve and fetching needs to be repeated until ToResolve's 47 // result is empty. Between any two ToResolve invocations but after 48 // any Add or AddBatch AddUnresolved/AddToUpdate can also be used 49 // again. 50 // 51 // V 52 // | 53 // /-> AddUnresolved, AddToUpdate 54 // | | 55 // | V 56 // |------> ToResolve -> empty? done 57 // | | 58 // | V 59 // \ __________ Add 60 // 61 // 62 // If errors prevent from fulfilling assertions from a ToResolve, 63 // AddError and AddGroupingError can be used to report the errors so 64 // that they can be associated with groups. 65 // 66 // All the resolved assertions in a Pool from groups not in error can 67 // be committed to a destination database with CommitTo. 68 type Pool struct { 69 groundDB RODatabase 70 71 numbering map[string]uint16 72 groupings *internal.Groupings 73 74 unresolved map[string]*unresolvedRec 75 prerequisites map[string]*unresolvedRec 76 77 bs Backstore 78 unchanged map[string]bool 79 80 groups map[uint16]*groupRec 81 82 curPhase poolPhase 83 } 84 85 // NewPool creates a new Pool, groundDB is used to resolve trusted and 86 // predefined assertions and to provide the current revision for 87 // assertions to update and their prerequisites. Up to n groups can be 88 // used to organize the assertions. 89 func NewPool(groundDB RODatabase, n int) *Pool { 90 groupings, err := internal.NewGroupings(n) 91 if err != nil { 92 panic(fmt.Sprintf("NewPool: %v", err)) 93 } 94 return &Pool{ 95 groundDB: groundDB, 96 numbering: make(map[string]uint16), 97 groupings: groupings, 98 unresolved: make(map[string]*unresolvedRec), 99 prerequisites: make(map[string]*unresolvedRec), 100 bs: NewMemoryBackstore(), 101 unchanged: make(map[string]bool), 102 groups: make(map[uint16]*groupRec), 103 } 104 } 105 106 func (p *Pool) groupNum(group string) (gnum uint16, err error) { 107 if gnum, ok := p.numbering[group]; ok { 108 return gnum, nil 109 } 110 gnum = uint16(len(p.numbering)) 111 if err = p.groupings.WithinRange(gnum); err != nil { 112 return 0, err 113 } 114 p.numbering[group] = gnum 115 return gnum, nil 116 } 117 118 func (p *Pool) ensureGroup(group string) (gnum uint16, err error) { 119 gnum, err = p.groupNum(group) 120 if err != nil { 121 return 0, err 122 } 123 if gRec := p.groups[gnum]; gRec == nil { 124 p.groups[gnum] = &groupRec{ 125 name: group, 126 } 127 } 128 return gnum, nil 129 } 130 131 // Singleton returns a grouping containing only the given group. 132 // It is useful mainly for tests and to drive Add are AddBatch when the 133 // server is pushing assertions (instead of the usual pull scenario). 134 func (p *Pool) Singleton(group string) (Grouping, error) { 135 gnum, err := p.ensureGroup(group) 136 if err != nil { 137 return Grouping(""), nil 138 } 139 140 var grouping internal.Grouping 141 p.groupings.AddTo(&grouping, gnum) 142 return Grouping(p.groupings.Serialize(&grouping)), nil 143 } 144 145 // An unresolvedRec tracks a single unresolved assertion until it is 146 // resolved or there is an error doing so. The field 'grouping' will 147 // grow to contain all the groups requiring this assertion while it 148 // is unresolved. 149 type unresolvedRec struct { 150 at *AtRevision 151 grouping internal.Grouping 152 153 serializedLabel Grouping 154 155 err error 156 } 157 158 func (u *unresolvedRec) exportTo(r map[Grouping][]*AtRevision, gr *internal.Groupings) { 159 serLabel := Grouping(gr.Serialize(&u.grouping)) 160 // remember serialized label 161 u.serializedLabel = serLabel 162 r[serLabel] = append(r[serLabel], u.at) 163 } 164 165 func (u *unresolvedRec) merge(at *AtRevision, gnum uint16, gr *internal.Groupings) { 166 gr.AddTo(&u.grouping, gnum) 167 // assume we want to resolve/update wrt the highest revision 168 if at.Revision > u.at.Revision { 169 u.at.Revision = at.Revision 170 } 171 } 172 173 // A groupRec keeps track of all the resolved assertions in a group 174 // or whether the group should be considered in error (err != nil). 175 type groupRec struct { 176 name string 177 err error 178 resolved []Ref 179 } 180 181 func (gRec *groupRec) hasErr() bool { 182 return gRec.err != nil 183 } 184 185 func (gRec *groupRec) setErr(e error) { 186 if gRec.err == nil { 187 gRec.err = e 188 } 189 } 190 191 func (gRec *groupRec) markResolved(ref *Ref) (marked bool) { 192 if gRec.hasErr() { 193 return false 194 } 195 gRec.resolved = append(gRec.resolved, *ref) 196 return true 197 } 198 199 // markResolved marks the assertion referenced by ref as resolved 200 // in all the groups in grouping, except those already in error. 201 func (p *Pool) markResolved(grouping *internal.Grouping, resolved *Ref) (marked bool) { 202 p.groupings.Iter(grouping, func(gnum uint16) error { 203 if p.groups[gnum].markResolved(resolved) { 204 marked = true 205 } 206 return nil 207 }) 208 return marked 209 } 210 211 // setErr marks all the groups in grouping as in error with error err 212 // except those already in error. 213 func (p *Pool) setErr(grouping *internal.Grouping, err error) { 214 p.groupings.Iter(grouping, func(gnum uint16) error { 215 p.groups[gnum].setErr(err) 216 return nil 217 }) 218 } 219 220 func (p *Pool) isPredefined(ref *Ref) (bool, error) { 221 _, err := ref.Resolve(p.groundDB.FindPredefined) 222 if err == nil { 223 return true, nil 224 } 225 if !IsNotFound(err) { 226 return false, err 227 } 228 return false, nil 229 } 230 231 func (p *Pool) isResolved(ref *Ref) (bool, error) { 232 if p.unchanged[ref.Unique()] { 233 return true, nil 234 } 235 _, err := p.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat()) 236 if err == nil { 237 return true, nil 238 } 239 if !IsNotFound(err) { 240 return false, err 241 } 242 return false, nil 243 } 244 245 func (p *Pool) curRevision(ref *Ref) (int, error) { 246 a, err := ref.Resolve(p.groundDB.Find) 247 if err != nil && !IsNotFound(err) { 248 return 0, err 249 } 250 if err == nil { 251 return a.Revision(), nil 252 } 253 return RevisionNotKnown, nil 254 } 255 256 type poolPhase int 257 258 const ( 259 poolPhaseAddUnresolved = iota 260 poolPhaseAdd 261 ) 262 263 func (p *Pool) phase(ph poolPhase) error { 264 if ph == p.curPhase { 265 return nil 266 } 267 if ph == poolPhaseAdd { 268 return fmt.Errorf("internal error: cannot switch to Pool add phase without invoking ToResolve first") 269 } 270 // ph == poolPhaseAddUnresolved 271 p.unresolvedBookkeeping() 272 p.curPhase = poolPhaseAddUnresolved 273 return nil 274 } 275 276 // AddUnresolved adds the assertion referenced by unresolved 277 // AtRevision to the Pool as unresolved and as required by the given group. 278 // Usually unresolved.Revision will have been set to RevisionNotKnown. 279 func (p *Pool) AddUnresolved(unresolved *AtRevision, group string) error { 280 if err := p.phase(poolPhaseAddUnresolved); err != nil { 281 return err 282 } 283 gnum, err := p.ensureGroup(group) 284 if err != nil { 285 return err 286 } 287 u := *unresolved 288 ok, err := p.isPredefined(&u.Ref) 289 if err != nil { 290 return err 291 } 292 if ok { 293 // predefined, nothing to do 294 return nil 295 } 296 return p.addUnresolved(&u, gnum) 297 } 298 299 func (p *Pool) addUnresolved(unresolved *AtRevision, gnum uint16) error { 300 ok, err := p.isResolved(&unresolved.Ref) 301 if err != nil { 302 return err 303 } 304 if ok { 305 // We assume that either the resolving of 306 // prerequisites for the already resolved assertion in 307 // progress has succeeded or will. If that's not the 308 // case we will fail at CommitTo time. We could 309 // instead recurse into its prerequisites again but the 310 // complexity isn't clearly worth it. 311 // See TestParallelPartialResolutionFailure 312 // Mark this as resolved in the group. 313 p.groups[gnum].markResolved(&unresolved.Ref) 314 return nil 315 } 316 uniq := unresolved.Ref.Unique() 317 var u *unresolvedRec 318 if u = p.unresolved[uniq]; u == nil { 319 u = &unresolvedRec{ 320 at: unresolved, 321 } 322 p.unresolved[uniq] = u 323 } 324 u.merge(unresolved, gnum, p.groupings) 325 return nil 326 } 327 328 // ToResolve returns all the currently unresolved assertions in the 329 // Pool, organized in opaque groupings based on which set of groups 330 // requires each of them. 331 // At the next ToResolve any unresolved assertion with not known 332 // revision that was not added via Add or AddBatch will result in all 333 // groups requiring it being in error with ErrUnresolved. 334 // Conversely, the remaining unresolved assertions originally added 335 // via AddToUpdate will be assumed to still be at their current 336 // revisions. 337 func (p *Pool) ToResolve() (map[Grouping][]*AtRevision, error) { 338 if p.curPhase == poolPhaseAdd { 339 p.unresolvedBookkeeping() 340 } else { 341 p.curPhase = poolPhaseAdd 342 } 343 r := make(map[Grouping][]*AtRevision) 344 for _, u := range p.unresolved { 345 if u.at.Revision == RevisionNotKnown { 346 rev, err := p.curRevision(&u.at.Ref) 347 if err != nil { 348 return nil, err 349 } 350 if rev != RevisionNotKnown { 351 u.at.Revision = rev 352 } 353 } 354 u.exportTo(r, p.groupings) 355 } 356 return r, nil 357 } 358 359 func (p *Pool) addPrerequisite(pref *Ref, g *internal.Grouping) error { 360 uniq := pref.Unique() 361 u := p.unresolved[uniq] 362 at := &AtRevision{ 363 Ref: *pref, 364 Revision: RevisionNotKnown, 365 } 366 if u == nil { 367 u = p.prerequisites[uniq] 368 } 369 if u != nil { 370 gr := p.groupings 371 gr.Iter(g, func(gnum uint16) error { 372 u.merge(at, gnum, gr) 373 return nil 374 }) 375 return nil 376 } 377 ok, err := p.isPredefined(pref) 378 if err != nil { 379 return err 380 } 381 if ok { 382 // nothing to do 383 return nil 384 } 385 ok, err = p.isResolved(pref) 386 if err != nil { 387 return err 388 } 389 if ok { 390 // nothing to do, it is anyway implied 391 return nil 392 } 393 p.prerequisites[uniq] = &unresolvedRec{ 394 at: at, 395 grouping: g.Copy(), 396 } 397 return nil 398 } 399 400 func (p *Pool) add(a Assertion, g *internal.Grouping) error { 401 if err := p.bs.Put(a.Type(), a); err != nil { 402 if revErr, ok := err.(*RevisionError); ok { 403 if revErr.Current >= a.Revision() { 404 // we already got something more recent 405 return nil 406 } 407 } 408 409 return err 410 } 411 for _, pref := range a.Prerequisites() { 412 if err := p.addPrerequisite(pref, g); err != nil { 413 return err 414 } 415 } 416 keyRef := &Ref{ 417 Type: AccountKeyType, 418 PrimaryKey: []string{a.SignKeyID()}, 419 } 420 if err := p.addPrerequisite(keyRef, g); err != nil { 421 return err 422 } 423 return nil 424 } 425 426 func (p *Pool) resolveWith(unresolved map[string]*unresolvedRec, uniq string, u *unresolvedRec, a Assertion, extrag *internal.Grouping) (ok bool, err error) { 427 if a.Revision() > u.at.Revision { 428 if extrag == nil { 429 extrag = &u.grouping 430 } else { 431 p.groupings.Iter(&u.grouping, func(gnum uint16) error { 432 p.groupings.AddTo(extrag, gnum) 433 return nil 434 }) 435 } 436 ref := a.Ref() 437 if p.markResolved(extrag, ref) { 438 // remove from tracking - 439 // remove u from unresolved only if the assertion 440 // is added to the resolved backstore; 441 // otherwise it might resurface as unresolved; 442 // it will be ultimately handled in 443 // unresolvedBookkeeping if it stays around 444 delete(unresolved, uniq) 445 if err := p.add(a, extrag); err != nil { 446 p.setErr(extrag, err) 447 return false, err 448 } 449 } 450 } 451 return true, nil 452 } 453 454 // Add adds the given assertion associated with the given grouping to the 455 // Pool as resolved in all the groups requiring it. 456 // Any not already resolved prerequisites of the assertion will 457 // be implicitly added as unresolved and required by all of those groups. 458 // The grouping will usually have been associated with the assertion 459 // in a ToResolve's result. Otherwise the union of all groups 460 // requiring the assertion plus the groups in grouping will be considered. 461 // The latter is mostly relevant in scenarios where the server is pushing 462 // assertions. 463 // If an error is returned it refers to an immediate or local error. 464 // Errors related to the assertions are associated with the relevant groups 465 // and can be retrieved with Err, in which case ok is set to false. 466 func (p *Pool) Add(a Assertion, grouping Grouping) (ok bool, err error) { 467 if err := p.phase(poolPhaseAdd); err != nil { 468 return false, err 469 } 470 471 if !a.SupportedFormat() { 472 e := &UnsupportedFormatError{Ref: a.Ref(), Format: a.Format()} 473 p.AddGroupingError(e, grouping) 474 return false, nil 475 } 476 477 return p.addToGrouping(a, grouping, p.groupings.Deserialize) 478 } 479 480 func (p *Pool) addToGrouping(a Assertion, grouping Grouping, deserializeGrouping func(string) (*internal.Grouping, error)) (ok bool, err error) { 481 uniq := a.Ref().Unique() 482 var u *unresolvedRec 483 var extrag *internal.Grouping 484 var unresolved map[string]*unresolvedRec 485 if u = p.unresolved[uniq]; u != nil { 486 unresolved = p.unresolved 487 } else if u = p.prerequisites[uniq]; u != nil { 488 unresolved = p.prerequisites 489 } else { 490 ok, err := p.isPredefined(a.Ref()) 491 if err != nil { 492 return false, err 493 } 494 if ok { 495 // nothing to do 496 return true, nil 497 } 498 // a is not tracked as unresolved in any way so far, 499 // this is an atypical scenario where something gets 500 // pushed but we still want to add it to the resolved 501 // lists of the relevant groups; in case it is 502 // actually already resolved most of resolveWith below will 503 // be a nop 504 u = &unresolvedRec{ 505 at: a.At(), 506 } 507 u.at.Revision = RevisionNotKnown 508 } 509 510 if u.serializedLabel != grouping { 511 var err error 512 extrag, err = deserializeGrouping(string(grouping)) 513 if err != nil { 514 return false, err 515 } 516 } 517 518 return p.resolveWith(unresolved, uniq, u, a, extrag) 519 } 520 521 // AddBatch adds all the assertions in the Batch to the Pool, 522 // associated with the given grouping and as resolved in all the 523 // groups requiring them. It is equivalent to using Add on each of them. 524 // If an error is returned it refers to an immediate or local error. 525 // Errors related to the assertions are associated with the relevant groups 526 // and can be retrieved with Err, in which case ok set to false. 527 func (p *Pool) AddBatch(b *Batch, grouping Grouping) (ok bool, err error) { 528 if err := p.phase(poolPhaseAdd); err != nil { 529 return false, err 530 } 531 532 // b dealt with unsupported formats already 533 534 // deserialize grouping if needed only once 535 var cachedGrouping *internal.Grouping 536 deser := func(_ string) (*internal.Grouping, error) { 537 if cachedGrouping != nil { 538 // do a copy as addToGrouping and resolveWith 539 // might add to their input 540 g := cachedGrouping.Copy() 541 return &g, nil 542 } 543 var err error 544 cachedGrouping, err = p.groupings.Deserialize(string(grouping)) 545 return cachedGrouping, err 546 } 547 548 inError := false 549 for _, a := range b.added { 550 ok, err := p.addToGrouping(a, grouping, deser) 551 if err != nil { 552 return false, err 553 } 554 if !ok { 555 inError = true 556 } 557 } 558 559 return !inError, nil 560 } 561 562 var ( 563 ErrUnresolved = errors.New("unresolved assertion") 564 ErrUnknownPoolGroup = errors.New("unknown pool group") 565 ) 566 567 // unresolvedBookkeeping processes any left over unresolved assertions 568 // since the last ToResolve invocation and intervening calls to Add/AddBatch, 569 // * they were either marked as in error which will be propagated 570 // to all groups requiring them 571 // * simply unresolved, which will be propagated to groups requiring them 572 // as ErrUnresolved 573 // * unchanged (update case) 574 // unresolvedBookkeeping will also promote any recorded prerequisites 575 // into actively unresolved, as long as not all the groups requiring them 576 // are in error. 577 func (p *Pool) unresolvedBookkeeping() { 578 // any left over unresolved are either: 579 // * in error 580 // * unchanged 581 // * or unresolved 582 for uniq, u := range p.unresolved { 583 e := u.err 584 if e == nil { 585 if u.at.Revision == RevisionNotKnown { 586 e = ErrUnresolved 587 } else { 588 // unchanged 589 p.unchanged[uniq] = true 590 } 591 } 592 if e != nil { 593 p.setErr(&u.grouping, e) 594 } 595 delete(p.unresolved, uniq) 596 } 597 598 // prerequisites will become the new unresolved but drop them 599 // if all their groups are in error 600 for uniq, prereq := range p.prerequisites { 601 useful := false 602 p.groupings.Iter(&prereq.grouping, func(gnum uint16) error { 603 if !p.groups[gnum].hasErr() { 604 useful = true 605 } 606 return nil 607 }) 608 if !useful { 609 delete(p.prerequisites, uniq) 610 continue 611 } 612 } 613 614 // prerequisites become the new unresolved, the emptied 615 // unresolved is used for prerequisites in the next round 616 p.unresolved, p.prerequisites = p.prerequisites, p.unresolved 617 } 618 619 // Err returns the error for group if group is in error, nil otherwise. 620 func (p *Pool) Err(group string) error { 621 gnum, err := p.groupNum(group) 622 if err != nil { 623 return err 624 } 625 gRec := p.groups[gnum] 626 if gRec == nil { 627 return ErrUnknownPoolGroup 628 } 629 return gRec.err 630 } 631 632 // Errors returns a mapping of groups in error to their errors. 633 func (p *Pool) Errors() map[string]error { 634 res := make(map[string]error) 635 for _, gRec := range p.groups { 636 if err := gRec.err; err != nil { 637 res[gRec.name] = err 638 } 639 } 640 if len(res) == 0 { 641 return nil 642 } 643 return res 644 } 645 646 // AddError associates error e with the unresolved assertion. 647 // The error will be propagated to all the affected groups at 648 // the next ToResolve. 649 func (p *Pool) AddError(e error, ref *Ref) error { 650 if err := p.phase(poolPhaseAdd); err != nil { 651 return err 652 } 653 uniq := ref.Unique() 654 if u := p.unresolved[uniq]; u != nil && u.err == nil { 655 u.err = e 656 } 657 return nil 658 } 659 660 // AddGroupingError puts all the groups of grouping in error, with error e. 661 func (p *Pool) AddGroupingError(e error, grouping Grouping) error { 662 if err := p.phase(poolPhaseAdd); err != nil { 663 return err 664 } 665 666 g, err := p.groupings.Deserialize(string(grouping)) 667 if err != nil { 668 return err 669 } 670 671 p.setErr(g, e) 672 return nil 673 } 674 675 // AddToUpdate adds the assertion referenced by toUpdate and all its 676 // prerequisites to the Pool as unresolved and as required by the 677 // given group. It is assumed that the assertion is currently in the 678 // ground database of the Pool, otherwise this will error. 679 // The current revisions of the assertion and its prerequisites will 680 // be recorded and only higher revisions will then resolve them, 681 // otherwise if ultimately unresolved they will be assumed to still be 682 // at their current ones. 683 func (p *Pool) AddToUpdate(toUpdate *Ref, group string) error { 684 if err := p.phase(poolPhaseAddUnresolved); err != nil { 685 return err 686 } 687 gnum, err := p.ensureGroup(group) 688 if err != nil { 689 return err 690 } 691 retrieve := func(ref *Ref) (Assertion, error) { 692 return ref.Resolve(p.groundDB.Find) 693 } 694 add := func(a Assertion) error { 695 return p.addUnresolved(a.At(), gnum) 696 } 697 f := NewFetcher(p.groundDB, retrieve, add) 698 if err := f.Fetch(toUpdate); err != nil { 699 return err 700 } 701 return nil 702 } 703 704 // CommitTo adds the assertions from groups without errors to the 705 // given assertion database. Commit errors can be retrieved via Err 706 // per group. An error is returned directly only if CommitTo is called 707 // with possible pending unresolved assertions. 708 func (p *Pool) CommitTo(db *Database) error { 709 if p.curPhase == poolPhaseAddUnresolved { 710 return fmt.Errorf("internal error: cannot commit Pool during add unresolved phase") 711 } 712 p.unresolvedBookkeeping() 713 714 retrieve := func(ref *Ref) (Assertion, error) { 715 a, err := p.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat()) 716 if IsNotFound(err) { 717 // fallback to pre-existing assertions 718 a, err = ref.Resolve(db.Find) 719 } 720 if err != nil { 721 return nil, resolveError("cannot resolve prerequisite assertion: %s", ref, err) 722 } 723 return a, nil 724 } 725 save := func(a Assertion) error { 726 err := db.Add(a) 727 if IsUnaccceptedUpdate(err) { 728 // unsupported format case is handled before. 729 // be idempotent, db has already the same or 730 // newer. 731 return nil 732 } 733 return err 734 } 735 736 NextGroup: 737 for _, gRec := range p.groups { 738 if gRec.hasErr() { 739 // already in error, ignore 740 continue 741 } 742 // TODO: try to reuse fetcher 743 f := NewFetcher(db, retrieve, save) 744 for i := range gRec.resolved { 745 if err := f.Fetch(&gRec.resolved[i]); err != nil { 746 gRec.setErr(err) 747 continue NextGroup 748 } 749 } 750 } 751 752 return nil 753 } 754 755 // ClearGroups clears the pool in terms of information associated with groups 756 // while preserving information about already resolved or unchanged assertions. 757 // It is useful for reusing a pool once the maximum of usable groups 758 // that was set with NewPool has been exhausted. Group errors must be 759 // queried before calling it otherwise they are lost. It is an error 760 // to call it when there are still pending unresolved assertions in 761 // the pool. 762 func (p *Pool) ClearGroups() error { 763 if len(p.unresolved) != 0 || len(p.prerequisites) != 0 { 764 return fmt.Errorf("internal error: trying to clear groups of asserts.Pool with pending unresolved or prerequisites") 765 } 766 767 p.numbering = make(map[string]uint16) 768 // use a fresh Groupings as well so that max group tracking starts 769 // from scratch. 770 // NewGroupings cannot fail on a value accepted by it previously 771 p.groupings, _ = internal.NewGroupings(p.groupings.N()) 772 p.groups = make(map[uint16]*groupRec) 773 p.curPhase = poolPhaseAdd 774 return nil 775 }