2019-05-22 23:16:55 +02:00
|
|
|
package concurrencylimiter
|
|
|
|
|
|
|
|
import (
|
2019-05-29 11:35:47 +02:00
|
|
|
"flag"
|
2019-05-22 23:16:55 +02:00
|
|
|
"fmt"
|
2019-08-23 08:46:45 +02:00
|
|
|
"net/http"
|
2019-05-22 23:16:55 +02:00
|
|
|
"runtime"
|
|
|
|
"time"
|
2019-05-28 16:17:19 +02:00
|
|
|
|
2019-08-23 08:46:45 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
|
2019-05-28 16:17:19 +02:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/timerpool"
|
2019-05-29 11:35:47 +02:00
|
|
|
"github.com/VictoriaMetrics/metrics"
|
2019-05-22 23:16:55 +02:00
|
|
|
)
|
|
|
|
|
2019-05-29 11:35:47 +02:00
|
|
|
var maxConcurrentInserts = flag.Int("maxConcurrentInserts", runtime.GOMAXPROCS(-1)*4, "The maximum number of concurrent inserts")
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
var (
|
2019-05-29 11:35:47 +02:00
|
|
|
// ch is the channel for limiting concurrent calls to Do.
|
|
|
|
ch chan struct{}
|
2019-05-22 23:16:55 +02:00
|
|
|
|
|
|
|
// waitDuration is the amount of time to wait until at least a single
|
2019-05-29 11:35:47 +02:00
|
|
|
// concurrent Do call out of cap(ch) inserts is complete.
|
2019-05-22 23:16:55 +02:00
|
|
|
waitDuration = time.Second * 30
|
|
|
|
)
|
|
|
|
|
2019-05-29 11:35:47 +02:00
|
|
|
// Init initializes concurrencylimiter.
|
|
|
|
//
|
|
|
|
// Init must be called after flag.Parse call.
|
|
|
|
func Init() {
|
|
|
|
ch = make(chan struct{}, *maxConcurrentInserts)
|
|
|
|
}
|
|
|
|
|
2019-05-22 23:16:55 +02:00
|
|
|
// Do calls f with the limited concurrency.
|
|
|
|
func Do(f func() error) error {
|
2019-05-29 11:35:47 +02:00
|
|
|
// Limit the number of conurrent f calls in order to prevent from excess
|
2019-05-22 23:16:55 +02:00
|
|
|
// memory usage and CPU trashing.
|
2019-08-05 17:27:50 +02:00
|
|
|
select {
|
|
|
|
case ch <- struct{}{}:
|
|
|
|
err := f()
|
|
|
|
<-ch
|
|
|
|
return err
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
// All the workers are busy.
|
|
|
|
// Sleep for up to waitDuration.
|
|
|
|
concurrencyLimitReached.Inc()
|
2019-05-28 16:17:19 +02:00
|
|
|
t := timerpool.Get(waitDuration)
|
2019-05-22 23:16:55 +02:00
|
|
|
select {
|
|
|
|
case ch <- struct{}{}:
|
2019-05-28 16:17:19 +02:00
|
|
|
timerpool.Put(t)
|
2019-05-22 23:16:55 +02:00
|
|
|
err := f()
|
|
|
|
<-ch
|
|
|
|
return err
|
|
|
|
case <-t.C:
|
2019-05-28 16:17:19 +02:00
|
|
|
timerpool.Put(t)
|
2019-08-05 17:27:50 +02:00
|
|
|
concurrencyLimitTimeout.Inc()
|
2019-08-23 08:46:45 +02:00
|
|
|
return &httpserver.ErrorWithStatusCode{
|
|
|
|
Err: fmt.Errorf("the server is overloaded with %d concurrent inserts; either increase -maxConcurrentInserts or reduce the load", cap(ch)),
|
|
|
|
StatusCode: http.StatusServiceUnavailable,
|
|
|
|
}
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
}
|
2019-05-29 11:35:47 +02:00
|
|
|
|
2019-08-05 17:27:50 +02:00
|
|
|
var (
|
|
|
|
concurrencyLimitReached = metrics.NewCounter(`vm_concurrent_insert_limit_reached_total`)
|
|
|
|
concurrencyLimitTimeout = metrics.NewCounter(`vm_concurrent_insert_limit_timeout_total`)
|
|
|
|
|
|
|
|
_ = metrics.NewGauge(`vm_concurrent_insert_capacity`, func() float64 {
|
|
|
|
return float64(cap(ch))
|
|
|
|
})
|
|
|
|
_ = metrics.NewGauge(`vm_concurrent_insert_current`, func() float64 {
|
|
|
|
return float64(len(ch))
|
|
|
|
})
|
|
|
|
)
|