diff options
author | Wolfy-J <[email protected]> | 2019-05-05 12:29:12 +0300 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2019-05-05 12:29:12 +0300 |
commit | e6acf9d57099c69ce58b1121716528825095814f (patch) | |
tree | e072766b682fa40521aeb4661094ea0779bb1123 | |
parent | ee12d7b834b501beed56945a54fdabe8bbdd4570 (diff) |
testing limits
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | cmd/rr/limit/debug.go | 6 | ||||
-rw-r--r-- | service/limit/controller.go | 18 | ||||
-rw-r--r-- | service/limit/service_test.go | 182 | ||||
-rw-r--r-- | tests/http/stuck.php | 11 |
5 files changed, 206 insertions, 13 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 775f6c22..6709015d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ v1.4.0 - the ability to safely remove the worker from the pool in runtime - minor performance improvements - `real ip` resolution using X-Real-Ip and X-Forwarded-For (+cidr verification) -- automatic worker lifecycle manager (controller) +- automatic worker lifecycle manager (controller, see [sample config](https://github.com/spiral/roadrunner/blob/master/.rr.yaml)) - maxMemory (graceful stop) - ttl (graceful stop) - idleTTL (graceful stop) diff --git a/cmd/rr/limit/debug.go b/cmd/rr/limit/debug.go index bb25d099..b9d919dc 100644 --- a/cmd/rr/limit/debug.go +++ b/cmd/rr/limit/debug.go @@ -32,7 +32,7 @@ func (s *debugger) listener(event int, ctx interface{}) { // watchers switch event { - case limit.EventMaxTTL: + case limit.EventTTL: w := ctx.(roadrunner.WorkerError) s.logger.Debug(util.Sprintf( "<white+hb>worker.%v</reset> <yellow>%s</reset>", @@ -41,7 +41,7 @@ func (s *debugger) listener(event int, ctx interface{}) { )) return - case limit.EventMaxIdleTTL: + case limit.EventIdleTTL: w := ctx.(roadrunner.WorkerError) s.logger.Debug(util.Sprintf( "<white+hb>worker.%v</reset> <yellow>%s</reset>", @@ -59,7 +59,7 @@ func (s *debugger) listener(event int, ctx interface{}) { )) return - case limit.EventMaxExecTTL: + case limit.EventExecTTL: w := ctx.(roadrunner.WorkerError) s.logger.Error(util.Sprintf( "<white+hb>worker.%v</reset> <red>%s</reset>", diff --git a/service/limit/controller.go b/service/limit/controller.go index 706197fa..c11f4b91 100644 --- a/service/limit/controller.go +++ b/service/limit/controller.go @@ -11,14 +11,14 @@ const ( // EventMaxMemory caused when worker consumes more memory than allowed. EventMaxMemory = iota + 8000 - // EventMaxTTL thrown when worker is removed due TTL being reached. Context is rr.WorkerError - EventMaxTTL + // EventTTL thrown when worker is removed due TTL being reached. Context is rr.WorkerError + EventTTL - // EventMaxIdleTTL triggered when worker spends too much time at rest. - EventMaxIdleTTL + // EventIdleTTL triggered when worker spends too much time at rest. + EventIdleTTL - // EventMaxExecTTL triggered when worker spends too much time doing the task (max_execution_time). - EventMaxExecTTL + // EventExecTTL triggered when worker spends too much time doing the task (max_execution_time). + EventExecTTL ) // handles controller events @@ -67,7 +67,7 @@ func (c *controller) control(p roadrunner.Pool) { // make sure worker still on initial request if p.Remove(w, err) && w.State().NumExecs() == eID { go w.Kill() - c.report(EventMaxExecTTL, w, err) + c.report(EventExecTTL, w, err) } } } @@ -80,7 +80,7 @@ func (c *controller) control(p roadrunner.Pool) { ) { err := fmt.Errorf("max idle time reached (%vs)", c.cfg.IdleTTL) if p.Remove(w, err) { - c.report(EventMaxIdleTTL, w, err) + c.report(EventIdleTTL, w, err) } } } @@ -103,7 +103,7 @@ func (c *controller) loadWorkers(p roadrunner.Pool) { if c.cfg.TTL != 0 && now.Sub(w.Created).Seconds() >= float64(c.cfg.TTL) { err := fmt.Errorf("max TTL reached (%vs)", c.cfg.TTL) if p.Remove(w, err) { - c.report(EventMaxTTL, w, err) + c.report(EventTTL, w, err) } continue } diff --git a/service/limit/service_test.go b/service/limit/service_test.go index 87c8046b..1d751442 100644 --- a/service/limit/service_test.go +++ b/service/limit/service_test.go @@ -93,6 +93,188 @@ func Test_Service_PidEcho(t *testing.T) { assert.Equal(t, getPID(s), string(b)) } +func Test_Service_ListenerPlusTTL(t *testing.T) { + logger, _ := test.NewNullLogger() + logger.SetLevel(logrus.DebugLevel) + + c := service.NewContainer(logger) + c.Register(rrhttp.ID, &rrhttp.Service{}) + c.Register(ID, &Service{}) + + assert.NoError(t, c.Init(&testCfg{ + httpCfg: `{ + "address": ":6029", + "workers":{ + "command": "php ../../tests/http/client.php pid pipes", + "pool": {"numWorkers": 1} + } + }`, + limitCfg: `{ + "services": { + "http": { + "ttl": 1 + } + } + }`, + })) + + s, _ := c.Get(rrhttp.ID) + assert.NotNil(t, s) + + l, _ := c.Get(ID) + captured := make(chan interface{}) + l.(*Service).AddListener(func(event int, ctx interface{}) { + if event == EventTTL { + close(captured) + } + }) + + go func() { c.Serve() }() + time.Sleep(time.Millisecond * 100) + defer c.Stop() + + lastPID := getPID(s) + + req, err := http.NewRequest("GET", "http://localhost:6029", nil) + assert.NoError(t, err) + + r, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + defer r.Body.Close() + + b, err := ioutil.ReadAll(r.Body) + assert.NoError(t, err) + assert.Equal(t, lastPID, string(b)) + + <-captured + + // clean state + req, err = http.NewRequest("GET", "http://localhost:6029?new", nil) + assert.NoError(t, err) + + _, err = http.DefaultClient.Do(req) + assert.NoError(t, err) + + assert.NotEqual(t, lastPID, getPID(s)) +} + +func Test_Service_ListenerPlusIdleTTL(t *testing.T) { + logger, _ := test.NewNullLogger() + logger.SetLevel(logrus.DebugLevel) + + c := service.NewContainer(logger) + c.Register(rrhttp.ID, &rrhttp.Service{}) + c.Register(ID, &Service{}) + + assert.NoError(t, c.Init(&testCfg{ + httpCfg: `{ + "address": ":6029", + "workers":{ + "command": "php ../../tests/http/client.php pid pipes", + "pool": {"numWorkers": 1} + } + }`, + limitCfg: `{ + "services": { + "http": { + "idleTtl": 1 + } + } + }`, + })) + + s, _ := c.Get(rrhttp.ID) + assert.NotNil(t, s) + + l, _ := c.Get(ID) + captured := make(chan interface{}) + l.(*Service).AddListener(func(event int, ctx interface{}) { + if event == EventIdleTTL { + close(captured) + } + }) + + go func() { c.Serve() }() + time.Sleep(time.Millisecond * 100) + defer c.Stop() + + lastPID := getPID(s) + + req, err := http.NewRequest("GET", "http://localhost:6029", nil) + assert.NoError(t, err) + + r, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + defer r.Body.Close() + + b, err := ioutil.ReadAll(r.Body) + assert.NoError(t, err) + + assert.NoError(t, err) + assert.Equal(t, lastPID, string(b)) + + <-captured + + // clean state + req, err = http.NewRequest("GET", "http://localhost:6029?new", nil) + assert.NoError(t, err) + + _, err = http.DefaultClient.Do(req) + assert.NoError(t, err) + + assert.NotEqual(t, lastPID, getPID(s)) +} + +func Test_Service_Listener_MaxExecTTL(t *testing.T) { + logger, _ := test.NewNullLogger() + logger.SetLevel(logrus.DebugLevel) + + c := service.NewContainer(logger) + c.Register(rrhttp.ID, &rrhttp.Service{}) + c.Register(ID, &Service{}) + + assert.NoError(t, c.Init(&testCfg{ + httpCfg: `{ + "address": ":6029", + "workers":{ + "command": "php ../../tests/http/client.php stuck pipes", + "pool": {"numWorkers": 1} + } + }`, + limitCfg: `{ + "services": { + "http": { + "execTTL": 1 + } + } + }`, + })) + + s, _ := c.Get(rrhttp.ID) + assert.NotNil(t, s) + + l, _ := c.Get(ID) + captured := make(chan interface{}) + l.(*Service).AddListener(func(event int, ctx interface{}) { + if event == EventExecTTL { + close(captured) + } + }) + + go func() { c.Serve() }() + time.Sleep(time.Millisecond * 100) + defer c.Stop() + + req, err := http.NewRequest("GET", "http://localhost:6029", nil) + assert.NoError(t, err) + + r, err := http.DefaultClient.Do(req) + assert.NoError(t, err) + assert.Equal(t, 500, r.StatusCode) + + <-captured +} + func getPID(s interface{}) string { w := s.(*rrhttp.Service).Server().Workers()[0] return fmt.Sprintf("%v", *w.Pid) diff --git a/tests/http/stuck.php b/tests/http/stuck.php new file mode 100644 index 00000000..7df8b0f7 --- /dev/null +++ b/tests/http/stuck.php @@ -0,0 +1,11 @@ +<?php + +use \Psr\Http\Message\ServerRequestInterface; +use \Psr\Http\Message\ResponseInterface; + +function handleRequest(ServerRequestInterface $req, ResponseInterface $resp): ResponseInterface +{ + sleep(10); + $resp->getBody()->write(strtoupper($req->getQueryParams()['hello'])); + return $resp->withStatus(201); +}
\ No newline at end of file |