Added timeout functionality to Servers (#3192)
* Added timeout functionality to servers currently only possible on the pairnig serve * Removed logging (like a mad man) * handling linter erroring
This commit is contained in:
parent
4d491da8de
commit
90d54b1a3d
|
@ -61,6 +61,9 @@ type PayloadSourceConfig struct {
|
||||||
// they are required in other cases
|
// they are required in other cases
|
||||||
KeyUID string `json:"keyUID"`
|
KeyUID string `json:"keyUID"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|
||||||
|
// Timeout the number of milliseconds after which the pairing server will automatically terminate
|
||||||
|
Timeout uint `json:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountPayloadManagerConfig represents the initialisation parameters required for a AccountPayloadManager
|
// AccountPayloadManagerConfig represents the initialisation parameters required for a AccountPayloadManager
|
||||||
|
|
|
@ -76,7 +76,7 @@ func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Server{Server: server.NewServer(
|
s := &Server{Server: server.NewServer(
|
||||||
config.Cert,
|
config.Cert,
|
||||||
config.Hostname,
|
config.Hostname,
|
||||||
nil,
|
nil,
|
||||||
|
@ -88,7 +88,10 @@ func NewPairingServer(backend *api.GethStatusBackend, config *Config) (*Server,
|
||||||
PayloadManager: pm,
|
PayloadManager: pm,
|
||||||
cookieStore: cs,
|
cookieStore: cs,
|
||||||
rawMessagePayloadManager: rmpm,
|
rawMessagePayloadManager: rmpm,
|
||||||
}, nil
|
}
|
||||||
|
s.SetTimeout(config.Timeout)
|
||||||
|
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
|
// MakeConnectionParams generates a *ConnectionParams based on the Server's current state
|
||||||
|
|
|
@ -41,6 +41,39 @@ func (s *PairingServerSuite) TestMultiBackgroundForeground() {
|
||||||
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple
|
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PairingServerSuite) TestMultiTimeout() {
|
||||||
|
s.PS.SetTimeout(20)
|
||||||
|
|
||||||
|
err := s.PS.Start()
|
||||||
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
s.PS.ToBackground()
|
||||||
|
s.PS.ToForeground()
|
||||||
|
s.PS.ToBackground()
|
||||||
|
s.PS.ToBackground()
|
||||||
|
s.PS.ToForeground()
|
||||||
|
s.PS.ToForeground()
|
||||||
|
|
||||||
|
s.Require().Regexp(regexp.MustCompile("(https://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5})"), s.PS.MakeBaseURL().String()) // nolint: gosimple
|
||||||
|
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToBackground()
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToForeground()
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToBackground()
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToBackground()
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToForeground()
|
||||||
|
time.Sleep(7 * time.Millisecond)
|
||||||
|
s.PS.ToForeground()
|
||||||
|
|
||||||
|
// Wait for timeout to expire
|
||||||
|
time.Sleep(40 * time.Millisecond)
|
||||||
|
s.Require().False(s.PS.IsRunning())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PairingServerSuite) TestPairingServer_StartPairing() {
|
func (s *PairingServerSuite) TestPairingServer_StartPairing() {
|
||||||
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
|
// Replace PairingServer.PayloadManager with a MockEncryptOnlyPayloadManager
|
||||||
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
pm, err := NewMockEncryptOnlyPayloadManager(s.EphemeralAES)
|
||||||
|
|
|
@ -18,15 +18,18 @@ type Server struct {
|
||||||
cert *tls.Certificate
|
cert *tls.Certificate
|
||||||
hostname string
|
hostname string
|
||||||
handlers HandlerPatternMap
|
handlers HandlerPatternMap
|
||||||
|
|
||||||
portManger
|
portManger
|
||||||
|
*timeoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(cert *tls.Certificate, hostname string, afterPortChanged func(int), logger *zap.Logger) Server {
|
func NewServer(cert *tls.Certificate, hostname string, afterPortChanged func(int), logger *zap.Logger) Server {
|
||||||
return Server{
|
return Server{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
cert: cert,
|
cert: cert,
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
portManger: newPortManager(logger.Named("Server"), afterPortChanged),
|
portManger: newPortManager(logger.Named("Server"), afterPortChanged),
|
||||||
|
timeoutManager: newTimeoutManager(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +76,13 @@ func (s *Server) listenAndServe() {
|
||||||
|
|
||||||
s.isRunning = true
|
s.isRunning = true
|
||||||
|
|
||||||
|
s.StartTimeout(func() {
|
||||||
|
err := s.Stop()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("PairingServer termination fail", zap.Error(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
err = s.server.Serve(listener)
|
err = s.server.Serve(listener)
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
s.logger.Error("server failed unexpectedly, restarting", zap.Error(err))
|
s.logger.Error("server failed unexpectedly, restarting", zap.Error(err))
|
||||||
|
@ -82,11 +92,11 @@ func (s *Server) listenAndServe() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.isRunning = false
|
s.isRunning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) resetServer() {
|
func (s *Server) resetServer() {
|
||||||
|
s.StopTimeout()
|
||||||
s.server = new(http.Server)
|
s.server = new(http.Server)
|
||||||
s.ResetPort()
|
s.ResetPort()
|
||||||
}
|
}
|
||||||
|
@ -112,6 +122,7 @@ func (s *Server) Start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
func (s *Server) Stop() error {
|
||||||
|
s.StopTimeout()
|
||||||
if s.server != nil {
|
if s.server != nil {
|
||||||
return s.server.Shutdown(context.Background())
|
return s.server.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
@ -119,6 +130,10 @@ func (s *Server) Stop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) IsRunning() bool {
|
||||||
|
return s.isRunning
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) ToForeground() {
|
func (s *Server) ToForeground() {
|
||||||
if !s.isRunning && (s.server != nil) {
|
if !s.isRunning && (s.server != nil) {
|
||||||
err := s.Start()
|
err := s.Start()
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// timeoutManager represents a discrete encapsulation of timeout functionality.
|
||||||
|
// this struct expose 3 functions:
|
||||||
|
// - SetTimeout
|
||||||
|
// - StartTimeout
|
||||||
|
// - StopTimeout
|
||||||
|
type timeoutManager struct {
|
||||||
|
// timeout number of milliseconds the timeout operation will run before executing the `terminate` func()
|
||||||
|
// 0 represents an inactive timeout
|
||||||
|
timeout uint
|
||||||
|
|
||||||
|
// exitQueue handles the cancel signal channels that circumvent timeout operations and prevent the
|
||||||
|
// execution of any `terminate` func()
|
||||||
|
exitQueue *exitQueueManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTimeoutManager returns a fully qualified and initialised timeoutManager
|
||||||
|
func newTimeoutManager() *timeoutManager {
|
||||||
|
return &timeoutManager{
|
||||||
|
exitQueue: &exitQueueManager{queue: []chan struct{}{}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout sets the value of the timeoutManager.timeout
|
||||||
|
func (t *timeoutManager) SetTimeout(milliseconds uint) {
|
||||||
|
t.timeout = milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTimeout starts a timeout operation based on the set timeoutManager.timeout value
|
||||||
|
// the given terminate func() will be executed once the timeout duration has passed
|
||||||
|
func (t *timeoutManager) StartTimeout(terminate func()) {
|
||||||
|
if t.timeout == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.StopTimeout()
|
||||||
|
|
||||||
|
exit := make(chan struct{}, 1)
|
||||||
|
t.exitQueue.add(exit)
|
||||||
|
go t.run(terminate, exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTimeout terminates a timeout operation and exits gracefully
|
||||||
|
func (t *timeoutManager) StopTimeout() {
|
||||||
|
if t.timeout == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.exitQueue.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run inits the main timeout run function that awaits for the exit command to be triggered or for the
|
||||||
|
// timeout duration to elapse and trigger the parameter terminate function.
|
||||||
|
func (t *timeoutManager) run(terminate func(), exit chan struct{}) {
|
||||||
|
select {
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
case <-time.After(time.Duration(t.timeout) * time.Millisecond):
|
||||||
|
terminate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exitQueueManager
|
||||||
|
type exitQueueManager struct {
|
||||||
|
queue []chan struct{}
|
||||||
|
queueLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// add handles new exit channels adding them to the exit queue
|
||||||
|
func (e *exitQueueManager) add(exit chan struct{}) {
|
||||||
|
e.queueLock.Lock()
|
||||||
|
defer e.queueLock.Unlock()
|
||||||
|
|
||||||
|
e.queue = append(e.queue, exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty sends a signal to every exit channel in the queue and then resets the queue
|
||||||
|
func (e *exitQueueManager) empty() {
|
||||||
|
e.queueLock.Lock()
|
||||||
|
defer e.queueLock.Unlock()
|
||||||
|
|
||||||
|
for i := range e.queue {
|
||||||
|
e.queue[i] <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.queue = []chan struct{}{}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeoutManager(t *testing.T) {
|
||||||
|
tm := newTimeoutManager()
|
||||||
|
|
||||||
|
// test 0 timeout means timeout does not occur
|
||||||
|
tm.SetTimeout(0)
|
||||||
|
|
||||||
|
// test fuzzing - 0 timeout - multiple sequential calls to random init and stop funcs
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
b, err := rand.Int(rand.Reader, big.NewInt(2))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Int64() == 1 {
|
||||||
|
tm.StartTimeout(t.FailNow)
|
||||||
|
} else {
|
||||||
|
tm.StopTimeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test fuzzing - random timeout - multiple sequential calls to random init and stop funcs
|
||||||
|
for i := 0; i < 30; i++ {
|
||||||
|
b, err := rand.Int(rand.Reader, big.NewInt(2))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
to, err := rand.Int(rand.Reader, big.NewInt(11))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tm.SetTimeout(uint(to.Int64() * 20))
|
||||||
|
|
||||||
|
if b.Int64() == 1 {
|
||||||
|
tm.StartTimeout(t.FailNow)
|
||||||
|
} else {
|
||||||
|
tm.StopTimeout()
|
||||||
|
}
|
||||||
|
tm.StopTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
// test StopTimeout() prevents termination func
|
||||||
|
tm.SetTimeout(20)
|
||||||
|
tm.StartTimeout(t.FailNow)
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
tm.StopTimeout()
|
||||||
|
|
||||||
|
// test StartTimeout() executes termination func on timeout
|
||||||
|
ok := false
|
||||||
|
tm.SetTimeout(10)
|
||||||
|
tm.StartTimeout(func() {
|
||||||
|
ok = true
|
||||||
|
})
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
if !ok {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue