mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 06:08:23 +08:00
This reverts commit 4f3aa9a3f2.
We're unable to build for 32-bit Linux due to
https://github.com/golang/go/issues/55152,
which looks like it will be fixed with Go 1.19.2 next week.
We'll re-evaluate with the next Go release and reintroduce this change
as soon as we can.
285 lines
6.8 KiB
Go
285 lines
6.8 KiB
Go
/*
|
|
Copyright 2020 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package workpool
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"go.uber.org/atomic"
|
|
)
|
|
|
|
// Pool manages a collection of work group by key and is the primary means
|
|
// by which group are managed. Each work group has an adjustable target value
|
|
// which is the number of target leases which should be active for the given
|
|
// group.
|
|
type Pool struct {
|
|
mu sync.Mutex
|
|
leaseIDs *atomic.Uint64
|
|
group *group
|
|
// grantC is an unbuffered channel that funnels available leases from the
|
|
// workgroups to the outside world
|
|
grantC chan Lease
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func NewPool(ctx context.Context) *Pool {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
return &Pool{
|
|
leaseIDs: atomic.NewUint64(0),
|
|
grantC: make(chan Lease),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// Acquire is the channel which must be received on to acquire
|
|
// new leases. Each lease acquired in this way *must* have its
|
|
// Release method called when the lease is no longer needed.
|
|
// Note this channel will deliver leases from all active work
|
|
// group. It's up to the receiver to differentiate what group
|
|
// the lease refers to and act accordingly.
|
|
func (p *Pool) Acquire() <-chan Lease {
|
|
return p.grantC
|
|
}
|
|
|
|
// Done signals pool closure.
|
|
func (p *Pool) Done() <-chan struct{} {
|
|
return p.ctx.Done()
|
|
}
|
|
|
|
// Get gets the current counts for the specified key.
|
|
func (p *Pool) Get() Counts {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
if p.group == nil {
|
|
return Counts{}
|
|
}
|
|
|
|
return p.group.loadCounts()
|
|
}
|
|
|
|
// Set sets the target for the specified key.
|
|
func (p *Pool) Set(target uint64) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
if target < 1 {
|
|
p.del()
|
|
return
|
|
}
|
|
|
|
if p.group == nil {
|
|
p.start(target)
|
|
return
|
|
}
|
|
|
|
p.group.setTarget(target)
|
|
}
|
|
|
|
// Start starts a new work group with the specified initial target.
|
|
// If Start returns false, the group already exists.
|
|
func (p *Pool) start(target uint64) {
|
|
ctx, cancel := context.WithCancel(p.ctx)
|
|
notifyC := make(chan struct{}, 1)
|
|
g := &group{
|
|
counts: Counts{
|
|
Active: 0,
|
|
Target: target,
|
|
},
|
|
leaseIDs: p.leaseIDs,
|
|
grantC: p.grantC,
|
|
notifyC: notifyC,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
p.group = g
|
|
|
|
// Start a routine to monitor the group's lease acquisition
|
|
// and handle notifications when a lease is returned to the
|
|
// pool
|
|
go g.run()
|
|
}
|
|
|
|
func (p *Pool) del() (ok bool) {
|
|
if p.group == nil {
|
|
return false
|
|
}
|
|
|
|
p.group.cancel()
|
|
p.group = nil
|
|
return true
|
|
}
|
|
|
|
// Stop permanently halts all associated group.
|
|
func (p *Pool) Stop() {
|
|
p.cancel()
|
|
}
|
|
|
|
// Counts holds the target and active counts for a
|
|
// key/group.
|
|
type Counts struct {
|
|
// Target is the number of active leases that we would
|
|
// like to converge toward.
|
|
Target uint64
|
|
// Active is the current active lease count.
|
|
Active uint64
|
|
}
|
|
|
|
// group is a work group for a particular key in the pool. It tracks the number of
|
|
// active and target leases and adds leases when active drops below target.
|
|
type group struct {
|
|
cmu sync.Mutex
|
|
counts Counts
|
|
leaseIDs *atomic.Uint64
|
|
grantC chan Lease
|
|
notifyC chan struct{}
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
// notify ensures that group is in a notified state.
|
|
// if the group is already in a notified state, this
|
|
// method has no effect. This function should be called
|
|
// any time state is changed to ensure that the group's
|
|
// goroutine unblocks & handles the updated state.
|
|
func (g *group) notify() {
|
|
select {
|
|
case g.notifyC <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// loadCounts loads the current lease counts.
|
|
func (g *group) loadCounts() Counts {
|
|
g.cmu.Lock()
|
|
defer g.cmu.Unlock()
|
|
return g.counts
|
|
}
|
|
|
|
// incrActive increases the active lease count by 1
|
|
// and ensures group is in the notified state.
|
|
func (g *group) incrActive() Counts {
|
|
g.cmu.Lock()
|
|
defer g.cmu.Unlock()
|
|
g.counts.Active++
|
|
g.notify()
|
|
return g.counts
|
|
}
|
|
|
|
// decrActive decreases the active lease count by 1
|
|
// and ensures group is in the notified state.
|
|
func (g *group) decrActive() Counts {
|
|
g.cmu.Lock()
|
|
defer g.cmu.Unlock()
|
|
g.counts.Active--
|
|
g.notify()
|
|
return g.counts
|
|
}
|
|
|
|
// setTarget sets the target lease count that the group should
|
|
// attempt to converge toward.
|
|
func (g *group) setTarget(target uint64) {
|
|
g.cmu.Lock()
|
|
defer g.cmu.Unlock()
|
|
g.counts.Target = target
|
|
g.notify()
|
|
}
|
|
|
|
// run manages the issuing of leases and handling their return to the pool for
|
|
// a given work group.
|
|
func (g *group) run() {
|
|
var counts Counts
|
|
var nextLease Lease
|
|
var grant chan Lease
|
|
for {
|
|
// Are we able to grant new leases? We are only allowed to grant a lease
|
|
// while the number of outstanding leases is less than the target value.
|
|
// If anyone else wants one after that, they will have to wait until a
|
|
// lease is returned to the pool.
|
|
counts = g.loadCounts()
|
|
if counts.Active < counts.Target {
|
|
// We are in a "granting" state; prepare to issue the lease to the
|
|
// next person who asks.
|
|
grant = g.grantC
|
|
if nextLease.id == 0 {
|
|
nextLease = newLease(g)
|
|
}
|
|
} else {
|
|
// we are in a non-"granting" state; prepare to block until a new
|
|
// event wakes the routine.
|
|
grant = nil
|
|
}
|
|
|
|
// Remembering that writes to a `nil` channel block indefinitely,
|
|
// if we're in a non-granting state this select blocks until `notify()`
|
|
// is called (usually by a lease being returned), or the context is
|
|
// canceled.
|
|
//
|
|
// Otherwise, we post the lease to the outside world and block on
|
|
// someone picking it up (or cancellation)
|
|
select {
|
|
case grant <- nextLease:
|
|
g.incrActive()
|
|
nextLease = Lease{}
|
|
case <-g.notifyC:
|
|
// some event has woken the dispatch routine. Go back around.
|
|
case <-g.ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Lease grants access to a resource or group. When the lease is received,
|
|
// work can begin. Leases are held by workers and must be released when
|
|
// the worker has finished its work.
|
|
type Lease struct {
|
|
*group
|
|
id uint64
|
|
relOnce *sync.Once
|
|
}
|
|
|
|
func newLease(group *group) Lease {
|
|
return Lease{
|
|
group: group,
|
|
id: group.leaseIDs.Add(1),
|
|
relOnce: new(sync.Once),
|
|
}
|
|
}
|
|
|
|
// ID returns the unique ID of this lease.
|
|
func (l Lease) ID() uint64 {
|
|
return l.id
|
|
}
|
|
|
|
// IsZero checks if this is the zero value of Lease.
|
|
func (l Lease) IsZero() bool {
|
|
return l == Lease{}
|
|
}
|
|
|
|
// Release relinquishes this lease. Each lease is unique,
|
|
// so double-calling Release() on the same Lease has no effect.
|
|
func (l Lease) Release() {
|
|
if l.IsZero() {
|
|
return
|
|
}
|
|
l.relOnce.Do(func() {
|
|
l.decrActive()
|
|
})
|
|
}
|