From d4a391e5bc2bc6053fafd9af79986c9c86a88749 Mon Sep 17 00:00:00 2001 From: Alexander Pykavy Date: Tue, 5 Aug 2025 11:23:20 +0200 Subject: [PATCH 1/2] Install lsof & ping tools useful for development --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index e7abdc5..d9a2873 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./cmd/worker FROM ubuntu:noble +RUN apt-get update && apt-get install lsof iputils-ping -y + COPY --from=build /app /app EXPOSE 8080 From ecc71a833e02a96e83b29baac88c73fcd3e91c17 Mon Sep 17 00:00:00 2001 From: Alexander Pykavy Date: Tue, 5 Aug 2025 11:54:00 +0200 Subject: [PATCH 2/2] Handle client closed request situations --- tests/helper/stdout.go | 3 +++ tests/worker/start_task_test.go | 40 +++++++++++++++++++++++++++++++++ worker/api.go | 34 ++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/tests/helper/stdout.go b/tests/helper/stdout.go index a835c1e..48a998e 100644 --- a/tests/helper/stdout.go +++ b/tests/helper/stdout.go @@ -1,6 +1,7 @@ package helper import ( + "fmt" "io" "os" ) @@ -17,6 +18,8 @@ func CaptureStdout(f func()) string { f() + fmt.Println() // to flush data + w.Close() b, _ := io.ReadAll(r) diff --git a/tests/worker/start_task_test.go b/tests/worker/start_task_test.go index b27dbcf..1a9f659 100644 --- a/tests/worker/start_task_test.go +++ b/tests/worker/start_task_test.go @@ -1,6 +1,7 @@ package worker import ( + "context" "dirigeant/task" "dirigeant/tests/helper" "dirigeant/worker" @@ -9,6 +10,7 @@ import ( "net/http/httptest" "sync" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -105,3 +107,41 @@ func TestStartTask__AllButOneRequestsShouldFailIfCreatingTheSameTaskSimultaneous assert.Equal(t, 1, len(api.Worker.Tasks), "Tasks map should contain 1 task") assert.NotNil(t, api.Worker.Tasks[testTask.ID], "Persisted task ID should match the one from request") } + +func TestStartTask__ShouldHandleClientClosedRequest(t *testing.T) { + api := &worker.Api{ + Worker: &worker.Worker{ + Tasks: make(map[uuid.UUID]*task.Task), + }, + } + testTask := helper.PingTask("ping-task", "127.0.0.1") + ctx, cancel := context.WithCancel(context.TODO()) + + // 1 - Create a task + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + createRequest := helper.NewTaskPostRequest(testTask).WithContext(ctx) + createResponseRecorder := httptest.NewRecorder() + + stdout := helper.CaptureStdout(func() { + api.HandleCreateTask(createResponseRecorder, createRequest) + }) + + assert.Equal(t, 499, createResponseRecorder.Code, "Response status code should be 499 Client Closed Request") + assert.Equal(t, "Error when executing the task: client closed request", createResponseRecorder.Body.String(), "Response body should contain error message") + assert.NotEmpty(t, stdout, "Task logs shouldn't be empty") + assert.Empty(t, api.Worker.Tasks, "Tasks map should be empty") + }() + + time.Sleep(1 * time.Second) + + assert.Equal(t, 1, len(api.Worker.Tasks), "Tasks map should contain 1 task") + + // 2 - Cancel a request + cancel() + + wg.Wait() +} diff --git a/worker/api.go b/worker/api.go index 3f42e6d..caa8d90 100644 --- a/worker/api.go +++ b/worker/api.go @@ -69,18 +69,34 @@ func (a *Api) HandleCreateTask(w http.ResponseWriter, r *http.Request) { return } - if err := a.Worker.StartTask(t); err != nil { - if errors.Is(err, task.ErrAlreadyExists) { - w.WriteHeader(http.StatusConflict) - } else { - w.WriteHeader(http.StatusInternalServerError) + errCh := make(chan error) + defer close(errCh) + go func() { + errCh <- a.Worker.StartTask(t) + }() + + select { + case <-r.Context().Done(): + a.Worker.StopTask(t.ID) + + <-errCh + + w.WriteHeader(499) // client closed request + fmt.Fprint(w, "Error when executing the task: client closed request") + case err := <-errCh: + if err != nil { + if errors.Is(err, task.ErrAlreadyExists) { + w.WriteHeader(http.StatusConflict) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + + fmt.Fprintf(w, "Error when executing the task: %v", err) + return } - fmt.Fprintf(w, "Error when executing the task: %v", err) - return + w.WriteHeader(http.StatusCreated) } - - w.WriteHeader(http.StatusCreated) } func (a *Api) HandleDeleteTask(w http.ResponseWriter, r *http.Request) {