summaryrefslogtreecommitdiff
path: root/ipc
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2022-01-12 00:19:48 +0300
committerValery Piashchynski <[email protected]>2022-01-12 00:19:48 +0300
commit215c7c91937bf65704db18a59a327a3c64e43530 (patch)
tree85d2b32ddf7230064e620bb59f76ef4bfd6b24cf /ipc
parent7b5d220f0f1be155d83d887cd4996bdf4394c570 (diff)
pass logger from the factory
Signed-off-by: Valery Piashchynski <[email protected]>
Diffstat (limited to 'ipc')
-rw-r--r--ipc/interface.go20
-rwxr-xr-xipc/pipe/pipe_factory.go178
-rw-r--r--ipc/pipe/pipe_factory_spawn_test.go430
-rwxr-xr-xipc/pipe/pipe_factory_test.go511
-rwxr-xr-xipc/socket/socket_factory.go252
-rw-r--r--ipc/socket/socket_factory_spawn_test.go485
-rwxr-xr-xipc/socket/socket_factory_test.go609
7 files changed, 2485 insertions, 0 deletions
diff --git a/ipc/interface.go b/ipc/interface.go
new file mode 100644
index 00000000..1d70cd21
--- /dev/null
+++ b/ipc/interface.go
@@ -0,0 +1,20 @@
+package ipc
+
+import (
+ "context"
+ "os/exec"
+
+ "github.com/spiral/roadrunner/v2/worker"
+)
+
+// Factory is responsible for wrapping given command into tasks WorkerProcess.
+type Factory interface {
+ // SpawnWorkerWithTimeout creates new WorkerProcess process based on given command with context.
+ // Process must not be started.
+ SpawnWorkerWithTimeout(context.Context, *exec.Cmd) (*worker.Process, error)
+ // SpawnWorker creates new WorkerProcess process based on given command.
+ // Process must not be started.
+ SpawnWorker(*exec.Cmd) (*worker.Process, error)
+ // Close the factory and underlying connections.
+ Close() error
+}
diff --git a/ipc/pipe/pipe_factory.go b/ipc/pipe/pipe_factory.go
new file mode 100755
index 00000000..4a3c9a67
--- /dev/null
+++ b/ipc/pipe/pipe_factory.go
@@ -0,0 +1,178 @@
+package pipe
+
+import (
+ "context"
+ "os/exec"
+
+ "github.com/spiral/goridge/v3/pkg/pipe"
+ "github.com/spiral/roadrunner/v2/internal"
+ "github.com/spiral/roadrunner/v2/worker"
+ "go.uber.org/zap"
+)
+
+// Factory connects to stack using standard
+// streams (STDIN, STDOUT pipes).
+type Factory struct {
+ log *zap.Logger
+}
+
+// NewPipeFactory returns new factory instance and starts
+// listening
+func NewPipeFactory(log *zap.Logger) *Factory {
+ return &Factory{
+ log: log,
+ }
+}
+
+type sr struct {
+ w *worker.Process
+ err error
+}
+
+// SpawnWorkerWithTimeout creates new Process and connects it to goridge relay,
+// method Wait() must be handled on level above.
+func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd) (*worker.Process, error) {
+ spCh := make(chan sr)
+ go func() {
+ w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log))
+ if err != nil {
+ select {
+ case spCh <- sr{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ in, err := cmd.StdoutPipe()
+ if err != nil {
+ select {
+ case spCh <- sr{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ out, err := cmd.StdinPipe()
+ if err != nil {
+ select {
+ case spCh <- sr{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ // Init new PIPE relay
+ relay := pipe.NewPipeRelay(in, out)
+ w.AttachRelay(relay)
+
+ // Start the worker
+ err = w.Start()
+ if err != nil {
+ select {
+ case spCh <- sr{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ // used as a ping
+ _, err = internal.Pid(relay)
+ if err != nil {
+ _ = w.Kill()
+ select {
+ case spCh <- sr{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ _ = w.Kill()
+ return
+ }
+ }
+
+ select {
+ case
+ // return worker
+ spCh <- sr{
+ w: w,
+ err: nil,
+ }:
+ // everything ok, set ready state
+ w.State().Set(worker.StateReady)
+ return
+ default:
+ _ = w.Kill()
+ return
+ }
+ }()
+
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case res := <-spCh:
+ if res.err != nil {
+ return nil, res.err
+ }
+ return res.w, nil
+ }
+}
+
+func (f *Factory) SpawnWorker(cmd *exec.Cmd) (*worker.Process, error) {
+ w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log))
+ if err != nil {
+ return nil, err
+ }
+
+ in, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ out, err := cmd.StdinPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ // Init new PIPE relay
+ relay := pipe.NewPipeRelay(in, out)
+ w.AttachRelay(relay)
+
+ // Start the worker
+ err = w.Start()
+ if err != nil {
+ return nil, err
+ }
+
+ // errors bundle
+ _, err = internal.Pid(relay)
+ if err != nil {
+ _ = w.Kill()
+ return nil, err
+ }
+
+ // everything ok, set ready state
+ w.State().Set(worker.StateReady)
+ return w, nil
+}
+
+// Close the factory.
+func (f *Factory) Close() error {
+ return nil
+}
diff --git a/ipc/pipe/pipe_factory_spawn_test.go b/ipc/pipe/pipe_factory_spawn_test.go
new file mode 100644
index 00000000..2ce5a257
--- /dev/null
+++ b/ipc/pipe/pipe_factory_spawn_test.go
@@ -0,0 +1,430 @@
+package pipe
+
+import (
+ "os/exec"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/payload"
+ "github.com/spiral/roadrunner/v2/worker"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+)
+
+var log = zap.NewNop()
+
+func Test_GetState2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorker(cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ assert.Equal(t, worker.StateStopped, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, worker.StateReady, w.State().Value())
+ assert.NoError(t, w.Stop())
+}
+
+func Test_Kill2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorker(cmd)
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ assert.Error(t, w.Wait())
+ assert.Equal(t, worker.StateErrored, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, worker.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(log).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(log).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(log).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(log).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(log).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+}
+
+func Test_Pipe_Invalid2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/invalid.php")
+ w, err := NewPipeFactory(log).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(log).SpawnWorker(cmd)
+ assert.NoError(t, err)
+
+ sw := worker.From(w)
+ 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)
+
+ go func() {
+ if w.Wait() != nil {
+ t.Fail()
+ }
+ }()
+
+ assert.Equal(t, "hello", res.String())
+ err = w.Stop()
+ assert.NoError(t, err)
+}
+
+func Test_Pipe_Broken2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes")
+ w, err := NewPipeFactory(log).SpawnWorker(cmd)
+ assert.NoError(t, err)
+ require.NotNil(t, w)
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.Error(t, err)
+ assert.Nil(t, res)
+
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.Error(t, err)
+}
+
+func Benchmark_Pipe_SpawnWorker_Stop2(b *testing.B) {
+ f := NewPipeFactory(log)
+ 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(log).SpawnWorker(cmd)
+ sw := worker.From(w)
+
+ 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(log).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 := worker.From(w)
+
+ 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(log).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 := worker.From(w)
+
+ 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(log).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sw := worker.From(w)
+
+ go func() {
+ assert.NoError(t, sw.Wait())
+ }()
+ defer func() {
+ err = sw.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := sw.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(log).SpawnWorker(cmd)
+
+ sw := worker.From(w)
+
+ go func() {
+ assert.NoError(t, sw.Wait())
+ }()
+ defer func() {
+ err := sw.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := sw.Exec(&payload.Payload{})
+ assert.Error(t, err)
+ assert.Nil(t, res)
+
+ 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(log).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(), "num_execs: 0")
+}
+
+func Test_Echo_Slow2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "pipes", "10", "10")
+
+ w, _ := NewPipeFactory(log).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 := worker.From(w)
+
+ res, err := sw.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")
+
+ w, err := NewPipeFactory(log).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res)
+
+ time.Sleep(time.Second * 3)
+ assert.Error(t, w.Stop())
+}
+
+func Test_Error2(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/client.php", "error", "pipes")
+
+ w, _ := NewPipeFactory(log).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 := worker.From(w)
+
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res)
+
+ 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(log).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 := worker.From(w)
+
+ _, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(1), w.State().NumExecs())
+
+ _, err = sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(2), w.State().NumExecs())
+
+ _, err = sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(3), w.State().NumExecs())
+}
diff --git a/ipc/pipe/pipe_factory_test.go b/ipc/pipe/pipe_factory_test.go
new file mode 100755
index 00000000..025498b5
--- /dev/null
+++ b/ipc/pipe/pipe_factory_test.go
@@ -0,0 +1,511 @@
+package pipe
+
+import (
+ "context"
+ "os/exec"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/spiral/errors"
+ "github.com/spiral/roadrunner/v2/payload"
+ "github.com/spiral/roadrunner/v2/worker"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_GetState(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ go func() {
+ assert.NoError(t, w.Wait())
+ assert.Equal(t, worker.StateStopped, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, worker.StateReady, w.State().Value())
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Kill(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ assert.Error(t, w.Wait())
+ assert.Equal(t, worker.StateErrored, w.State().Value())
+ }()
+
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ assert.Equal(t, worker.StateReady, w.State().Value())
+ err = w.Kill()
+ if err != nil {
+ t.Errorf("error killing the Process: error %v", err)
+ }
+ wg.Wait()
+}
+
+func Test_Pipe_Start(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ go func() {
+ assert.NoError(t, w.Wait())
+ }()
+
+ assert.NoError(t, w.Stop())
+}
+
+func Test_Pipe_StartError(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ err := cmd.Start()
+ if err != nil {
+ t.Errorf("error running the command: error %v", err)
+ }
+
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_PipeError(t *testing.T) {
+ t.Parallel()
+ 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)
+ }
+
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_PipeError2(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ // error cause
+ _, err := cmd.StdinPipe()
+ if err != nil {
+ t.Errorf("error creating the STDIN pipe: error %v", err)
+ }
+
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_Failboot(t *testing.T) {
+ cmd := exec.Command("php", "../../tests/failboot.php")
+ ctx := context.Background()
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+
+ assert.Nil(t, w)
+ assert.Error(t, err)
+}
+
+func Test_Pipe_Invalid(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("php", "../../tests/invalid.php")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Pipe_Echo(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+ 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)
+
+ go func() {
+ if w.Wait() != nil {
+ t.Fail()
+ }
+ }()
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_Pipe_Echo_Script(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("sh", "../../tests/pipes_test_script.sh")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+ 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)
+
+ go func() {
+ if w.Wait() != nil {
+ t.Fail()
+ }
+ }()
+
+ assert.Equal(t, "hello", res.String())
+}
+
+func Test_Pipe_Broken(t *testing.T) {
+ t.Parallel()
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ require.NoError(t, err)
+ require.NotNil(t, w)
+
+ go func() {
+ errW := w.Wait()
+ require.Error(t, errW)
+ }()
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.Error(t, err)
+ assert.Nil(t, res)
+
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.NoError(t, err)
+}
+
+func Benchmark_Pipe_SpawnWorker_Stop(b *testing.B) {
+ f := NewPipeFactory(log)
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ w, _ := f.SpawnWorkerWithTimeout(context.Background(), 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_ExecEcho(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(context.Background(), cmd)
+ sw := worker.From(w)
+
+ 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_ExecEcho3(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Benchmark_Pipe_Worker_ExecEchoWithoutContext(b *testing.B) {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+ ctx := context.Background()
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Test_Echo(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sw := worker.From(w)
+ go func() {
+ assert.NoError(t, sw.Wait())
+ }()
+ defer func() {
+ err = sw.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := sw.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_BadPayload(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+
+ sw := worker.From(w)
+
+ go func() {
+ assert.NoError(t, sw.Wait())
+ }()
+ defer func() {
+ err := sw.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+ }()
+
+ res, err := sw.Exec(&payload.Payload{})
+
+ assert.Error(t, err)
+ assert.Nil(t, res)
+
+ assert.Contains(t, err.Error(), "payload can not be empty")
+}
+
+func Test_String(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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(), "num_execs: 0")
+}
+
+func Test_Echo_Slow(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/slow-client.php", "echo", "pipes", "10", "10")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ res, err := sw.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_Broken(t *testing.T) {
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "pipes")
+
+ w, err := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sw := worker.From(w)
+
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res)
+
+ time.Sleep(time.Second * 3)
+ assert.Error(t, w.Stop())
+}
+
+func Test_Error(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "error", "pipes")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.NotNil(t, err)
+ assert.Nil(t, res)
+
+ if errors.Is(errors.SoftJob, err) == false {
+ t.Fatal("error should be of type errors.ErrSoftJob")
+ }
+ assert.Contains(t, err.Error(), "hello")
+}
+
+func Test_NumExecs(t *testing.T) {
+ t.Parallel()
+ ctx := context.Background()
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "pipes")
+
+ w, _ := NewPipeFactory(log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ _, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(1), w.State().NumExecs())
+
+ _, err = sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(2), w.State().NumExecs())
+
+ _, err = sw.Exec(&payload.Payload{Body: []byte("hello")})
+ if err != nil {
+ t.Errorf("fail to execute payload: error %v", err)
+ }
+ assert.Equal(t, uint64(3), w.State().NumExecs())
+}
diff --git a/ipc/socket/socket_factory.go b/ipc/socket/socket_factory.go
new file mode 100755
index 00000000..aa356424
--- /dev/null
+++ b/ipc/socket/socket_factory.go
@@ -0,0 +1,252 @@
+package socket
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "os/exec"
+ "sync"
+ "time"
+
+ "github.com/shirou/gopsutil/process"
+ "github.com/spiral/errors"
+ "github.com/spiral/goridge/v3/pkg/relay"
+ "github.com/spiral/goridge/v3/pkg/socket"
+ "github.com/spiral/roadrunner/v2/internal"
+ "github.com/spiral/roadrunner/v2/worker"
+ "go.uber.org/zap"
+
+ "golang.org/x/sync/errgroup"
+)
+
+// Factory connects to external stack using socket server.
+type Factory struct {
+ // listens for incoming connections from underlying processes
+ ls net.Listener
+
+ // relay connection timeout
+ tout time.Duration
+
+ // sockets which are waiting for process association
+ relays sync.Map
+
+ log *zap.Logger
+}
+
+// NewSocketServer returns Factory attached to a given socket listener.
+// tout specifies for how long factory should serve for incoming relay connection
+func NewSocketServer(ls net.Listener, tout time.Duration, log *zap.Logger) *Factory {
+ f := &Factory{
+ ls: ls,
+ tout: tout,
+ relays: sync.Map{},
+ log: log,
+ }
+
+ // Be careful
+ // https://github.com/go101/go101/wiki/About-memory-ordering-guarantees-made-by-atomic-operations-in-Go
+ // https://github.com/golang/go/issues/5045
+ go func() {
+ err := f.listen()
+ // there is no logger here, use fmt
+ if err != nil {
+ fmt.Printf("[WARN]: socket server listen, error: %v\n", err)
+ }
+ }()
+
+ return f
+}
+
+// blocking operation, returns an error
+func (f *Factory) listen() error {
+ errGr := &errgroup.Group{}
+ errGr.Go(func() error {
+ for {
+ conn, err := f.ls.Accept()
+ if err != nil {
+ return err
+ }
+
+ rl := socket.NewSocketRelay(conn)
+ pid, err := internal.Pid(rl)
+ if err != nil {
+ return err
+ }
+
+ f.attachRelayToPid(pid, rl)
+ }
+ })
+
+ return errGr.Wait()
+}
+
+type socketSpawn struct {
+ w *worker.Process
+ err error
+}
+
+// SpawnWorkerWithTimeout creates Process and connects it to appropriate relay or return an error
+func (f *Factory) SpawnWorkerWithTimeout(ctx context.Context, cmd *exec.Cmd) (*worker.Process, error) {
+ c := make(chan socketSpawn)
+ go func() {
+ ctxT, cancel := context.WithTimeout(ctx, f.tout)
+ defer cancel()
+ w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log))
+ if err != nil {
+ select {
+ case c <- socketSpawn{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ err = w.Start()
+ if err != nil {
+ select {
+ case c <- socketSpawn{
+ w: nil,
+ err: err,
+ }:
+ return
+ default:
+ return
+ }
+ }
+
+ rl, err := f.findRelayWithContext(ctxT, w)
+ if err != nil {
+ _ = w.Kill()
+ select {
+ // try to write result
+ case c <- socketSpawn{
+ w: nil,
+ err: err,
+ }:
+ return
+ // if no receivers - return
+ default:
+ return
+ }
+ }
+
+ w.AttachRelay(rl)
+ w.State().Set(worker.StateReady)
+
+ select {
+ case c <- socketSpawn{
+ w: w,
+ err: nil,
+ }:
+ return
+ default:
+ _ = w.Kill()
+ return
+ }
+ }()
+
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case res := <-c:
+ if res.err != nil {
+ return nil, res.err
+ }
+
+ return res.w, nil
+ }
+}
+
+func (f *Factory) SpawnWorker(cmd *exec.Cmd) (*worker.Process, error) {
+ w, err := worker.InitBaseWorker(cmd, worker.WithLog(f.log))
+ if err != nil {
+ return nil, err
+ }
+
+ err = w.Start()
+ if err != nil {
+ return nil, err
+ }
+
+ rl, err := f.findRelay(w)
+ if err != nil {
+ _ = w.Kill()
+ return nil, err
+ }
+
+ w.AttachRelay(rl)
+
+ // errors bundle
+ _, err = internal.Pid(rl)
+ if err != nil {
+ _ = w.Kill()
+ return nil, err
+ }
+
+ w.State().Set(worker.StateReady)
+
+ return w, nil
+}
+
+// Close socket factory and underlying socket connection.
+func (f *Factory) Close() error {
+ return f.ls.Close()
+}
+
+// waits for Process to connect over socket and returns associated relay of timeout
+func (f *Factory) findRelayWithContext(ctx context.Context, w worker.BaseProcess) (*socket.Relay, error) {
+ ticker := time.NewTicker(time.Millisecond * 10)
+ for {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ case <-ticker.C:
+ // check for the process exists
+ _, err := process.NewProcess(int32(w.Pid()))
+ if err != nil {
+ return nil, err
+ }
+ default:
+ // find first pid and attach relay to it
+ var r *socket.Relay
+ f.relays.Range(func(k, val interface{}) bool {
+ r = val.(*socket.Relay)
+ f.relays.Delete(k)
+ return false
+ })
+
+ // no relay exists
+ if r == nil {
+ continue
+ }
+
+ return r, nil
+ }
+ }
+}
+
+func (f *Factory) findRelay(w worker.BaseProcess) (*socket.Relay, error) {
+ const op = errors.Op("factory_find_relay")
+ // poll every 1ms for the relay
+ pollDone := time.NewTimer(f.tout)
+ for {
+ select {
+ case <-pollDone.C:
+ return nil, errors.E(op, errors.Str("relay timeout"))
+ default:
+ tmp, ok := f.relays.Load(w.Pid())
+ if !ok {
+ continue
+ }
+ return tmp.(*socket.Relay), nil
+ }
+ }
+}
+
+// chan to store relay associated with specific pid
+func (f *Factory) attachRelayToPid(pid int64, relay relay.Relay) {
+ f.relays.Store(pid, relay)
+}
diff --git a/ipc/socket/socket_factory_spawn_test.go b/ipc/socket/socket_factory_spawn_test.go
new file mode 100644
index 00000000..36c6cce2
--- /dev/null
+++ b/ipc/socket/socket_factory_spawn_test.go
@@ -0,0 +1,485 @@
+package socket
+
+import (
+ "net"
+ "os/exec"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "github.com/spiral/roadrunner/v2/payload"
+ "github.com/spiral/roadrunner/v2/worker"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.uber.org/zap"
+)
+
+var log = zap.NewNop()
+
+func Test_Tcp_Start2(t *testing.T) {
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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, log).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", "127.0.0.1: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, log)
+ 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)
+
+ go func() {
+ require.NoError(t, w.Wait())
+ }()
+
+ 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", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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, log).SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Failboot2(t *testing.T) {
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err2)
+}
+
+func Test_Tcp_Invalid2(t *testing.T) {
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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, log).SpawnWorker(cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Broken2(t *testing.T) {
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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, log).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ errW := w.Wait()
+ assert.Error(t, errW)
+ }()
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.Error(t, err)
+ assert.Nil(t, res)
+ wg.Wait()
+
+ time.Sleep(time.Second)
+ err2 := w.Stop()
+ // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection
+ // but process exited
+ assert.NoError(t, err2)
+}
+
+func Test_Tcp_Echo2(t *testing.T) {
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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, log).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 := worker.From(w)
+
+ 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, log).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, log).SpawnWorker(cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+}
+
+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, log).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, log).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() {
+ errC := ls.Close()
+ assert.NoError(t, errC)
+ }()
+
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute, log).SpawnWorker(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ errW := w.Wait()
+ assert.Error(t, errW)
+ }()
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+
+ assert.Error(t, err)
+ assert.Nil(t, res)
+ wg.Wait()
+
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.NoError(t, err)
+}
+
+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, log).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 := worker.From(w)
+
+ 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, log)
+ 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, log).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 := worker.From(w)
+
+ 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() {
+ errC := ls.Close()
+ if errC != nil {
+ b.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ b.Skip("socket is busy")
+ }
+
+ f := NewSocketServer(ls, time.Minute, log)
+ 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() {
+ errC := ls.Close()
+ if errC != 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, log).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 := worker.From(w)
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
diff --git a/ipc/socket/socket_factory_test.go b/ipc/socket/socket_factory_test.go
new file mode 100755
index 00000000..9b48d233
--- /dev/null
+++ b/ipc/socket/socket_factory_test.go
@@ -0,0 +1,609 @@
+package socket
+
+import (
+ "context"
+ "net"
+ "os/exec"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/spiral/roadrunner/v2/payload"
+ "github.com/spiral/roadrunner/v2/worker"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Tcp_Start(t *testing.T) {
+ ctx := context.Background()
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log).SpawnWorkerWithTimeout(ctx, 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_StartCloseFactory(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log)
+ defer func() {
+ err = ls.Close()
+ if err != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+
+ w, err := f.SpawnWorkerWithTimeout(ctx, cmd)
+ assert.NoError(t, err)
+ assert.NotNil(t, w)
+
+ go func() {
+ require.NoError(t, w.Wait())
+ }()
+
+ err = w.Stop()
+ if err != nil {
+ t.Errorf("error stopping the Process: error %v", err)
+ }
+}
+
+func Test_Tcp_StartError(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ defer cancel()
+
+ ls, err := net.Listen("tcp", "127.0.0.1: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)
+ }
+
+ serv := NewSocketServer(ls, time.Minute, log)
+ time.Sleep(time.Second * 2)
+ w, err := serv.SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Failboot(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err2)
+}
+
+func Test_Tcp_Timeout(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1: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/slow-client.php", "echo", "tcp", "200", "0")
+
+ w, err := NewSocketServer(ls, time.Millisecond*1, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "context deadline exceeded")
+}
+
+func Test_Tcp_Invalid(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Tcp_Broken(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ if assert.NoError(t, err) {
+ defer func() {
+ errC := ls.Close()
+ if errC != 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.Second*10, log).SpawnWorkerWithTimeout(ctx, cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ errW := w.Wait()
+ assert.Error(t, errW)
+ }()
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+ assert.Error(t, err)
+ assert.Nil(t, res)
+ wg.Wait()
+
+ time.Sleep(time.Second)
+ err2 := w.Stop()
+ // write tcp 127.0.0.1:9007->127.0.0.1:34204: use of closed network connection
+ // but process is stopped
+ assert.NoError(t, err2)
+}
+
+func Test_Tcp_Echo(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1: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, log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ 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_Tcp_Echo_Script(t *testing.T) {
+ time.Sleep(time.Millisecond * 10) // to ensure free socket
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1: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("sh", "../../tests/socket_test_script.sh")
+
+ w, _ := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ 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_Start(t *testing.T) {
+ ctx := context.Background()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ 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", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, 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_Failboot(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ ctx := context.Background()
+ if err == nil {
+ 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/failboot.php")
+
+ w, err := NewSocketServer(ls, time.Second*5, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+}
+
+func Test_Unix_Timeout(t *testing.T) {
+ ls, err := net.Listen("unix", "sock.unix")
+ ctx := context.Background()
+ if err == nil {
+ 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/slow-client.php", "echo", "unix", "200", "0")
+
+ w, err := NewSocketServer(ls, time.Millisecond*100, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Nil(t, w)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "context deadline exceeded")
+}
+
+func Test_Unix_Invalid(t *testing.T) {
+ ctx := context.Background()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ 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*10, log).SpawnWorkerWithTimeout(ctx, cmd)
+ assert.Error(t, err)
+ assert.Nil(t, w)
+}
+
+func Test_Unix_Broken(t *testing.T) {
+ ctx := context.Background()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ defer func() {
+ errC := ls.Close()
+ if errC != nil {
+ t.Errorf("error closing the listener: error %v", err)
+ }
+ }()
+ } else {
+ t.Skip("socket is busy")
+ }
+
+ cmd := exec.Command("php", "../../tests/client.php", "broken", "unix")
+ w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wg := &sync.WaitGroup{}
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ errW := w.Wait()
+ assert.Error(t, errW)
+ }()
+
+ sw := worker.From(w)
+ res, err := sw.Exec(&payload.Payload{Body: []byte("hello")})
+
+ assert.Error(t, err)
+ assert.Nil(t, res)
+
+ time.Sleep(time.Second)
+ err = w.Stop()
+ assert.NoError(t, err)
+
+ wg.Wait()
+}
+
+func Test_Unix_Echo(t *testing.T) {
+ ctx := context.Background()
+ ls, err := net.Listen("unix", "sock.unix")
+ if err == nil {
+ 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", "unix")
+
+ w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ 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_Stop(b *testing.B) {
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ 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, log)
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "tcp")
+
+ w, err := f.SpawnWorkerWithTimeout(ctx, 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_ExecEcho(b *testing.B) {
+ ctx := context.Background()
+ ls, err := net.Listen("tcp", "127.0.0.1:9007")
+ 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", "tcp")
+
+ w, err := NewSocketServer(ls, time.Minute, log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}
+
+func Benchmark_Unix_SpawnWorker_Stop(b *testing.B) {
+ ctx := context.Background()
+ 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, log)
+ for n := 0; n < b.N; n++ {
+ cmd := exec.Command("php", "../../tests/client.php", "echo", "unix")
+
+ w, err := f.SpawnWorkerWithTimeout(ctx, 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_ExecEcho(b *testing.B) {
+ ctx := context.Background()
+ 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, log).SpawnWorkerWithTimeout(ctx, 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 := worker.From(w)
+
+ for n := 0; n < b.N; n++ {
+ if _, err := sw.Exec(&payload.Payload{Body: []byte("hello")}); err != nil {
+ b.Fail()
+ }
+ }
+}