go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/limiter/module.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package limiter 16 17 import ( 18 "context" 19 "flag" 20 "fmt" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/common/tsmon" 25 26 "go.chromium.org/luci/server/module" 27 ) 28 29 // ModuleName can be used to refer to this module when declaring dependencies. 30 var ModuleName = module.RegisterName("go.chromium.org/luci/server/limiter") 31 32 const defaultMaxConcurrentRPCs = 100000 // e.g. unlimited 33 34 // ModuleOptions contains configuration of the server module that installs 35 // default limiters applied to all routes/services in the server. 36 type ModuleOptions struct { 37 MaxConcurrentRPCs int64 // limit on a number of incoming concurrent RPCs (default is 100000, i.e. unlimited) 38 AdvisoryMode bool // if set, don't enforce MaxConcurrentRPCs, but still report violations 39 } 40 41 // Register registers the command line flags. 42 func (o *ModuleOptions) Register(f *flag.FlagSet) { 43 if o.MaxConcurrentRPCs == 0 { 44 o.MaxConcurrentRPCs = defaultMaxConcurrentRPCs 45 } 46 f.Int64Var( 47 &o.MaxConcurrentRPCs, 48 "limiter-max-concurrent-rpcs", 49 o.MaxConcurrentRPCs, 50 fmt.Sprintf("Limit on a number of incoming concurrent RPCs (default is %d)", o.MaxConcurrentRPCs), 51 ) 52 f.BoolVar( 53 &o.AdvisoryMode, 54 "limiter-advisory-mode", 55 o.AdvisoryMode, 56 "If set, don't enforce -limiter-max-concurrent-rpcs, but still report violations", 57 ) 58 } 59 60 // NewModule returns a server module that installs default limiters applied to 61 // all routes/services in the server. 62 func NewModule(opts *ModuleOptions) module.Module { 63 if opts == nil { 64 opts = &ModuleOptions{} 65 } 66 return &serverModule{opts: opts} 67 } 68 69 // NewModuleFromFlags is a variant of NewModule that initializes options through 70 // command line flags. 71 // 72 // Calling this function registers flags in flag.CommandLine. They are usually 73 // parsed in server.Main(...). 74 func NewModuleFromFlags() module.Module { 75 opts := &ModuleOptions{} 76 opts.Register(flag.CommandLine) 77 return NewModule(opts) 78 } 79 80 // serverModule implements module.Module. 81 type serverModule struct { 82 opts *ModuleOptions 83 rpcLimiter *Limiter // limits in-flight RPCs in the main port 84 } 85 86 // Name is part of module.Module interface. 87 func (*serverModule) Name() module.Name { 88 return ModuleName 89 } 90 91 // Dependencies is part of module.Module interface. 92 func (*serverModule) Dependencies() []module.Dependency { 93 return nil 94 } 95 96 // Initialize is part of module.Module interface. 97 func (m *serverModule) Initialize(ctx context.Context, host module.Host, opts module.HostOptions) (context.Context, error) { 98 // Now that options are parsed by the server we can validate them and 99 // construct a limiter from them. 100 if m.opts.MaxConcurrentRPCs == 0 { 101 m.opts.MaxConcurrentRPCs = defaultMaxConcurrentRPCs 102 } 103 var err error 104 m.rpcLimiter, err = New(Options{ 105 Name: "rpc", 106 AdvisoryMode: m.opts.AdvisoryMode, 107 MaxConcurrentRequests: m.opts.MaxConcurrentRPCs, 108 }) 109 if err != nil { 110 return nil, err 111 } 112 113 // We want limiter's metrics to be reported before every flush (so the flushed 114 // values are as fresh as possible) and also once per second (to make the 115 // limiter state observable through /admin/portal/tsmon/metrics debug UI). 116 tsmon.RegisterCallbackIn(ctx, m.rpcLimiter.ReportMetrics) 117 host.RunInBackground("luci.limiter.rpc", func(ctx context.Context) { 118 for { 119 m.rpcLimiter.ReportMetrics(ctx) 120 if r := <-clock.After(ctx, time.Second); r.Err != nil { 121 return // the context is canceled 122 } 123 } 124 }) 125 126 // Actually add the limiter to the default interceptors chains. 127 intr := NewServerInterceptor(m.rpcLimiter) 128 host.RegisterUnaryServerInterceptors(intr.Unary()) 129 host.RegisterStreamServerInterceptors(intr.Stream()) 130 return ctx, nil 131 }