Skip to content

Commit

Permalink
Merge pull request #3169 from corhere/idm-bitset
Browse files Browse the repository at this point in the history
internal/idm: drop libnetwork dependency
  • Loading branch information
dperny committed Feb 26, 2024
2 parents 4b261ae + a1f8cff commit 2e06ed0
Show file tree
Hide file tree
Showing 21 changed files with 1,917 additions and 163 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -6,6 +6,7 @@ require (
code.cloudfoundry.org/clock v1.1.0
github.com/Microsoft/go-winio v0.6.1
github.com/akutz/memconn v0.1.0
github.com/bits-and-blooms/bitset v1.13.0
github.com/cloudflare/cfssl v1.6.4
github.com/container-storage-interface/spec v1.2.0
github.com/distribution/reference v0.5.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -39,6 +39,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
Expand Down
75 changes: 45 additions & 30 deletions internal/idm/idm.go
Expand Up @@ -5,65 +5,80 @@ import (
"errors"
"fmt"

"github.com/docker/docker/libnetwork/bitmap"
"github.com/bits-and-blooms/bitset"
)

var (
// ErrNoBitAvailable is returned when no more bits are available to set
ErrNoBitAvailable = errors.New("no bit available")
// ErrBitAllocated is returned when the specific bit requested is already set
ErrBitAllocated = errors.New("requested bit is already allocated")
)

// IDM manages the reservation/release of numerical ids from a contiguous set.
//
// An IDM instance is not safe for concurrent use.
type IDM struct {
start uint64
end uint64
handle *bitmap.Bitmap
start, end uint
set *bitset.BitSet
next uint // index of the bit to start searching for the next serial allocation from (not offset by start)
}

// New returns an instance of id manager for a [start,end] set of numerical ids.
func New(start, end uint64) (*IDM, error) {
func New(start, end uint) (*IDM, error) {
if end <= start {
return nil, fmt.Errorf("invalid set range: [%d, %d]", start, end)
}

return &IDM{start: start, end: end, handle: bitmap.New(1 + end - start)}, nil
return &IDM{start: start, end: end, set: bitset.New(1 + end - start)}, nil
}

// GetID returns the first available id in the set.
func (i *IDM) GetID(serial bool) (uint64, error) {
if i.handle == nil {
func (i *IDM) GetID(serial bool) (uint, error) {
if i.set == nil {
return 0, errors.New("ID set is not initialized")
}
ordinal, err := i.handle.SetAny(serial)
return i.start + ordinal, err
var (
ordinal uint
ok bool
)
if serial && i.next != 0 {
ordinal, ok = i.set.NextClear(i.next)
if ok {
goto found
}
}
ordinal, ok = i.set.NextClear(0)
if !ok {
return 0, ErrNoBitAvailable
}

found:
i.set.Set(ordinal)
i.next = ordinal + 1
if i.next > i.end-i.start {
i.next = 0
}
return i.start + ordinal, nil
}

// GetSpecificID tries to reserve the specified id.
func (i *IDM) GetSpecificID(id uint64) error {
if i.handle == nil {
func (i *IDM) GetSpecificID(id uint) error {
if i.set == nil {
return errors.New("ID set is not initialized")
}

if id < i.start || id > i.end {
return errors.New("requested id does not belong to the set")
}

return i.handle.Set(id - i.start)
}

// GetIDInRange returns the first available id in the set within a [start,end] range.
func (i *IDM) GetIDInRange(start, end uint64, serial bool) (uint64, error) {
if i.handle == nil {
return 0, errors.New("ID set is not initialized")
}

if start < i.start || end > i.end {
return 0, errors.New("requested range does not belong to the set")
if i.set.Test(id - i.start) {
return ErrBitAllocated
}

ordinal, err := i.handle.SetAnyInRange(start-i.start, end-i.start, serial)

return i.start + ordinal, err
i.set.Set(id - i.start)
return nil
}

// Release releases the specified id.
func (i *IDM) Release(id uint64) {
i.handle.Unset(id - i.start)
func (i *IDM) Release(id uint) {
i.set.Clear(id - i.start)
}
130 changes: 2 additions & 128 deletions internal/idm/idm_test.go
Expand Up @@ -9,7 +9,7 @@ func TestNew(t *testing.T) {
if err != nil {
t.Errorf("idm.New(0, 10) error = %v", err)
}
if i.handle == nil {
if i.set == nil {
t.Error("set is not initialized")
}
if i.start != 0 {
Expand Down Expand Up @@ -60,7 +60,7 @@ func TestAllocate(t *testing.T) {
t.Errorf("i.GetID(false) error = %v", err)
}
if o != 52 {
t.Errorf("i.GetID(false) = %v, want 51", o)
t.Errorf("i.GetID(false) = %v, want 52", o)
}

o, err = i.GetID(false)
Expand Down Expand Up @@ -97,132 +97,6 @@ func TestUninitialized(t *testing.T) {
}
}

func TestAllocateInRange(t *testing.T) {
i, err := New(5, 10)
if err != nil {
t.Fatal(err)
}

o, err := i.GetIDInRange(6, 6, false)
if err != nil {
t.Errorf("i.GetIDInRange(6, 6, false) error = %v", err)
}
if o != 6 {
t.Errorf("i.GetIDInRange(6, 6, false) = %d, want 6", o)
}

if err = i.GetSpecificID(6); err == nil {
t.Errorf("i.GetSpecificID(6): allocating already-allocated id should fail")
}

o, err = i.GetID(false)
if err != nil {
t.Errorf("i.GetID(false) error = %v", err)
}
if o != 5 {
t.Errorf("i.GetID(false) = %v, want 5", o)
}

i.Release(6)

o, err = i.GetID(false)
if err != nil {
t.Errorf("i.GetID(false) error = %v", err)
}
if o != 6 {
t.Errorf("i.GetID(false) = %v, want 6", o)
}

for n := uint64(7); n <= 10; n++ {
o, err := i.GetIDInRange(7, 10, false)
if err != nil {
t.Errorf("i.GetIDInRange(7, 10, false) error = %v", err)
}
if o != n {
t.Errorf("i.GetIDInRange(7, 10, false) = %d, want %d", o, n)
}
}

if err = i.GetSpecificID(7); err == nil {
t.Errorf("i.GetSpecificID(7): allocating already-allocated id should fail")
}

if err = i.GetSpecificID(10); err == nil {
t.Errorf("i.GetSpecificID(10): allocating already-allocated id should fail")
}

i.Release(10)

o, err = i.GetIDInRange(5, 10, false)
if err != nil {
t.Errorf("i.GetIDInRange(5, 10, false) error = %v", err)
}
if o != 10 {
t.Errorf("i.GetIDInRange(5, 10, false) = %d, want 10", o)
}

i.Release(5)

o, err = i.GetIDInRange(5, 10, false)
if err != nil {
t.Errorf("i.GetIDInRange(5, 10, false) error = %v", err)
}
if o != 5 {
t.Errorf("i.GetIDInRange(5, 10, false) = %d, want 5", o)
}

for n := uint64(5); n <= 10; n++ {
i.Release(n)
}

for n := uint64(5); n <= 10; n++ {
o, err := i.GetIDInRange(5, 10, false)
if err != nil {
t.Errorf("i.GetIDInRange(5, 10, false) error = %v", err)
}
if o != n {
t.Errorf("i.GetIDInRange(5, 10, false) = %d, want %d", o, n)
}
}

for n := uint64(5); n <= 10; n++ {
if err = i.GetSpecificID(n); err == nil {
t.Errorf("i.GetSpecificID(%d): allocating already-allocated id should fail", n)
}
}

// New larger set
const ul = (1 << 24) - 1
i, err = New(0, ul)
if err != nil {
t.Fatalf("New(0, %d) error = %v", ul, err)
}

o, err = i.GetIDInRange(4096, ul, false)
if err != nil {
t.Errorf("i.GetIDInRange(4096, %d, false) error = %v", ul, err)
}
if o != 4096 {
t.Errorf("i.GetIDInRange(4096, %d, false) = %d, want 4096", ul, o)
}

o, err = i.GetIDInRange(4096, ul, false)
if err != nil {
t.Errorf("i.GetIDInRange(4096, %d, false) error = %v", ul, err)
}
if o != 4097 {
t.Errorf("i.GetIDInRange(4096, %d, false) = %d, want 4097", ul, o)
}

o, err = i.GetIDInRange(4096, ul, false)
if err != nil {
t.Errorf("i.GetIDInRange(4096, %d, false) error = %v", ul, err)
}
if o != 4098 {
t.Errorf("i.GetIDInRange(4096, %d, false) = %d, want 4098", ul, o)
}
}

func TestAllocateSerial(t *testing.T) {
i, err := New(50, 55)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions manager/allocator/portallocator.go
Expand Up @@ -372,18 +372,18 @@ func (ps *portSpace) allocate(p *api.PortConfig) (err error) {
// If it falls in the dynamic port range check out
// from dynamic port space first.
if p.PublishedPort >= dynamicPortStart && p.PublishedPort <= dynamicPortEnd {
if err = ps.dynamicPortSpace.GetSpecificID(uint64(p.PublishedPort)); err != nil {
if err = ps.dynamicPortSpace.GetSpecificID(uint(p.PublishedPort)); err != nil {
return err
}

defer func() {
if err != nil {
ps.dynamicPortSpace.Release(uint64(p.PublishedPort))
ps.dynamicPortSpace.Release(uint(p.PublishedPort))
}
}()
}

return ps.masterPortSpace.GetSpecificID(uint64(p.PublishedPort))
return ps.masterPortSpace.GetSpecificID(uint(p.PublishedPort))
}

// Check out an arbitrary port from dynamic port space.
Expand All @@ -408,8 +408,8 @@ func (ps *portSpace) allocate(p *api.PortConfig) (err error) {

func (ps *portSpace) free(p *api.PortConfig) {
if p.PublishedPort >= dynamicPortStart && p.PublishedPort <= dynamicPortEnd {
ps.dynamicPortSpace.Release(uint64(p.PublishedPort))
ps.dynamicPortSpace.Release(uint(p.PublishedPort))
}

ps.masterPortSpace.Release(uint64(p.PublishedPort))
ps.masterPortSpace.Release(uint(p.PublishedPort))
}
26 changes: 26 additions & 0 deletions vendor/github.com/bits-and-blooms/bitset/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions vendor/github.com/bits-and-blooms/bitset/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2e06ed0

Please sign in to comment.