mirror of https://github.com/fatedier/frp
fatedier
1 year ago
committed by
GitHub
11 changed files with 690 additions and 529 deletions
@ -1,3 +1,4 @@
|
||||
### Fixes |
||||
|
||||
* frpc: Return code 1 when the first login attempt fails and exits. |
||||
* When auth.method is `oidc` and auth.additionalScopes contains `HeartBeats`, if obtaining AccessToken fails, the application will be unresponsive. |
||||
|
@ -0,0 +1,103 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 msg |
||||
|
||||
import ( |
||||
"io" |
||||
"reflect" |
||||
) |
||||
|
||||
func AsyncHandler(f func(Message)) func(Message) { |
||||
return func(m Message) { |
||||
go f(m) |
||||
} |
||||
} |
||||
|
||||
// Dispatcher is used to send messages to net.Conn or register handlers for messages read from net.Conn.
|
||||
type Dispatcher struct { |
||||
rw io.ReadWriter |
||||
|
||||
sendCh chan Message |
||||
doneCh chan struct{} |
||||
msgHandlers map[reflect.Type]func(Message) |
||||
defaultHandler func(Message) |
||||
} |
||||
|
||||
func NewDispatcher(rw io.ReadWriter) *Dispatcher { |
||||
return &Dispatcher{ |
||||
rw: rw, |
||||
sendCh: make(chan Message, 100), |
||||
doneCh: make(chan struct{}), |
||||
msgHandlers: make(map[reflect.Type]func(Message)), |
||||
} |
||||
} |
||||
|
||||
// Run will block until io.EOF or some error occurs.
|
||||
func (d *Dispatcher) Run() { |
||||
go d.sendLoop() |
||||
go d.readLoop() |
||||
} |
||||
|
||||
func (d *Dispatcher) sendLoop() { |
||||
for { |
||||
select { |
||||
case <-d.doneCh: |
||||
return |
||||
case m := <-d.sendCh: |
||||
_ = WriteMsg(d.rw, m) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (d *Dispatcher) readLoop() { |
||||
for { |
||||
m, err := ReadMsg(d.rw) |
||||
if err != nil { |
||||
close(d.doneCh) |
||||
return |
||||
} |
||||
|
||||
if handler, ok := d.msgHandlers[reflect.TypeOf(m)]; ok { |
||||
handler(m) |
||||
} else if d.defaultHandler != nil { |
||||
d.defaultHandler(m) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (d *Dispatcher) Send(m Message) error { |
||||
select { |
||||
case <-d.doneCh: |
||||
return io.EOF |
||||
case d.sendCh <- m: |
||||
return nil |
||||
} |
||||
} |
||||
|
||||
func (d *Dispatcher) SendChannel() chan Message { |
||||
return d.sendCh |
||||
} |
||||
|
||||
func (d *Dispatcher) RegisterHandler(msg Message, handler func(Message)) { |
||||
d.msgHandlers[reflect.TypeOf(msg)] = handler |
||||
} |
||||
|
||||
func (d *Dispatcher) RegisterDefaultHandler(handler func(Message)) { |
||||
d.defaultHandler = handler |
||||
} |
||||
|
||||
func (d *Dispatcher) Done() chan struct{} { |
||||
return d.doneCh |
||||
} |
@ -0,0 +1,197 @@
|
||||
// Copyright 2023 The frp Authors
|
||||
//
|
||||
// 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 wait |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"time" |
||||
|
||||
"github.com/samber/lo" |
||||
|
||||
"github.com/fatedier/frp/pkg/util/util" |
||||
) |
||||
|
||||
type BackoffFunc func(previousDuration time.Duration, previousConditionError bool) time.Duration |
||||
|
||||
func (f BackoffFunc) Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration { |
||||
return f(previousDuration, previousConditionError) |
||||
} |
||||
|
||||
type BackoffManager interface { |
||||
Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration |
||||
} |
||||
|
||||
type FastBackoffOptions struct { |
||||
Duration time.Duration |
||||
Factor float64 |
||||
Jitter float64 |
||||
MaxDuration time.Duration |
||||
InitDurationIfFail time.Duration |
||||
|
||||
// If FastRetryCount > 0, then within the FastRetryWindow time window,
|
||||
// the retry will be performed with a delay of FastRetryDelay for the first FastRetryCount calls.
|
||||
FastRetryCount int |
||||
FastRetryDelay time.Duration |
||||
FastRetryJitter float64 |
||||
FastRetryWindow time.Duration |
||||
} |
||||
|
||||
type fastBackoffImpl struct { |
||||
options FastBackoffOptions |
||||
|
||||
lastCalledTime time.Time |
||||
consecutiveErrCount int |
||||
|
||||
fastRetryCutoffTime time.Time |
||||
countsInFastRetryWindow int |
||||
} |
||||
|
||||
func NewFastBackoffManager(options FastBackoffOptions) BackoffManager { |
||||
return &fastBackoffImpl{ |
||||
options: options, |
||||
countsInFastRetryWindow: 1, |
||||
} |
||||
} |
||||
|
||||
func (f *fastBackoffImpl) Backoff(previousDuration time.Duration, previousConditionError bool) time.Duration { |
||||
if f.lastCalledTime.IsZero() { |
||||
f.lastCalledTime = time.Now() |
||||
return f.options.Duration |
||||
} |
||||
now := time.Now() |
||||
f.lastCalledTime = now |
||||
|
||||
if previousConditionError { |
||||
f.consecutiveErrCount++ |
||||
} else { |
||||
f.consecutiveErrCount = 0 |
||||
} |
||||
|
||||
if f.options.FastRetryCount > 0 && previousConditionError { |
||||
f.countsInFastRetryWindow++ |
||||
if f.countsInFastRetryWindow <= f.options.FastRetryCount { |
||||
return Jitter(f.options.FastRetryDelay, f.options.FastRetryJitter) |
||||
} |
||||
if now.After(f.fastRetryCutoffTime) { |
||||
// reset
|
||||
f.fastRetryCutoffTime = now.Add(f.options.FastRetryWindow) |
||||
f.countsInFastRetryWindow = 0 |
||||
} |
||||
} |
||||
|
||||
if previousConditionError { |
||||
var duration time.Duration |
||||
if f.consecutiveErrCount == 1 { |
||||
duration = util.EmptyOr(f.options.InitDurationIfFail, previousDuration) |
||||
} else { |
||||
duration = previousDuration |
||||
} |
||||
|
||||
duration = util.EmptyOr(duration, time.Second) |
||||
if f.options.Factor != 0 { |
||||
duration = time.Duration(float64(duration) * f.options.Factor) |
||||
} |
||||
if f.options.Jitter > 0 { |
||||
duration = Jitter(duration, f.options.Jitter) |
||||
} |
||||
if f.options.MaxDuration > 0 && duration > f.options.MaxDuration { |
||||
duration = f.options.MaxDuration |
||||
} |
||||
return duration |
||||
} |
||||
return f.options.Duration |
||||
} |
||||
|
||||
func BackoffUntil(f func() error, backoff BackoffManager, sliding bool, stopCh <-chan struct{}) { |
||||
var delay time.Duration |
||||
previousError := false |
||||
|
||||
ticker := time.NewTicker(backoff.Backoff(delay, previousError)) |
||||
defer ticker.Stop() |
||||
|
||||
for { |
||||
select { |
||||
case <-stopCh: |
||||
return |
||||
default: |
||||
} |
||||
|
||||
if !sliding { |
||||
delay = backoff.Backoff(delay, previousError) |
||||
} |
||||
|
||||
if err := f(); err != nil { |
||||
previousError = true |
||||
} else { |
||||
previousError = false |
||||
} |
||||
|
||||
if sliding { |
||||
delay = backoff.Backoff(delay, previousError) |
||||
} |
||||
|
||||
ticker.Reset(delay) |
||||
select { |
||||
case <-stopCh: |
||||
return |
||||
default: |
||||
} |
||||
|
||||
select { |
||||
case <-stopCh: |
||||
return |
||||
case <-ticker.C: |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Jitter returns a time.Duration between duration and duration + maxFactor *
|
||||
// duration.
|
||||
//
|
||||
// This allows clients to avoid converging on periodic behavior. If maxFactor
|
||||
// is 0.0, a suggested default value will be chosen.
|
||||
func Jitter(duration time.Duration, maxFactor float64) time.Duration { |
||||
if maxFactor <= 0.0 { |
||||
maxFactor = 1.0 |
||||
} |
||||
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration)) |
||||
return wait |
||||
} |
||||
|
||||
func Until(f func(), period time.Duration, stopCh <-chan struct{}) { |
||||
ff := func() error { |
||||
f() |
||||
return nil |
||||
} |
||||
BackoffUntil(ff, BackoffFunc(func(time.Duration, bool) time.Duration { |
||||
return period |
||||
}), true, stopCh) |
||||
} |
||||
|
||||
func MergeAndCloseOnAnyStopChannel[T any](upstreams ...<-chan T) <-chan T { |
||||
out := make(chan T) |
||||
|
||||
for _, upstream := range upstreams { |
||||
ch := upstream |
||||
go lo.Try0(func() { |
||||
select { |
||||
case <-ch: |
||||
close(out) |
||||
case <-out: |
||||
} |
||||
}) |
||||
} |
||||
return out |
||||
} |
Loading…
Reference in new issue