Skip to content
quarry Artifact [f0fd04a645]
Login

Artifact [f0fd04a645]

Artifact f0fd04a6450615d6f4e76d78bca6bf2d30c991452e239c528aa440f7180f9760:


// Package streamcap bounds how many live streams anonymous readers may
// hold open. Each stream keeps a goroutine and, for chat, a broker
// consumer alive for as long as the connection lasts, and an anonymous
// reader can open them without signing in, so nothing else stops one
// client from holding as many as it likes.
package streamcap

import "sync"

const (
	// PerAddress is how many anonymous streams one address may hold.
	// A reader with several tabs open on several channels needs a few,
	// and a household or office behind one address needs a few more;
	// a client past eight is opening streams rather than reading them.
	PerAddress = 8
	// Total is how many anonymous streams the whole process may hold.
	// It bounds the goroutines and broker consumers anonymous readers
	// can pin at once, with room for a busy public channel, while
	// staying small beside what the broker and the process can carry.
	Total = 256
)

// Anonymous is the process-wide budget every anonymous stream draws
// on. It is one budget rather than one per surface because the cost it
// bounds, goroutines and broker consumers, is the process's, however
// the stream was reached.
var Anonymous = New(PerAddress, Total)

// Limiter counts open streams per address and in total.
type Limiter struct {
	perAddr, total int

	mu     sync.Mutex
	n      int
	byAddr map[string]int
}

// New returns a limiter admitting perAddr streams from one address and
// total streams overall.
func New(perAddr, total int) *Limiter {
	return &Limiter{perAddr: perAddr, total: total, byAddr: map[string]int{}}
}

// Acquire admits one stream from addr, or reports that addr or the
// process is at its cap. addr is the caller's to decide, by the
// forge's trusted-proxy rule; this package never reads a request, so
// it cannot grow a second rule of its own. The caller must call release when the stream
// ends; calling it more than once frees the slot only once.
func (l *Limiter) Acquire(addr string) (release func(), ok bool) {
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.n >= l.total || l.byAddr[addr] >= l.perAddr {
		return nil, false
	}
	l.n++
	l.byAddr[addr]++
	var once sync.Once
	return func() {
		once.Do(func() {
			l.mu.Lock()
			defer l.mu.Unlock()
			l.n--
			if l.byAddr[addr]--; l.byAddr[addr] <= 0 {
				delete(l.byAddr, addr)
			}
		})
	}, true
}