summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/pipe/pipe_factory_spawn_test.go490
-rw-r--r--pkg/socket/socket_factory_spawn_test.go507
-rw-r--r--pkg/worker_watcher/stack.go18
-rw-r--r--pkg/worker_watcher/stack_test.go129
-rwxr-xr-xpkg/worker_watcher/worker_watcher.go18
5 files changed, 1143 insertions, 19 deletions
diff --git a/pkg/pipe/pipe_factory_spawn_test.go b/pkg/pipe/pipe_factory_spawn_test.go
new file mode 100644
index 00000000..6decedf8
--- /dev/null
+++ b/pkg/pipe/pipe_factory_spawn_test.go
@@ -0,0 +1,490 @@
+package pipe
+
+import (
+ "os/exec"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/interfaces/events"
+ "github.com/spiral/roadrunner/v2/internal"
+ "github.com/spiral/roadrunner/v2/pkg/payload"
+ workerImpl "github.com/spiral/roadrunner/v2/pkg/worker"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_GetState2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ assert.Equal(t, internal.StateStopped, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, internal.StateReady, w.State().Value())
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Kill2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ assert.Error(t, w.Wait())
+ assert.Equal(t, internal.StateErrored, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, internal.StateReady, w.State().Value())
+ err = w.Kill()
+ if err != nil {
+ t.Errorf("error killing the Process: error %v", err)
+ }
+ wg.Wait()
+}
+
+func Test_Pipe_Start2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+
+ assert.NoError(t, w.Stop())
+}
+
+func Test_Pipe_StartError2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ err := cmd.Start()
+ if err != nil {
+ t.Errorf("error running the command: error %v", err)
+ }
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_PipeError3(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ _, err := cmd.StdinPipe()
+ if err != nil {
+ t.Errorf("error creating the STDIN pipe: error %v", err)
+ }
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_PipeError4(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ _, err := cmd.StdinPipe()
+ if err != nil {
+ t.Errorf("error creating the STDIN pipe: error %v", err)
+ }
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_Failboot2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/failboot.php")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+
+ assert.Nil(t, w)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "failboot")
+}
+
+func Test_Pipe_Invalid2t(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/invalid.php")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_Echo2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.NoError(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Body)
+ assert.Empty(t, res.Context)
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_Pipe_Broken2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.Error(t, err)
+ }()
+
+ sw, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.Error(t, err)
+ assert.Nil(t, res.Body)
+ assert.Nil(t, res.Context)
+}
+
+func Benchmark_Pipe_SpawnWorker_Stop2(b *testing.B) {
+ f := NewPipeFactory()
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ w, _ := f.SpawnWorker(cmd)
+ go func() {
+ if w.Wait() != nil {
+ b.Fail()
+ }
+ }()
+
+ err := w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the worker: error %v", err)
+ }
+ }
+}
+
+func Benchmark_Pipe_Worker_ExecEcho2(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+ sw, err := workerImpl.From(w)
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+ go func() {
+ err := w.Wait()
+ if err != nil {
+ b.Errorf("error waiting the worker: error %v", err)
+ }
+ }()
+ defer func() {
+ err := w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the worker: error %v", err)
+ }
+ }()
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Benchmark_Pipe_Worker_ExecEcho4(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := workerImpl.From(w)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Benchmark_Pipe_Worker_ExecEchoWithoutContext2(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := workerImpl.From(w)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Test_Echo2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory().SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+ go func() {
+ assert.NoError(t, syncWorker.Wait())
+ }()
+ defer func() {
+ err := syncWorker.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.Nil(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Body)
+ assert.Empty(t, res.Context)
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_BadPayload2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ go func() {
+ assert.NoError(t, syncWorker.Wait())
+ }()
+ defer func() {
+ err := syncWorker.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := syncWorker.Exec(payload.Payload{})
+
+ assert.Error(t, err)
+ assert.Nil(t, res.Body)
+ assert.Nil(t, res.Context)
+
+ assert.Contains(t, err.Error(), "payload can not be empty")
+}
+
+func Test_String2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+ defer func() {
+ err := w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ assert.Contains(t, w.String(), "php ../../tests/client.php echo pipes")
+ assert.Contains(t, w.String(), "ready")
+ assert.Contains(t, w.String(), "numExecs: 0")
+}
+
+func Test_Echo_Slow2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "pipes", "10", "10")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+ defer func() {
+ err := w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.Nil(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Body)
+ assert.Empty(t, res.Context)
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_Broken2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes")
+ data := ""
+ mu := &sync.Mutex{}
+ listener := func(event interface{}) {
+ if wev, ok := event.(events.WorkerEvent); ok {
+ mu.Lock()
+ data = string(wev.Payload.([]byte))
+ mu.Unlock()
+ }
+ }
+
+ w, err := NewPipeFactory().SpawnWorker(cmd, listener)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res.Body)
+ assert.Nil(t, res.Context)
+
+ time.Sleep(time.Second * 3)
+ mu.Lock()
+ if strings.ContainsAny(data, "undefined_function()") == false {
+ t.Fail()
+ }
+ mu.Unlock()
+ assert.Error(t, w.Stop())
+}
+
+func Test_Error2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "error", "pipes")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+
+ defer func() {
+ err := w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res.Body)
+ assert.Nil(t, res.Context)
+
+ if errors.Is(errors.SoftJob, err) == false {
+ t.Fatal("error should be of type errors.ErrSoftJob")
+ }
+ assert.Contains(t, err.Error(), "hello")
+}
+
+func Test_NumExecs2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory().SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+ defer func() {
+ err := w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ syncWorker, err := workerImpl.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, int64(1), w.State().NumExecs())
+
+ _, err = syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, int64(2), w.State().NumExecs())
+
+ _, err = syncWorker.Exec(payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, int64(3), w.State().NumExecs())
+}
diff --git a/pkg/socket/socket_factory_spawn_test.go b/pkg/socket/socket_factory_spawn_test.go
new file mode 100644
index 00000000..2f21e408
--- /dev/null
+++ b/pkg/socket/socket_factory_spawn_test.go
@@ -0,0 +1,507 @@
+package socket
+
+import (
+ "net"
+ "os/exec"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/spiral/roadrunner/v2/pkg/payload"
+ "github.com/spiral/roadrunner/v2/pkg/worker"
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_Tcp_Start2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Tcp_StartCloseFactory2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ f := NewSocketServer(ls, time.Minute)
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+
+ w, err := f.SpawnWorker(cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Tcp_StartError2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ err = cmd.Start()
+ if err != nil {
+ t.Errorf("error executing the command: error %v", err)
+ }
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Failboot2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err3 := ls.Close()
+ if err3 != nil {
+ t.Errorf("error closing the listener: error %v", err3)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/failboot.php")
+
+ w, err2 := NewSocketServer(ls, time.Second*5).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err2)
+ assert.Contains(t, err2.Error(), "failboot")
+}
+
+func Test_Tcp_Invalid2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/invalid.php")
+
+ w, err := NewSocketServer(ls, time.Second*1).SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Broken2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "tcp")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ err := w.Wait()
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "undefined_function()")
+ }()
+
+ defer func() {
+ time.Sleep(time.Second)
+ err2 := w.Stop()
+ // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection
+ assert.Error(t, err2)
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+ assert.Error(t, err)
+ assert.Nil(t, res.Body)
+ assert.Nil(t, res.Context)
+ wg.Wait()
+}
+
+func Test_Tcp_Echo2(t *testing.T) {
+ ls, err := net.Listen("tcp", "localhost:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ w, _ := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.NoError(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Body)
+ assert.Empty(t, res.Context)
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_Unix_Start2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Unix_Failboot2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/failboot.php")
+
+ w, err := NewSocketServer(ls, time.Second*5).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "failboot")
+}
+
+func Test_Unix_Timeout2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "unix", "200", "0")
+
+ w, err := NewSocketServer(ls, time.Millisecond*100).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "relay timeout")
+}
+
+func Test_Unix_Invalid2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/invalid.php")
+
+ w, err := NewSocketServer(ls, time.Second*10).SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Unix_Broken2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ err := w.Wait()
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "undefined_function()")
+ }()
+
+ defer func() {
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.Error(t, err)
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.Error(t, err)
+ assert.Nil(t, res.Context)
+ assert.Nil(t, res.Body)
+ wg.Wait()
+}
+
+func Test_Unix_Echo2(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(t, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(t, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ res, err := sw.Exec(payload.Payload{Body: []byte("hello")})
+
+ assert.NoError(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Body)
+ assert.Empty(t, res.Context)
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Benchmark_Tcp_SpawnWorker_Stop2(b *testing.B) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(b, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(b, err)
+ }()
+
+ f := NewSocketServer(ls, time.Minute)
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ w, err := f.SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+ go func() {
+ assert.NoError(b, w.Wait())
+ }()
+
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }
+}
+
+func Benchmark_Tcp_Worker_ExecEcho2(b *testing.B) {
+ ls, err := net.Listen("unix", "sock.unix")
+ assert.NoError(b, err)
+ defer func() {
+ err := ls.Close()
+ assert.NoError(b, err)
+ }()
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Benchmark_Unix_SpawnWorker_Stop2(b *testing.B) {
+ defer func() {
+ _ = syscall.Unlink("sock.unix")
+ }()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ b.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ b.Skip("socket is busy")
+ }
+
+ f := NewSocketServer(ls, time.Minute)
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "unix")
+
+ w, err := f.SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }
+}
+
+func Benchmark_Unix_Worker_ExecEcho2(b *testing.B) {
+ defer func() {
+ _ = syscall.Unlink("sock.unix")
+ }()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ defer func() {
+ err := ls.Close()
+ if err != nil {
+ b.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ b.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute).SpawnWorker(cmd)
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer func() {
+ err = w.Stop()
+ if err != nil {
+ b.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ sw, err := worker.From(w)
+ if err != nil {
+ b.Fatal(err)
+ }
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
diff --git a/pkg/worker_watcher/stack.go b/pkg/worker_watcher/stack.go
index 55f1f52a..788750dc 100644
--- a/pkg/worker_watcher/stack.go
+++ b/pkg/worker_watcher/stack.go
@@ -10,17 +10,19 @@ import (
)
type Stack struct {
- workers []worker.BaseProcess
- mutex sync.RWMutex
- destroy bool
- actualNumOfWorkers int64
+ workers []worker.BaseProcess
+ mutex sync.RWMutex
+ destroy bool
+ actualNumOfWorkers uint64
+ initialNumOfWorkers uint64
}
-func NewWorkersStack() *Stack {
+func NewWorkersStack(initialNumOfWorkers uint64) *Stack {
w := runtime.NumCPU()
return &Stack{
- workers: make([]worker.BaseProcess, 0, w),
- actualNumOfWorkers: 0,
+ workers: make([]worker.BaseProcess, 0, w),
+ actualNumOfWorkers: 0,
+ initialNumOfWorkers: initialNumOfWorkers,
}
}
@@ -113,7 +115,7 @@ func (stack *Stack) Destroy(ctx context.Context) {
case <-tt.C:
stack.mutex.Lock()
// that might be one of the workers is working
- if len(stack.workers) != int(stack.actualNumOfWorkers) {
+ if stack.initialNumOfWorkers != stack.actualNumOfWorkers {
stack.mutex.Unlock()
continue
}
diff --git a/pkg/worker_watcher/stack_test.go b/pkg/worker_watcher/stack_test.go
new file mode 100644
index 00000000..86af2043
--- /dev/null
+++ b/pkg/worker_watcher/stack_test.go
@@ -0,0 +1,129 @@
+package worker_watcher //nolint:golint,stylecheck
+import (
+ "context"
+ "os/exec"
+ "testing"
+ "time"
+
+ "github.com/spiral/roadrunner/v2/interfaces/worker"
+ workerImpl "github.com/spiral/roadrunner/v2/pkg/worker"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNewWorkersStack(t *testing.T) {
+ stack := NewWorkersStack(0)
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+ assert.Equal(t, []worker.BaseProcess{}, stack.workers)
+}
+
+func TestStack_Push(t *testing.T) {
+ stack := NewWorkersStack(1)
+
+ w, err := workerImpl.InitBaseWorker(&exec.Cmd{})
+ assert.NoError(t, err)
+
+ stack.Push(w)
+ assert.Equal(t, uint64(1), stack.actualNumOfWorkers)
+}
+
+func TestStack_Pop(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+
+ stack.Push(w)
+ assert.Equal(t, uint64(1), stack.actualNumOfWorkers)
+
+ _, _ = stack.Pop()
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+}
+
+func TestStack_FindAndRemoveByPid(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+
+ assert.NoError(t, w.Start())
+
+ stack.Push(w)
+ assert.Equal(t, uint64(1), stack.actualNumOfWorkers)
+
+ stack.FindAndRemoveByPid(w.Pid())
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+}
+
+func TestStack_IsEmpty(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+
+ stack.Push(w)
+ assert.Equal(t, uint64(1), stack.actualNumOfWorkers)
+
+ assert.Equal(t, false, stack.IsEmpty())
+}
+
+func TestStack_Workers(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+ assert.NoError(t, w.Start())
+
+ stack.Push(w)
+
+ wrks := stack.Workers()
+ assert.Equal(t, 1, len(wrks))
+ assert.Equal(t, w.Pid(), wrks[0].Pid())
+}
+
+func TestStack_Reset(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+ assert.NoError(t, w.Start())
+
+ stack.Push(w)
+ assert.Equal(t, uint64(1), stack.actualNumOfWorkers)
+ stack.Reset()
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+}
+
+func TestStack_Destroy(t *testing.T) {
+ stack := NewWorkersStack(1)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+ assert.NoError(t, w.Start())
+
+ stack.Push(w)
+ stack.Destroy(context.Background())
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+}
+
+func TestStack_DestroyWithWait(t *testing.T) {
+ stack := NewWorkersStack(2)
+ cmd := exec.Command("php", "../tests/client.php", "echo", "pipes")
+ w, err := workerImpl.InitBaseWorker(cmd)
+ assert.NoError(t, err)
+ assert.NoError(t, w.Start())
+
+ stack.Push(w)
+ stack.Push(w)
+ assert.Equal(t, uint64(2), stack.actualNumOfWorkers)
+
+ go func() {
+ wrk, _ := stack.Pop()
+ time.Sleep(time.Second * 3)
+ stack.Push(wrk)
+ }()
+ time.Sleep(time.Second)
+ stack.Destroy(context.Background())
+ assert.Equal(t, uint64(0), stack.actualNumOfWorkers)
+}
diff --git a/pkg/worker_watcher/worker_watcher.go b/pkg/worker_watcher/worker_watcher.go
index 0c086d5f..bf1f2435 100755
--- a/pkg/worker_watcher/worker_watcher.go
+++ b/pkg/worker_watcher/worker_watcher.go
@@ -13,23 +13,19 @@ import (
// workerCreateFunc can be nil, but in that case, dead stack will not be replaced
func NewWorkerWatcher(allocator worker.Allocator, numWorkers int64, events events.Handler) worker.Watcher {
ww := &workerWatcher{
- stack: NewWorkersStack(),
- allocator: allocator,
- initialNumWorkers: numWorkers,
- actualNumWorkers: numWorkers,
- events: events,
+ stack: NewWorkersStack(uint64(numWorkers)),
+ allocator: allocator,
+ events: events,
}
return ww
}
type workerWatcher struct {
- mutex sync.RWMutex
- stack *Stack
- allocator worker.Allocator
- initialNumWorkers int64
- actualNumWorkers int64
- events events.Handler
+ mutex sync.RWMutex
+ stack *Stack
+ allocator worker.Allocator
+ events events.Handler
}
func (ww *workerWatcher) AddToWatch(workers []worker.BaseProcess) error {