Repository: ovh/cds
Pull request #5821 (2021-05-19 16:18:24)

feat(sdk): restart goroutines
Signed-off-by: francois  samin <francois.samin@corp.ovh.com>


ovh-cds: 
CDS Report build-all-cds#16031.0 ✘  
* Build
  * Build and Package All ✔ 
  * Build Debpacker ✔ 
  * Build OS-Ansible-Inventory ✔ 
  * Build SMTPMock ✘  
  * Lint UI Translate ✔ 
  * Test CLI ✔ 
  * Test Engine ✘  
  * Test SDK ✔ 
  * Test SDK Rust ✔ 
  * Test UI ✔ 


Patch:
diff --git a/cli/cdsctl/events.go b/cli/cdsctl/events.go
index eb891377e..41aac8a81 100644
--- a/cli/cdsctl/events.go
+++ b/cli/cdsctl/events.go
@@ -60,8 +60,8 @@ func eventsListenRun(v cli.Values) error {
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
 
-	sdk.NewGoRoutines().Run(ctx, "WebsocketEventsListenCmd", func(ctx context.Context) {
-		client.WebsocketEventsListen(ctx, sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	sdk.NewGoRoutines(ctx).Run(ctx, "WebsocketEventsListenCmd", func(ctx context.Context) {
+		client.WebsocketEventsListen(ctx, sdk.NewGoRoutines(ctx), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	})
 
 	switch {
diff --git a/cli/cdsctl/workflow_log.go b/cli/cdsctl/workflow_log.go
index 047a53658..fe136ccfa 100644
--- a/cli/cdsctl/workflow_log.go
+++ b/cli/cdsctl/workflow_log.go
@@ -437,7 +437,7 @@ func workflowLogStreamRun(v cli.Values) error {
 	chanMsgReceived := make(chan json.RawMessage)
 	chanErrorReceived := make(chan error)
 
-	goRoutines := sdk.NewGoRoutines()
+	goRoutines := sdk.NewGoRoutines(ctx)
 	goRoutines.Exec(ctx, "WebsocketEventsListenCmd", func(ctx context.Context) {
 		for ctx.Err() == nil {
 			if err := client.RequestWebsocket(ctx, goRoutines, fmt.Sprintf("%s/item/stream", link.CDNURL), chanMessageToSend, chanMsgReceived, chanErrorReceived); err != nil {
diff --git a/cli/cdsctl/workflow_run.go b/cli/cdsctl/workflow_run.go
index 2cf092d72..63c953833 100644
--- a/cli/cdsctl/workflow_run.go
+++ b/cli/cdsctl/workflow_run.go
@@ -162,9 +162,7 @@ func workflowRunManualRun(v cli.Values) error {
 	}
 
 	if v.GetBool("sync") {
-		if _, err := client.WorkflowRunResync(v.GetString(_ProjectKey), v.GetString(_WorkflowName), runNumber); err != nil {
-			return cli.WrapError(err, "Cannot resync your workflow run %d ", runNumber)
-		}
+		manual.Resync = true
 	}
 
 	if v.GetString("node-name") != "" {
diff --git a/cli/cdsctl/workflow_run_result.go b/cli/cdsctl/workflow_run_result.go
index 3708feaec..a1edc390d 100644
--- a/cli/cdsctl/workflow_run_result.go
+++ b/cli/cdsctl/workflow_run_result.go
@@ -244,6 +244,7 @@ func toCLIRunResult(results []sdk.WorkflowRunResult) ([]RunResultCli, error) {
 				return nil, err
 			}
 			name = artiResult.Name
+			artiType = artiResult.RepoType
 		}
 
 		cliresults = append(cliresults, RunResultCli{
diff --git a/cli/cdsctl/workflow_transform_as_code.go b/cli/cdsctl/workflow_transform_as_code.go
index edcf6a098..2662cef13 100644
--- a/cli/cdsctl/workflow_transform_as_code.go
+++ b/cli/cdsctl/workflow_transform_as_code.go
@@ -53,8 +53,8 @@ func workflowTransformAsCodeRun(v cli.Values) (interface{}, error) {
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
 
-	sdk.NewGoRoutines().Run(ctx, "WebsocketEventsListenCmd", func(ctx context.Context) {
-		client.WebsocketEventsListen(ctx, sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	sdk.NewGoRoutines(ctx).Run(ctx, "WebsocketEventsListenCmd", func(ctx context.Context) {
+		client.WebsocketEventsListen(ctx, sdk.NewGoRoutines(ctx), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	})
 
 	ope, err := client.WorkflowTransformAsCode(projectKey, v.GetString(_WorkflowName), branch, message)
diff --git a/engine/api/api.go b/engine/api/api.go
index b9e9f4d38..3c2d2e959 100644
--- a/engine/api/api.go
+++ b/engine/api/api.go
@@ -545,7 +545,7 @@ func (a *API) Serve(ctx context.Context) error {
 	}
 
 	log.Info(ctx, "Initializing HTTP router")
-	a.GoRoutines = sdk.NewGoRoutines()
+	a.GoRoutines = sdk.NewGoRoutines(ctx)
 	a.Router = &Router{
 		Mux:        mux.NewRouter(),
 		Background: ctx,
@@ -652,12 +652,12 @@ func (a *API) Serve(ctx context.Context) error {
 		log.Error(ctx, "error while initializing event system: %s", err)
 	}
 
-	a.GoRoutines.Run(ctx, "event.dequeue", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "event.dequeue", func(ctx context.Context) {
 		event.DequeueEvent(ctx, a.mustDB())
 	})
 
 	log.Info(ctx, "Initializing internal routines...")
-	a.GoRoutines.Run(ctx, "maintenance.Subscribe", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "maintenance.Subscribe", func(ctx context.Context) {
 		if err := a.listenMaintenance(ctx); err != nil {
 			log.Error(ctx, "error while initializing listen maintenance routine: %s", err)
 		}
@@ -668,7 +668,7 @@ func (a *API) Serve(ctx context.Context) error {
 			log.Error(ctx, "error while initializing worker models routine: %s", err)
 		}
 	})
-	a.GoRoutines.Run(ctx, "worker.Initialize", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "worker.Initialize", func(ctx context.Context) {
 		if err := worker.Initialize(ctx, a.DBConnectionFactory.GetDBMap(gorpmapping.Mapper), a.Cache); err != nil {
 			log.Error(ctx, "error while initializing workers routine: %s", err)
 		}
@@ -684,25 +684,25 @@ func (a *API) Serve(ctx context.Context) error {
 	a.GoRoutines.Run(ctx, "audit.ComputeWorkflowAudit", func(ctx context.Context) {
 		audit.ComputeWorkflowAudit(ctx, a.DBConnectionFactory.GetDBMap(gorpmapping.Mapper))
 	})
-	a.GoRoutines.Run(ctx, "auditCleanerRoutine(ctx", func(ctx context.Context) {
+	a.GoRoutines.Run(ctx, "auditCleanerRoutine", func(ctx context.Context) {
 		auditCleanerRoutine(ctx, a.DBConnectionFactory.GetDBMap(gorpmapping.Mapper))
 	})
-	a.GoRoutines.Run(ctx, "repositoriesmanager.ReceiveEvents", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "repositoriesmanager.ReceiveEvents", func(ctx context.Context) {
 		repositoriesmanager.ReceiveEvents(ctx, a.DBConnectionFactory.GetDBMap(gorpmapping.Mapper), a.Cache)
 	})
-	a.GoRoutines.Run(ctx, "services.KillDeadServices", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "services.KillDeadServices", func(ctx context.Context) {
 		services.KillDeadServices(ctx, a.mustDB)
 	})
 	a.GoRoutines.Run(ctx, "broadcast.Initialize", func(ctx context.Context) {
 		broadcast.Initialize(ctx, a.DBConnectionFactory.GetDBMap(gorpmapping.Mapper))
 	})
-	a.GoRoutines.Run(ctx, "api.serviceAPIHeartbeat", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "api.serviceAPIHeartbeat", func(ctx context.Context) {
 		a.serviceAPIHeartbeat(ctx)
 	})
-	a.GoRoutines.Run(ctx, "authentication.SessionCleaner", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "authentication.SessionCleaner", func(ctx context.Context) {
 		authentication.SessionCleaner(ctx, a.mustDB, 10*time.Second)
 	})
-	a.GoRoutines.Run(ctx, "api.WorkflowRunCraft", func(ctx context.Context) {
+	a.GoRoutines.RunWithRestart(ctx, "api.WorkflowRunCraft", func(ctx context.Context) {
 		a.WorkflowRunCraft(ctx, 100*time.Millisecond)
 	})
 
diff --git a/engine/api/migrate/migration.go b/engine/api/migrate/migration.go
index 110614498..5f2ef932c 100644
--- a/engine/api/migrate/migration.go
+++ b/engine/api/migrate/migration.go
@@ -46,7 +46,7 @@ func Run(ctx context.Context, db gorp.SqlExecutor) {
 				wg.Add(1)
 			}
 
-			sdk.NewGoRoutines().Run(ctx, "migrate_"+currentMigration.Name, func(contex context.Context) {
+			sdk.NewGoRoutines(ctx).Run(ctx, "migrate_"+currentMigration.Name, func(contex context.Context) {
 				defer func() {
 					if currentMigration.Blocker {
 						wg.Done()
diff --git a/engine/cdn/cdn.go b/engine/cdn/cdn.go
index 984228032..f52eaa3f7 100644
--- a/engine/cdn/cdn.go
+++ b/engine/cdn/cdn.go
@@ -36,8 +36,7 @@ const (
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
-
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/cdn/storage/storageunit_run.go b/engine/cdn/storage/storageunit_run.go
index 085cd4250..3e23cca11 100644
--- a/engine/cdn/storage/storageunit_run.go
+++ b/engine/cdn/storage/storageunit_run.go
@@ -160,7 +160,7 @@ func (x *RunningStorageUnits) runItem(ctx context.Context, tx gorpmapper.SqlExec
 
 	chanError := make(chan error)
 	pr, pw := io.Pipe()
-	gr := sdk.NewGoRoutines()
+	gr := sdk.NewGoRoutines(ctx)
 
 	gr.Exec(ctx, "runningStorageUnits.runItem.read", func(ctx context.Context) {
 		defer pw.Close()
diff --git a/engine/cdn/storage/webdav/webdav.go b/engine/cdn/storage/webdav/webdav.go
index e96401fea..b5be62aeb 100644
--- a/engine/cdn/storage/webdav/webdav.go
+++ b/engine/cdn/storage/webdav/webdav.go
@@ -83,7 +83,7 @@ func (s *Webdav) NewWriter(ctx context.Context, i sdk.CDNItemUnit) (io.WriteClos
 		return nil, err
 	}
 	pr, pw := io.Pipe()
-	gr := sdk.NewGoRoutines()
+	gr := sdk.NewGoRoutines(ctx)
 	gr.Exec(ctx, "webdav.newWriter", func(ctx context.Context) {
 		if err := s.client.WriteStream(f, pr, os.FileMode(0600)); err != nil {
 			log.Error(context.Background(), "unable to write stream %s: %v", f, err)
diff --git a/engine/elasticsearch/elasticsearch.go b/engine/elasticsearch/elasticsearch.go
index b8f3da4d5..dac6d8655 100644
--- a/engine/elasticsearch/elasticsearch.go
+++ b/engine/elasticsearch/elasticsearch.go
@@ -20,7 +20,7 @@ var esClient *elastic.Client
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hatchery/kubernetes/kubernetes.go b/engine/hatchery/kubernetes/kubernetes.go
index d452753ac..72d038124 100644
--- a/engine/hatchery/kubernetes/kubernetes.go
+++ b/engine/hatchery/kubernetes/kubernetes.go
@@ -29,7 +29,7 @@ import (
 // New instanciates a new hatchery local
 func New() *HatcheryKubernetes {
 	s := new(HatcheryKubernetes)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hatchery/local/local.go b/engine/hatchery/local/local.go
index cb2325501..c609ec266 100644
--- a/engine/hatchery/local/local.go
+++ b/engine/hatchery/local/local.go
@@ -28,7 +28,7 @@ import (
 // New instanciates a new hatchery local
 func New() *HatcheryLocal {
 	s := new(HatcheryLocal)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	s.LocalWorkerRunner = new(localWorkerRunner)
 	return s
 }
diff --git a/engine/hatchery/marathon/marathon.go b/engine/hatchery/marathon/marathon.go
index e506d117e..3d3021c0c 100644
--- a/engine/hatchery/marathon/marathon.go
+++ b/engine/hatchery/marathon/marathon.go
@@ -29,7 +29,7 @@ import (
 // New instanciates a new Hatchery Marathon
 func New() *HatcheryMarathon {
 	s := new(HatcheryMarathon)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hatchery/openstack/openstack.go b/engine/hatchery/openstack/openstack.go
index 4d747ed15..e8c75a1e6 100644
--- a/engine/hatchery/openstack/openstack.go
+++ b/engine/hatchery/openstack/openstack.go
@@ -40,7 +40,7 @@ var _ hatchery.InterfaceWithModels = new(HatcheryOpenstack)
 // New instanciates a new Hatchery Openstack
 func New() *HatcheryOpenstack {
 	s := new(HatcheryOpenstack)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hatchery/swarm/swarm.go b/engine/hatchery/swarm/swarm.go
index b455ccc0e..1f07a00a9 100644
--- a/engine/hatchery/swarm/swarm.go
+++ b/engine/hatchery/swarm/swarm.go
@@ -32,7 +32,7 @@ import (
 // New instanciates a new Hatchery Swarm
 func New() *HatcherySwarm {
 	s := new(HatcherySwarm)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hatchery/vsphere/hatchery.go b/engine/hatchery/vsphere/hatchery.go
index 49e8ecbf4..40067b7b2 100644
--- a/engine/hatchery/vsphere/hatchery.go
+++ b/engine/hatchery/vsphere/hatchery.go
@@ -26,7 +26,7 @@ import (
 // New instanciates a new Hatchery vsphere
 func New() *HatcheryVSphere {
 	s := new(HatcheryVSphere)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/hooks/hooks.go b/engine/hooks/hooks.go
index 9d166dea0..3a0cdf057 100644
--- a/engine/hooks/hooks.go
+++ b/engine/hooks/hooks.go
@@ -18,7 +18,7 @@ import (
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/migrateservice/service.go b/engine/migrateservice/service.go
index 3ffaf3248..c67bdedf1 100644
--- a/engine/migrateservice/service.go
+++ b/engine/migrateservice/service.go
@@ -48,7 +48,7 @@ type Configuration struct {
 // New instanciates a new API object
 func New() service.Service {
 	s := &dbmigservice{}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/repositories/repositories.go b/engine/repositories/repositories.go
index 932abcde7..383692110 100644
--- a/engine/repositories/repositories.go
+++ b/engine/repositories/repositories.go
@@ -18,7 +18,7 @@ import (
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/ui/ui.go b/engine/ui/ui.go
index 5ff275ff1..35306b0ae 100644
--- a/engine/ui/ui.go
+++ b/engine/ui/ui.go
@@ -27,7 +27,7 @@ import (
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	s.Router = &api.Router{
 		Mux: mux.NewRouter(),
 	}
diff --git a/engine/vcs/vcs.go b/engine/vcs/vcs.go
index eaa79e452..d0dad87c0 100644
--- a/engine/vcs/vcs.go
+++ b/engine/vcs/vcs.go
@@ -23,7 +23,7 @@ import (
 // New returns a new service
 func New() *Service {
 	s := new(Service)
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.Background())
 	return s
 }
 
diff --git a/engine/worker/internal/builtin.go b/engine/worker/internal/builtin.go
index 267516416..7d329a685 100644
--- a/engine/worker/internal/builtin.go
+++ b/engine/worker/internal/builtin.go
@@ -54,7 +54,7 @@ func (w *CurrentWorker) runBuiltin(ctx context.Context, a sdk.Action, secrets []
 func (w *CurrentWorker) runGRPCPlugin(ctx context.Context, a sdk.Action) sdk.Result {
 	chanRes := make(chan sdk.Result, 1)
 	done := make(chan struct{})
-	sdk.NewGoRoutines().Run(ctx, "runGRPCPlugin", func(ctx context.Context) {
+	sdk.NewGoRoutines(ctx).Run(ctx, "runGRPCPlugin", func(ctx context.Context) {
 		action.RunGRPCPlugin(ctx, a.Name, w.currentJob.params, a, w, chanRes, done)
 	})
 
diff --git a/sdk/cdsclient/client_workflow.go b/sdk/cdsclient/client_workflow.go
index 4abaca3a5..a8e2e9d97 100644
--- a/sdk/cdsclient/client_workflow.go
+++ b/sdk/cdsclient/client_workflow.go
@@ -106,15 +106,6 @@ func (c *client) WorkflowRunsDeleteByBranch(projectKey string, workflowName stri
 	return nil
 }
 
-func (c *client) WorkflowRunResync(projectKey string, workflowName string, number int64) (*sdk.WorkflowRun, error) {
-	url := fmt.Sprintf("/project/%s/workflows/%s/runs/%d/resync", projectKey, workflowName, number)
-	var run sdk.WorkflowRun
-	if _, err := c.PostJSON(context.Background(), url, nil, &run); err != nil {
-		return nil, err
-	}
-	return &run, nil
-}
-
 func (c *client) WorkflowRunSearch(projectKey string, offset, limit int64, filters ...Filter) ([]sdk.WorkflowRun, error) {
 	if offset < 0 {
 		offset = 0
diff --git a/sdk/cdsclient/interface.go b/sdk/cdsclient/interface.go
index dac1634a6..e376fcb3a 100644
--- a/sdk/cdsclient/interface.go
+++ b/sdk/cdsclient/interface.go
@@ -333,7 +333,6 @@ type WorkflowClient interface {
 	WorkflowGroupDelete(projectKey, name, groupName string) error
 	WorkflowRunGet(projectKey string, workflowName string, number int64) (*sdk.WorkflowRun, error)
 	WorkflowRunsDeleteByBranch(projectKey string, workflowName string, branch string) error
-	WorkflowRunResync(projectKey string, workflowName string, number int64) (*sdk.WorkflowRun, error)
 	WorkflowRunSearch(projectKey string, offset, limit int64, filter ...Filter) ([]sdk.WorkflowRun, error)
 	WorkflowRunList(projectKey string, workflowName string, offset, limit int64) ([]sdk.WorkflowRun, error)
 	WorkflowRunArtifacts(projectKey string, name string, number int64) ([]sdk.WorkflowNodeRunArtifact, error)
diff --git a/sdk/cdsclient/mock_cdsclient/interface_mock.go b/sdk/cdsclient/mock_cdsclient/interface_mock.go
index 12400cc65..daaad8148 100644
--- a/sdk/cdsclient/mock_cdsclient/interface_mock.go
+++ b/sdk/cdsclient/mock_cdsclient/interface_mock.go
@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
-// Source: interface.go
+// Source: sdk/cdsclient/interface.go
 
 // Package mock_cdsclient is a generated GoMock package.
 package mock_cdsclient
@@ -4341,21 +4341,6 @@ func (mr *MockWorkflowClientMockRecorder) WorkflowRunResultsList(ctx, projectKey
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResultsList", reflect.TypeOf((*MockWorkflowClient)(nil).WorkflowRunResultsList), ctx, projectKey, name, number)
 }
 
-// WorkflowRunResync mocks base method.
-func (m *MockWorkflowClient) WorkflowRunResync(projectKey, workflowName string, number int64) (*sdk.WorkflowRun, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WorkflowRunResync", projectKey, workflowName, number)
-	ret0, _ := ret[0].(*sdk.WorkflowRun)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// WorkflowRunResync indicates an expected call of WorkflowRunResync.
-func (mr *MockWorkflowClientMockRecorder) WorkflowRunResync(projectKey, workflowName, number interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResync", reflect.TypeOf((*MockWorkflowClient)(nil).WorkflowRunResync), projectKey, workflowName, number)
-}
-
 // WorkflowRunSearch mocks base method.
 func (m *MockWorkflowClient) WorkflowRunSearch(projectKey string, offset, limit int64, filter ...cdsclient.Filter) ([]sdk.WorkflowRun, error) {
 	m.ctrl.T.Helper()
@@ -8688,21 +8673,6 @@ func (mr *MockInterfaceMockRecorder) WorkflowRunResultsList(ctx, projectKey, nam
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResultsList", reflect.TypeOf((*MockInterface)(nil).WorkflowRunResultsList), ctx, projectKey, name, number)
 }
 
-// WorkflowRunResync mocks base method.
-func (m *MockInterface) WorkflowRunResync(projectKey, workflowName string, number int64) (*sdk.WorkflowRun, error) {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "WorkflowRunResync", projectKey, workflowName, number)
-	ret0, _ := ret[0].(*sdk.WorkflowRun)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// WorkflowRunResync indicates an expected call of WorkflowRunResync.
-func (mr *MockInterfaceMockRecorder) WorkflowRunResync(projectKey, workflowName, number interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowRunResync", reflect.TypeOf((*MockInterface)(nil).WorkflowRunResync), projectKey, workflowName, number)
-}
-
 // WorkflowRunSearch mocks base method.
 func (m *MockInterface) WorkflowRunSearch(projectKey string, offset, limit int64, filter ...cdsclient.Filter) ([]sdk.WorkflowRun, error) {
 	m.ctrl.T.Helper()
diff --git a/sdk/goroutine.go b/sdk/goroutine.go
index c66966636..aedec2e36 100644
--- a/sdk/goroutine.go
+++ b/sdk/goroutine.go
@@ -15,6 +15,7 @@ import (
 	"runtime/pprof"
 	"strconv"
 	"sync"
+	"time"
 
 	panicparsestack "github.com/maruel/panicparse/stack"
 	cdslog "github.com/ovh/cds/sdk/log"
@@ -23,44 +24,103 @@ import (
 	"github.com/pkg/errors"
 )
 
+type GoRoutine struct {
+	ctx     context.Context
+	Name    string
+	Func    func(ctx context.Context)
+	Restart bool
+	Active  bool
+}
+
 // GoRoutines contains list of routines that have to stay up
 type GoRoutines struct {
 	mutex  sync.Mutex
-	status map[string]bool
+	status []*GoRoutine
+}
+
+func (m *GoRoutines) GoRoutine(name string) *GoRoutine {
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+	for _, g := range m.status {
+		if g.Name == name {
+			return g
+		}
+	}
+	return nil
 }
 
 // NewGoRoutines instanciates a new GoRoutineManager
-func NewGoRoutines() *GoRoutines {
-	return &GoRoutines{
-		mutex:  sync.Mutex{},
-		status: make(map[string]bool),
+func NewGoRoutines(ctx context.Context) *GoRoutines {
+	m := &GoRoutines{}
+	m.Exec(ctx, "GoRoutines-restart", func(ctx context.Context) {
+		m.restartGoRoutines(ctx)
+	})
+	return m
+}
+
+func (m *GoRoutines) restartGoRoutines(ctx context.Context) {
+	t := time.NewTicker(10 * time.Second)
+	defer t.Stop()
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-t.C:
+			for _, g := range m.status {
+				if !g.Active && g.Restart {
+					log.Info(ctx, "restarting goroutine %q", g.Name)
+					m.exec(g)
+				}
+			}
+		}
 	}
 }
 
 // Run runs the function within a goroutine with a panic recovery, and keep GoRoutine status.
 func (m *GoRoutines) Run(c context.Context, name string, fn func(ctx context.Context)) {
 	m.mutex.Lock()
-	m.status[name] = true
-	m.mutex.Unlock()
-	m.Exec(c, name, fn)
+	defer m.mutex.Unlock()
+	g := &GoRoutine{
+		ctx:     c,
+		Name:    name,
+		Func:    fn,
+		Active:  true,
+		Restart: false,
+	}
+	m.status = append(m.status, g)
+	m.exec(g)
 }
 
-// GetStatus returns the monitoring status of goroutines that should be running
-func (m *GoRoutines) GetStatus() []MonitoringStatusLine {
+// RunWithRestart runs the function within a goroutine with a panic recovery, and keep GoRoutine status.
+// if the goroutine is stopped, it will ne restarted
+func (m *GoRoutines) RunWithRestart(c context.Context, name string, fn func(ctx context.Context)) {
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
+	g := &GoRoutine{
+		ctx:     c,
+		Name:    name,
+		Func:    fn,
+		Active:  true,
+		Restart: true,
+	}
+	m.status = append(m.status, g)
+	m.exec(g)
+}
+
+// GetStatus returns the monitoring status of goroutines that should be running
+func (m *GoRoutines) GetStatus() []MonitoringStatusLine {
 	lines := make([]MonitoringStatusLine, len(m.status))
 	i := 0
-	for name, isActive := range m.status {
+	for _, g := range m.status {
 		status := MonitoringStatusAlert
 		value := "NOT running"
-		if isActive {
+		if g.Active {
 			status = MonitoringStatusOK
 			value = "Running"
 		}
 		lines[i] = MonitoringStatusLine{
 			Status:    status,
-			Component: "goroutine/" + name,
+			Component: "goroutine/" + g.Name,
 			Value:     value,
 		}
 		i++
@@ -68,13 +128,16 @@ func (m *GoRoutines) GetStatus() []MonitoringStatusLine {
 	return lines
 }
 
-// Exec runs the function within a goroutine with a panic recovery
-func (m *GoRoutines) Exec(c context.Context, name string, fn func(ctx context.Context)) {
+func (m *GoRoutines) exec(g *GoRoutine) {
 	hostname, _ := os.Hostname()
 	go func(ctx context.Context) {
-		ctx = context.WithValue(ctx, cdslog.Goroutine, name)
+		ctx = context.WithValue(ctx, cdslog.Goroutine, g.Name)
 
-		labels := pprof.Labels("goroutine-name", name, "goroutine-hostname", hostname, "goroutine-id", fmt.Sprintf("%d", GoroutineID()))
+		labels := pprof.Labels(
+			"goroutine-name", g.Name,
+			"goroutine-hostname", hostname,
+			"goroutine-id", fmt.Sprintf("%d", GoroutineID()),
+		)
 		goroutineCtx := pprof.WithLabels(ctx, labels)
 		pprof.SetGoroutineLabels(goroutineCtx)
 
@@ -83,17 +146,26 @@ func (m *GoRoutines) Exec(c context.Context, name string, fn func(ctx context.Co
 				buf := make([]byte, 1<<16)
 				runtime.Stack(buf, false)
 				ctx = context.WithValue(ctx, cdslog.Stacktrace, string(buf))
-				log.Error(ctx, "[PANIC][%s] %s failed", hostname, name)
-			}
-			m.mutex.Lock()
-			if _, ok := m.status[name]; ok {
-				m.status[name] = false
+				log.Error(ctx, "[PANIC][%s] %s failed", hostname, g.Name)
 			}
-			m.mutex.Unlock()
+			g.Active = false
 		}()
 
-		fn(goroutineCtx)
-	}(c)
+		g.Active = true
+		g.Func(goroutineCtx)
+	}(g.ctx)
+}
+
+// Exec runs the function within a goroutine with a panic recovery
+func (m *GoRoutines) Exec(c context.Context, name string, fn func(ctx context.Context)) {
+	g := &GoRoutine{
+		ctx:     c,
+		Name:    name,
+		Func:    fn,
+		Active:  true,
+		Restart: false,
+	}
+	m.exec(g)
 }
 
 // code from https://github.com/golang/net/blob/master/http2/gotrack.go
diff --git a/ui/src/app/model/workflow.run.model.ts b/ui/src/app/model/workflow.run.model.ts
index cf4b78b41..e31f5b5d3 100644
--- a/ui/src/app/model/workflow.run.model.ts
+++ b/ui/src/app/model/workflow.run.model.ts
@@ -146,6 +146,14 @@ export class WorkflowRunResult {
     data: any;
 }
 
+export class UIArtifact {
+    name: string;
+    md5: string;
+    size: number;
+    link: string;
+    type: string;
+}
+
 
 export class WorkflowRunResultArtifact {
     name: string
@@ -154,6 +162,15 @@ export class WorkflowRunResultArtifact {
     cdn_hash: string;
 }
 
+export class WorkflowRunResultArtifactManager {
+    name: string;
+    size: number;
+    md5: string;
+    path: string;
+    repository_name: string;
+    repository_type: string;
+}
+
 export class WorkflowNodeOutgoingHookRunCallback {
     workflow_node_outgoing_hook_id: number;
     start: Date;
diff --git a/ui/src/app/shared/table/data-table.html b/ui/src/app/shared/table/data-table.html
index 58bb223d2..92aaea409 100644
--- a/ui/src/app/shared/table/data-table.html
+++ b/ui/src/app/shared/table/data-table.html
@@ -99,11 +99,13 @@
                         {{' '}}<span *ngFor="let l of c.selector.labels" class="ui label {{l.color}}">{{l.title | translate}}</span>
                     </ng-container>
                     <div *ngSwitchCase="'text-copy'" class="ui fluid action input">
-                        <input type="text" value="{{c.selector}}">
-                        <button class="ui icon button copyButton" [title]="'common_copy_clipboard' | translate"
-                            ngxClipboard [cbContent]="c.selector">
-                            <i class="copy icon"></i>
-                        </button>
+                        <ng-container *ngIf="c.selector">
+                            <input type="text" value="{{c.selector}}">
+                            <button class="ui icon button copyButton" [title]="'common_copy_clipboard' | translate"
+                                    ngxClipboard [cbContent]="c.selector">
+                                <i class="copy icon"></i>
+                            </button>
+                        </ng-container>
                     </div>
                     <span *ngSwitchCase="'text-labels'" class="text-labels">
                         {{c.selector.value |
diff --git a/ui/src/app/views/workflow/run/node/artifact/artifact.list.component.ts b/ui/src/app/views/workflow/run/node/artifact/artifact.list.component.ts
index 02c94a170..7984b8dbf 100644
--- a/ui/src/app/views/workflow/run/node/artifact/artifact.list.component.ts
+++ b/ui/src/app/views/workflow/run/node/artifact/artifact.list.component.ts
@@ -1,16 +1,18 @@
 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import { Select, Store } from '@ngxs/store';
 import {
+    UIArtifact,
     WorkflowNodeRun,
     WorkflowNodeRunArtifact,
-    WorkflowNodeRunStaticFiles,
-    WorkflowRunResultArtifact
+    WorkflowNodeRunStaticFiles, WorkflowRunResult,
+    WorkflowRunResultArtifact, WorkflowRunResultArtifactManager
 } from 'app/model/workflow.run.model';
 
 import { AutoUnsubscribe } from 'app/shared/decorator/autoUnsubscribe';
 import { Column, ColumnType, Filter } from 'app/shared/table/data-table.component';
 import { WorkflowState } from 'app/store/workflow.state';
 import { Observable, Subscription } from 'rxjs';
+import { Workflow } from 'app/model/workflow.model';
 
 @Component({
     selector: 'app-workflow-artifact-list',
@@ -23,42 +25,46 @@ export class WorkflowRunArtifactListComponent implements OnInit, OnDestroy {
     @Select(WorkflowState.getSelectedNodeRun()) nodeRun$: Observable<WorkflowNodeRun>;
     nodeRunSubs: Subscription;
 
+    runResult: Array<WorkflowRunResult>
     artifacts: Array<WorkflowNodeRunArtifact>;
+
+    uiArtifacts: Array<UIArtifact>;
     staticFiles: Array<WorkflowNodeRunStaticFiles>;
 
-    filter: Filter<WorkflowNodeRunArtifact>;
-    columns: Array<Column<WorkflowNodeRunArtifact>>;
+    filter: Filter<UIArtifact>;
+    columns: Array<Column<UIArtifact>>;
 
     constructor(private _cd: ChangeDetectorRef, private _store: Store) {
         this.filter = f => {
             const lowerFilter = f.toLowerCase();
             return d => d.name.toLowerCase().indexOf(lowerFilter) !== -1 ||
-                d.sha512sum.toLowerCase().indexOf(lowerFilter) !== -1
+                d.md5.toLowerCase().indexOf(lowerFilter) !== -1
         };
         this.columns = [
-            <Column<WorkflowNodeRunArtifact>>{
+            <Column<UIArtifact>>{
                 type: ColumnType.LINK,
                 name: 'artifact_name',
-                selector: (a: WorkflowNodeRunArtifact) => {
+                selector: (a: UIArtifact) => {
                     let size = this.getHumainFileSize(a.size);
-                    let link = `./cdsapi/workflow/artifact/${a.download_hash}`
-                    if (!a.id) {
-                        link = `./cdscdn/item/run-result/${a.download_hash}/download`
+                    let link = a.link;
+                    let value = a.name;
+                    if (size) {
+                        value += ` (${size})`;
                     }
                     return {
                         link,
-                        value: `${a.name} (${size})`
+                        value
                     };
                 }
             },
-            <Column<WorkflowNodeRunArtifact>>{
-                name: 'artifact_tag',
-                selector: (a: WorkflowNodeRunArtifact) => a.tag
+            <Column<UIArtifact>>{
+                name: 'Type of artifact',
+                selector: (a: UIArtifact) => a.type
             },
-            <Column<WorkflowNodeRunArtifact>>{
+            <Column<UIArtifact>>{
                 type: ColumnType.TEXT_COPY,
                 name: 'MD5 Sum',
-                selector: (a: WorkflowNodeRunArtifact) => a.md5sum
+                selector: (a: UIArtifact) => a.md5
             }
         ];
     }
@@ -70,16 +76,36 @@ export class WorkflowRunArtifactListComponent implements OnInit, OnDestroy {
             if (!nr) {
                 return;
             }
-            let resultArtifacts = nr?.results.filter(r => r.type === 'artifact').map(r => <WorkflowRunResultArtifact>r.data);
-            if (!resultArtifacts) {
-                resultArtifacts = new Array<WorkflowRunResultArtifact>();
+
+            let computeArtifact = false;
+            if (nr.results && (!this.runResult || nr.results.length !== this.runResult.length)) {
+                computeArtifact = true
+            }
+            if (nr.artifacts && (!this.artifacts || nr.artifacts.length !== this.artifacts.length)) {
+                computeArtifact = true
             }
-            if ( (!this.artifacts && (nr.artifacts || resultArtifacts.length > 0)) || (this.artifacts && nr.artifacts && this.artifacts.length !== (nr.artifacts.length + resultArtifacts.length))) {
-                this.artifacts = new Array<WorkflowNodeRunArtifact>();
+            if (computeArtifact) {
+                let uiArtifacts: Array<UIArtifact>;
+                let uiRunResults: Array<UIArtifact>;
+                this.uiArtifacts = new Array<UIArtifact>();
+                if (nr.results) {
+                    let w = this._store.selectSnapshot(WorkflowState.workflowRunSnapshot).workflow
+                    uiRunResults = this.toUIArtifact(w, nr.results);
+                    this.uiArtifacts.push(...uiRunResults);
+                }
+
                 if (nr.artifacts) {
-                    this.artifacts.push(...nr.artifacts);
+                    uiArtifacts = nr.artifacts.map(a => {
+                        let uiArt = new UIArtifact();
+                        uiArt.name = a.name;
+                        uiArt.size = a.size;
+                        uiArt.md5 = a.md5sum;
+                        uiArt.type = 'file';
+                        uiArt.link = `./cdscdn/item/artifact/${a.download_hash}/download`;
+                        return uiArt;
+                    });
+                    this.uiArtifacts.push(...uiArtifacts)
                 }
-                this.artifacts.push(...this.toWorkflowNodeRunArtifacts(resultArtifacts));
                 this._cd.markForCheck();
             }
             if ((!this.staticFiles && nr.static_files) ||
@@ -90,25 +116,52 @@ export class WorkflowRunArtifactListComponent implements OnInit, OnDestroy {
         });
     }
 
-    toWorkflowNodeRunArtifacts(results: Array<WorkflowRunResultArtifact>): Array<WorkflowNodeRunArtifact> {
-        let arts = new Array<WorkflowNodeRunArtifact>();
-        results.forEach(r => {
-            let a = new WorkflowNodeRunArtifact();
-            a.download_hash = r.cdn_hash;
-            a.md5sum = r.md5;
-            a.size =  r.size;
-            a.name = r.name;
-            arts.push(a);
-        })
-        return arts;
-    }
-
     getHumainFileSize(size: number): string {
         if (size === 0) {
-            return '0B';
+            return '';
         }
         let i = Math.floor(Math.log(size) / Math.log(1024));
         let hSize = (size / Math.pow(1024, i)).toFixed(2);
         return hSize + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
     }
+
+    private toUIArtifact(w: Workflow, results: Array<WorkflowRunResult>): Array<UIArtifact> {
+        if (!results) {
+            return [];
+        }
+        let integrationArtifactManagerURL = '';
+        if (w?.integrations) {
+            for (let i = 0; i < w.integrations.length; i++) {
+               let integ = w.integrations[i];
+               if (!integ.project_integration.model.artifact_manager) {
+                   continue
+               }
+               integrationArtifactManagerURL = integ?.project_integration?.config['url']?.value;
+            }
+        }
+
+        return results.map(r => {
+            switch (r.type) {
+                case 'artifact':
+                case 'coverage':
+                    let data = <WorkflowRunResultArtifact>r.data;
+                    let uiArtifact = new UIArtifact();
+                    uiArtifact.link = `./cdscdn/item/run-result/${data.cdn_hash}/download`;
+                    uiArtifact.md5 = data.md5;
+                    uiArtifact.name = data.name;
+                    uiArtifact.size = data.size;
+                    uiArtifact.type = 'file';
+                    return uiArtifact;
+                case 'artifact-manager':
+                    let dataAM = <WorkflowRunResultArtifactManager>r.data;
+                    let uiArtifactAM = new UIArtifact();
+                    uiArtifactAM.link = `${integrationArtifactManagerURL}${dataAM.repository_name}/${dataAM.path}`;
+                    uiArtifactAM.md5 = dataAM.md5;
+                    uiArtifactAM.name = dataAM.name;
+                    uiArtifactAM.size = dataAM.size;
+                    uiArtifactAM.type = dataAM.repository_type;
+                    return uiArtifactAM;
+            }
+        })
+    }
 }
diff --git a/ui/src/app/views/workflow/run/node/artifact/artifact.list.html b/ui/src/app/views/workflow/run/node/artifact/artifact.list.html
index b387a4ad9..1e9895b1a 100644
--- a/ui/src/app/views/workflow/run/node/artifact/artifact.list.html
+++ b/ui/src/app/views/workflow/run/node/artifact/artifact.list.html
@@ -1,4 +1,4 @@
-<div class="artifact" *ngIf="artifacts || staticFiles">
+<div class="artifact" *ngIf="uiArtifacts || staticFiles">
     <table class="ui fixed celled table" *ngIf="staticFiles && staticFiles.length > 0">
         <thead>
             <tr>
@@ -21,5 +21,5 @@
             </tr>
         </tbody>
     </table>
-    <app-data-table [withFilter]="filter" [withPagination]="10" [columns]="columns" [data]="artifacts"></app-data-table>
-</div>
\ No newline at end of file
+    <app-data-table [withFilter]="filter" [withPagination]="10" [columns]="columns" [data]="uiArtifacts"></app-data-table>
+</div>
diff --git a/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts b/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
index 594766e93..aaadeed5a 100644
--- a/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
+++ b/ui/src/app/views/workflow/run/node/workflow.run.node.component.ts
@@ -124,16 +124,13 @@ export class WorkflowNodeRunComponent implements OnInit, OnDestroy {
                     refresh = true;
                 }
 
-                let artiResults = nr?.results?.filter(r => r.type === 'artifact');
-                let artiResultsLength = 0;
-                if (artiResults) {
-                    artiResultsLength = artiResults.length
-                }
+
+                let artiResultsLength = nr?.results?.length ?? 0
                 let oldArtiLength = 0;
                 if (nr.artifacts) {
                     oldArtiLength = nr.artifacts.length;
                 }
-                if ((nr.artifacts || artiResults) && (oldArtiLength + artiResultsLength) !== this.artifactLength) {
+                if ((nr.artifacts || artiResultsLength > 0) && (oldArtiLength + artiResultsLength) !== this.artifactLength) {
                     this.artifactLength = oldArtiLength + artiResultsLength;
                     refresh = true;
                 }
diff --git a/ui/src/assets/i18n/en.json b/ui/src/assets/i18n/en.json
index 4deaf86a8..8a8bf7ede 100644
--- a/ui/src/assets/i18n/en.json
+++ b/ui/src/assets/i18n/en.json
@@ -98,7 +98,6 @@
   "application_variable_list_title": "List of application variables: ",
   "application_variable_form_title": "Add a new variable: ",
   "artifact_name": "Artifact name",
-  "artifact_tag": "Tag",
   "ascode_error_unknown_type": "Cannot determine the resource to update",
   "ascode_modal_title": "Save as code",
   "ascode_modal_label_branch": "Select or create a branch",


Test patch:
diff --git a/engine/api/api_test.go b/engine/api/api_test.go
index 9d9d9ed13..7f57b7e52 100644
--- a/engine/api/api_test.go
+++ b/engine/api/api_test.go
@@ -42,7 +42,7 @@ func newTestAPI(t *testing.T, bootstrapFunc ...test.Bootstrapf) (*API, *test.Fak
 	api.AuthenticationDrivers[sdk.ConsumerBuiltin] = builtin.NewDriver()
 	api.AuthenticationDrivers[sdk.ConsumerTest] = authdrivertest.NewDriver(t)
 	api.AuthenticationDrivers[sdk.ConsumerTest2] = authdrivertest.NewDriver(t)
-	api.GoRoutines = sdk.NewGoRoutines()
+	api.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	api.InitRouter()
 
@@ -90,7 +90,7 @@ func newTestServer(t *testing.T, bootstrapFunc ...test.Bootstrapf) (*API, *test.
 	api.AuthenticationDrivers = make(map[sdk.AuthConsumerType]sdk.AuthDriver)
 	api.AuthenticationDrivers[sdk.ConsumerLocal] = local.NewDriver(context.TODO(), false, "http://localhost:8080", "")
 	api.AuthenticationDrivers[sdk.ConsumerBuiltin] = builtin.NewDriver()
-	api.GoRoutines = sdk.NewGoRoutines()
+	api.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	api.InitRouter()
 	ts := httptest.NewServer(router.Mux)
diff --git a/engine/api/application_test.go b/engine/api/application_test.go
index 00c2c9ccd..897c6e286 100644
--- a/engine/api/application_test.go
+++ b/engine/api/application_test.go
@@ -274,7 +274,7 @@ func TestUpdateAsCodeApplicationHandler(t *testing.T) {
 	chanMessageReceived := make(chan sdk.WebsocketEvent)
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	chanMessageToSend <- []sdk.WebsocketFilter{{
 		Type:         sdk.WebsocketFilterTypeAscodeEvent,
 		ProjectKey:   proj.Key,
diff --git a/engine/api/environment_ascode_test.go b/engine/api/environment_ascode_test.go
index a50cd73ef..46024564b 100644
--- a/engine/api/environment_ascode_test.go
+++ b/engine/api/environment_ascode_test.go
@@ -233,7 +233,7 @@ func TestUpdateAsCodeEnvironmentHandler(t *testing.T) {
 	chanMessageReceived := make(chan sdk.WebsocketEvent)
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	chanMessageToSend <- []sdk.WebsocketFilter{{
 		Type:         sdk.WebsocketFilterTypeAscodeEvent,
 		ProjectKey:   proj.Key,
diff --git a/engine/api/pipeline_test.go b/engine/api/pipeline_test.go
index 5e4c12799..5100a46d7 100644
--- a/engine/api/pipeline_test.go
+++ b/engine/api/pipeline_test.go
@@ -205,7 +205,7 @@ func TestUpdateAsCodePipelineHandler(t *testing.T) {
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
 
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	chanMessageToSend <- []sdk.WebsocketFilter{{
 		Type:         sdk.WebsocketFilterTypeAscodeEvent,
 		ProjectKey:   proj.Key,
diff --git a/engine/api/websocket_test.go b/engine/api/websocket_test.go
index a99db96ad..4e9434b84 100644
--- a/engine/api/websocket_test.go
+++ b/engine/api/websocket_test.go
@@ -45,7 +45,7 @@ func Test_websocketWrongFilters(t *testing.T) {
 		InsecureSkipVerifyTLS:             true,
 		BuitinConsumerAuthenticationToken: jws,
 	})
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 
 	// Subscribe to project without project key
 	chanMessageToSend <- []sdk.WebsocketFilter{{
@@ -126,7 +126,7 @@ func Test_websocketGetWorkflowEvent(t *testing.T) {
 		InsecureSkipVerifyTLS: true,
 		SessionToken:          jwt,
 	})
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	var lastResponse *sdk.WebsocketEvent
 	go func() {
 		for e := range chanMessageReceived {
@@ -268,7 +268,7 @@ func TestWebsocketNoEventLoose(t *testing.T) {
 		InsecureSkipVerifyTLS: true,
 		SessionToken:          jwt,
 	})
-	go client1.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chan1MessageToSend, chan1MessageReceived, chan1ErrorReceived)
+	go client1.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(ctx), chan1MessageToSend, chan1MessageReceived, chan1ErrorReceived)
 	var client1EventCount int64
 	go func() {
 		for {
@@ -299,7 +299,7 @@ func TestWebsocketNoEventLoose(t *testing.T) {
 		SessionToken:          jwt,
 	})
 	var client2EventCount int64
-	go client2.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chan2MessageToSend, chan2MessageReceived, chan2ErrorReceived)
+	go client2.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(ctx), chan2MessageToSend, chan2MessageReceived, chan2ErrorReceived)
 	go func() {
 		for {
 			select {
diff --git a/engine/api/workflow_ascode_test.go b/engine/api/workflow_ascode_test.go
index 056f18525..87be103a2 100644
--- a/engine/api/workflow_ascode_test.go
+++ b/engine/api/workflow_ascode_test.go
@@ -211,7 +211,7 @@ func TestPostUpdateWorkflowAsCodeHandler(t *testing.T) {
 	chanMessageReceived := make(chan sdk.WebsocketEvent)
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	chanMessageToSend <- []sdk.WebsocketFilter{{
 		Type:         sdk.WebsocketFilterTypeAscodeEvent,
 		ProjectKey:   proj.Key,
@@ -422,7 +422,7 @@ func TestPostMigrateWorkflowAsCodeHandler(t *testing.T) {
 	chanMessageReceived := make(chan sdk.WebsocketEvent)
 	chanMessageToSend := make(chan []sdk.WebsocketFilter)
 	chanErrorReceived := make(chan error)
-	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(context.TODO(), sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 	chanMessageToSend <- []sdk.WebsocketFilter{{
 		Type:         sdk.WebsocketFilterTypeAscodeEvent,
 		ProjectKey:   proj.Key,
diff --git a/engine/api/workflow_purge_test.go b/engine/api/workflow_purge_test.go
index 90031e866..5503d1035 100644
--- a/engine/api/workflow_purge_test.go
+++ b/engine/api/workflow_purge_test.go
@@ -98,7 +98,7 @@ func Test_purgeDryRunHandler(t *testing.T) {
 	})
 	contextWS, cancel := context.WithCancel(context.Background())
 	t.Cleanup(cancel)
-	go client.WebsocketEventsListen(contextWS, sdk.NewGoRoutines(), chanMessageToSend, chanMessageReceived, chanErrorReceived)
+	go client.WebsocketEventsListen(contextWS, sdk.NewGoRoutines(context.TODO()), chanMessageToSend, chanMessageReceived, chanErrorReceived)
 
 	// Subscribe to workflow retention
 	chanMessageToSend <- []sdk.WebsocketFilter{{
diff --git a/engine/api/workflow_run_test.go b/engine/api/workflow_run_test.go
index 9cc0f984f..255f3edfa 100644
--- a/engine/api/workflow_run_test.go
+++ b/engine/api/workflow_run_test.go
@@ -1407,7 +1407,7 @@ func Test_postWorkflowRunAsyncFailedHandler(t *testing.T) {
 		OperationUUID: ope.UUID,
 	}
 
-	ascode.UpdateAsCodeResult(context.TODO(), api.mustDB(), api.Cache, sdk.NewGoRoutines(), *proj, *w1, app, ed, u)
+	ascode.UpdateAsCodeResult(context.TODO(), api.mustDB(), api.Cache, sdk.NewGoRoutines(context.TODO()), *proj, *w1, app, ed, u)
 
 	// Prepare request
 	uri := router.GetRoute("POST", api.postWorkflowRunHandler, map[string]string{
diff --git a/engine/cdn/cdn_gc_test.go b/engine/cdn/cdn_gc_test.go
index f6258bc0f..0bd0d60ca 100644
--- a/engine/cdn/cdn_gc_test.go
+++ b/engine/cdn/cdn_gc_test.go
@@ -39,7 +39,7 @@ func TestCleanSynchronizedItem(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
 	require.NoError(t, err)
@@ -47,7 +47,7 @@ func TestCleanSynchronizedItem(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
 	t.Cleanup(cancel)
 
-	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
@@ -208,7 +208,7 @@ func TestCleanSynchronizedItemWithDisabledStorage(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
 	require.NoError(t, err)
@@ -216,7 +216,7 @@ func TestCleanSynchronizedItemWithDisabledStorage(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
 	t.Cleanup(cancel)
 
-	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
@@ -351,7 +351,7 @@ func TestCleanWaitingItem(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -402,7 +402,7 @@ func TestCleanWaitingItemWithoutItemUnit(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -446,7 +446,7 @@ func TestPurgeItem(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
diff --git a/engine/cdn/cdn_item_test.go b/engine/cdn/cdn_item_test.go
index 4d728ad7c..6c336c3ab 100644
--- a/engine/cdn/cdn_item_test.go
+++ b/engine/cdn/cdn_item_test.go
@@ -41,7 +41,7 @@ func TestGetItemValue(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -202,7 +202,7 @@ func TestGetItemValue_ThousandLines(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -309,7 +309,7 @@ func TestGetItemValue_Reverse(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -419,7 +419,7 @@ func TestGetItemValue_ThousandLinesReverse(t *testing.T) {
 		Mapper:              m,
 	}
 	s.Cfg.Log.StepMaxSize = 200000
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
diff --git a/engine/cdn/cdn_log_store_test.go b/engine/cdn/cdn_log_store_test.go
index 6d4773da8..242310c01 100644
--- a/engine/cdn/cdn_log_store_test.go
+++ b/engine/cdn/cdn_log_store_test.go
@@ -40,7 +40,7 @@ func TestStoreNewStepLog(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -123,7 +123,7 @@ func TestStoreLastStepLog(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
@@ -212,7 +212,7 @@ func TestStoreNewServiceLog(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, cancel := context.WithCancel(context.TODO())
 	t.Cleanup(cancel)
diff --git a/engine/cdn/cdn_log_tcp_test.go b/engine/cdn/cdn_log_tcp_test.go
index f13d21cb1..88aa417bc 100644
--- a/engine/cdn/cdn_log_tcp_test.go
+++ b/engine/cdn/cdn_log_tcp_test.go
@@ -61,7 +61,7 @@ func TestWorkerLogCDNEnabled(t *testing.T) {
 	}
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
 	require.NoError(t, err)
-	cdnUnits, err := storage.Init(context.TODO(), m, store, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(context.TODO(), m, store, db.DbMap, sdk.NewGoRoutines(context.TODO()), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
@@ -84,7 +84,7 @@ func TestWorkerLogCDNEnabled(t *testing.T) {
 	s.Units = cdnUnits
 
 	s.Cfg.Log.StepMaxSize = 1000
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	signature := cdn.Signature{
 		Worker: &cdn.SignatureWorker{
@@ -177,10 +177,10 @@ func TestServiceLogCDNDisabled(t *testing.T) {
 		Mapper:              m,
 	}
 	s.Cfg.Log.StepMaxSize = 1000
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
 	require.NoError(t, err)
-	cdnUnits, err := storage.Init(context.TODO(), m, store, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(context.TODO(), m, store, db.DbMap, sdk.NewGoRoutines(context.TODO()), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
@@ -292,7 +292,7 @@ func TestStoreTruncatedLogs(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 
 	ctx, ccl := context.WithCancel(context.TODO())
 	t.Cleanup(ccl)
diff --git a/engine/cdn/cdn_sync_test.go b/engine/cdn/cdn_sync_test.go
index 857f35c6f..34b22762f 100644
--- a/engine/cdn/cdn_sync_test.go
+++ b/engine/cdn/cdn_sync_test.go
@@ -39,10 +39,10 @@ func TestSyncBuffer(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 		Common: service.Common{
-			GoRoutines: sdk.NewGoRoutines(),
+			GoRoutines: sdk.NewGoRoutines(context.TODO()),
 		},
 	}
-	cdnUnits, err := storage.Init(context.Background(), m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(context.Background(), m, cache, db.DbMap, sdk.NewGoRoutines(context.TODO()), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
@@ -96,7 +96,7 @@ func TestSyncLog(t *testing.T) {
 		Cache:               cache,
 		Mapper:              m,
 		Common: service.Common{
-			GoRoutines: sdk.NewGoRoutines(),
+			GoRoutines: sdk.NewGoRoutines(context.TODO()),
 		},
 	}
 
@@ -106,7 +106,7 @@ func TestSyncLog(t *testing.T) {
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-*")
 	require.NoError(t, err)
 
-	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		SyncNbElements:  100,
 		SyncSeconds:     1,
@@ -136,7 +136,7 @@ func TestSyncLog(t *testing.T) {
 	})
 	require.NoError(t, err)
 
-	cdnUnits.Start(ctx, sdk.NewGoRoutines())
+	cdnUnits.Start(ctx, sdk.NewGoRoutines(ctx))
 	s.Units = cdnUnits
 
 	cdsStorage, ok := s.Units.Storages[0].(*cds.CDS)
diff --git a/engine/cdn/cdn_test.go b/engine/cdn/cdn_test.go
index df8e1ff6e..226491eb2 100644
--- a/engine/cdn/cdn_test.go
+++ b/engine/cdn/cdn_test.go
@@ -64,11 +64,11 @@ func newTestService(t *testing.T) (*Service, *test.FakeTransaction) {
 		Cache:               cache,
 		Mapper:              m,
 	}
-	s.GoRoutines = sdk.NewGoRoutines()
+	s.GoRoutines = sdk.NewGoRoutines(context.TODO())
 	if fakeAPIPrivateKey.key == nil {
 		fakeAPIPrivateKey.key, _ = jws.NewRandomRSAKey()
 	}
-	s.Common.GoRoutines = sdk.NewGoRoutines()
+	s.Common.GoRoutines = sdk.NewGoRoutines(context.TODO())
 	s.ParsedAPIPublicKey = &fakeAPIPrivateKey.key.PublicKey
 
 	ctx, cancel := context.WithCancel(context.Background())
@@ -123,7 +123,7 @@ func newRunningStorageUnits(t *testing.T, m *gorpmapper.Mapper, dbMap *gorp.DbMa
 	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
 	t.Cleanup(cancel)
 
-	cdnUnits, err := storage.Init(ctx, m, store, dbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, store, dbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		SyncSeconds:     2,
 		SyncNbElements:  100,
 		PurgeSeconds:    30,
@@ -161,6 +161,6 @@ func newRunningStorageUnits(t *testing.T, m *gorpmapper.Mapper, dbMap *gorp.DbMa
 		},
 	})
 	require.NoError(t, err)
-	cdnUnits.Start(ctx, sdk.NewGoRoutines())
+	cdnUnits.Start(ctx, sdk.NewGoRoutines(ctx))
 	return cdnUnits
 }
diff --git a/engine/cdn/item_logs_handler_test.go b/engine/cdn/item_logs_handler_test.go
index 8903456ed..6d42cb3c0 100644
--- a/engine/cdn/item_logs_handler_test.go
+++ b/engine/cdn/item_logs_handler_test.go
@@ -383,7 +383,7 @@ func TestGetItemLogsStreamHandler(t *testing.T) {
 	chanMsgReceived := make(chan json.RawMessage, 10)
 	chanErrorReceived := make(chan error, 10)
 	go func() {
-		chanErrorReceived <- client.RequestWebsocket(ctx, sdk.NewGoRoutines(), uri, chanMsgToSend, chanMsgReceived, chanErrorReceived)
+		chanErrorReceived <- client.RequestWebsocket(ctx, sdk.NewGoRoutines(ctx), uri, chanMsgToSend, chanMsgReceived, chanErrorReceived)
 	}()
 	buf, err := json.Marshal(sdk.CDNStreamFilter{
 		ItemType: sdk.CDNTypeItemStepLog,
@@ -441,7 +441,7 @@ func TestGetItemLogsStreamHandler(t *testing.T) {
 	ctx, cancel = context.WithTimeout(context.TODO(), time.Second*10)
 	t.Cleanup(func() { cancel() })
 	go func() {
-		chanErrorReceived <- client.RequestWebsocket(ctx, sdk.NewGoRoutines(), uri, chanMsgToSend, chanMsgReceived, chanErrorReceived)
+		chanErrorReceived <- client.RequestWebsocket(ctx, sdk.NewGoRoutines(ctx), uri, chanMsgToSend, chanMsgReceived, chanErrorReceived)
 	}()
 	buf, err = json.Marshal(sdk.CDNStreamFilter{
 		ItemType: sdk.CDNTypeItemStepLog,
diff --git a/engine/cdn/item_upload_test.go b/engine/cdn/item_upload_test.go
index 3c0c94f13..5ff443837 100644
--- a/engine/cdn/item_upload_test.go
+++ b/engine/cdn/item_upload_test.go
@@ -53,7 +53,7 @@ func TestPostUploadHandler(t *testing.T) {
 	tmpDir2, err := ioutil.TempDir("", t.Name()+"-cdn-2-*")
 	require.NoError(t, err)
 
-	cdnUnits, err := storage.Init(ctx, s.Mapper, s.Cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, s.Mapper, s.Cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		SyncSeconds:     1,
 		SyncNbElements:  1000,
 		PurgeNbElements: 1000,
@@ -100,7 +100,7 @@ func TestPostUploadHandler(t *testing.T) {
 	})
 	require.NoError(t, err)
 	s.Units = cdnUnits
-	cdnUnits.Start(ctx, sdk.NewGoRoutines())
+	cdnUnits.Start(ctx, sdk.NewGoRoutines(ctx))
 
 	// Mock cds client
 	s.Client = cdsclient.New(cdsclient.Config{Host: "http://lolcat.api", InsecureSkipVerifyTLS: false})
diff --git a/engine/cdn/storage/dao_test.go b/engine/cdn/storage/dao_test.go
index 95b4ee95b..18d10cc69 100644
--- a/engine/cdn/storage/dao_test.go
+++ b/engine/cdn/storage/dao_test.go
@@ -48,7 +48,7 @@ func TestLoadAllItemIDUnknownByUnit(t *testing.T) {
 	ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
 	t.Cleanup(cancel)
 
-	cdnUnits, err := storage.Init(ctx, m, store, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, store, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		HashLocatorSalt: "thisismysalt",
 		Buffers: map[string]storage.BufferConfiguration{
 			"redis_buffer": {
diff --git a/engine/cdn/storage/nfs/nfs_test.go b/engine/cdn/storage/nfs/nfs_test.go
index 898fd2b63..0ed8de2be 100644
--- a/engine/cdn/storage/nfs/nfs_test.go
+++ b/engine/cdn/storage/nfs/nfs_test.go
@@ -3,16 +3,17 @@ package nfs
 import (
 	"context"
 	"fmt"
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+
 	"github.com/ovh/cds/engine/cdn/storage"
 	"github.com/ovh/cds/sdk"
 	"github.com/ovh/symmecrypt/ciphers/aesgcm"
 	"github.com/ovh/symmecrypt/keyloader"
 	"github.com/rockbears/log"
 	"github.com/stretchr/testify/require"
-	"io/ioutil"
-	"os"
-	"testing"
-	"time"
 )
 
 // To run export the mentionned env variables: NFS_HOST, NFS_PARTITION
@@ -63,7 +64,7 @@ func TestNFSReadWrite(t *testing.T) {
 	require.NotNil(t, d)
 	bd, is := d.(storage.BufferUnit)
 	require.True(t, is)
-	bd.New(sdk.NewGoRoutines(), storage.AbstractUnitConfig{})
+	bd.New(sdk.NewGoRoutines(ctx), storage.AbstractUnitConfig{})
 	err := bd.Init(ctx, &storage.NFSBufferConfiguration{
 		Host:            nfsHost,
 		TargetPartition: nfsTargetPath,
diff --git a/engine/cdn/storage/storageunit_test.go b/engine/cdn/storage/storageunit_test.go
index f842f1e66..17fd2a0f4 100644
--- a/engine/cdn/storage/storageunit_test.go
+++ b/engine/cdn/storage/storageunit_test.go
@@ -47,7 +47,7 @@ func TestDeduplicationCrossType(t *testing.T) {
 	tmpDir, err := ioutil.TempDir("", t.Name()+"-cdn-1-*")
 	require.NoError(t, err)
 
-	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		SyncSeconds:     10,
 		SyncNbElements:  100,
 		HashLocatorSalt: "thisismysalt",
@@ -83,7 +83,7 @@ func TestDeduplicationCrossType(t *testing.T) {
 	})
 	require.NoError(t, err)
 	require.NotNil(t, cdnUnits)
-	cdnUnits.Start(ctx, sdk.NewGoRoutines())
+	cdnUnits.Start(ctx, sdk.NewGoRoutines(ctx))
 
 	units, err := storage.LoadAllUnits(ctx, m, db.DbMap)
 	require.NoError(t, err)
@@ -242,7 +242,7 @@ func TestRun(t *testing.T) {
 	tmpDir2, err := ioutil.TempDir("", t.Name()+"-cdn-2-*")
 	require.NoError(t, err)
 
-	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(), storage.Configuration{
+	cdnUnits, err := storage.Init(ctx, m, cache, db.DbMap, sdk.NewGoRoutines(ctx), storage.Configuration{
 		SyncSeconds:     10,
 		SyncNbElements:  100,
 		HashLocatorSalt: "thisismysalt",
@@ -284,7 +284,7 @@ func TestRun(t *testing.T) {
 	})
 	require.NoError(t, err)
 	require.NotNil(t, cdnUnits)
-	cdnUnits.Start(ctx, sdk.NewGoRoutines())
+	cdnUnits.Start(ctx, sdk.NewGoRoutines(ctx))
 
 	units, err := storage.LoadAllUnits(ctx, m, db.DbMap)
 	require.NoError(t, err)
diff --git a/engine/hatchery/kubernetes/kubernetes_test.go b/engine/hatchery/kubernetes/kubernetes_test.go
index c522195ba..b890f3c1b 100644
--- a/engine/hatchery/kubernetes/kubernetes_test.go
+++ b/engine/hatchery/kubernetes/kubernetes_test.go
@@ -97,8 +97,14 @@ func TestHatcheryKubernetes_Status(t *testing.T) {
 		require.Equal(t, 2, len(podRequest.Spec.Containers))
 		require.Equal(t, "k8s-toto", podRequest.Spec.Containers[0].Name)
 		require.Equal(t, int64(4096), podRequest.Spec.Containers[0].Resources.Requests.Memory().Value())
-		require.Equal(t, "ZZZZ", podRequest.Spec.Containers[0].Env[1].Name)
-		require.Equal(t, "ZZZZ", podRequest.Spec.Containers[0].Env[1].Value)
+		var zzzzFound bool
+		for _, env := range podRequest.Spec.Containers[0].Env {
+			if "ZZZZ" == env.Name && "ZZZZ" == env.Value {
+				zzzzFound = true
+				break
+			}
+		}
+		require.True(t, zzzzFound, "\"ZZZZ\" not found in env variables")
 		require.Equal(t, "service-0-pg", podRequest.Spec.Containers[1].Name)
 		require.Equal(t, 1, len(podRequest.Spec.Containers[1].Env))
 		require.Equal(t, "PG_USERNAME", podRequest.Spec.Containers[1].Env[0].Name)
diff --git a/engine/hatchery/vsphere/hatchery_test.go b/engine/hatchery/vsphere/hatchery_test.go
index b18d30905..7eba00a1e 100644
--- a/engine/hatchery/vsphere/hatchery_test.go
+++ b/engine/hatchery/vsphere/hatchery_test.go
@@ -381,7 +381,7 @@ func TestHatcheryVSphere_Status(t *testing.T) {
 		vSphereClient: c,
 		Common: hatchery.Common{
 			Common: service.Common{
-				GoRoutines: sdk.NewGoRoutines(),
+				GoRoutines: sdk.NewGoRoutines(context.Background()),
 			},
 		},
 	}
@@ -452,7 +452,7 @@ func TestHatcheryVSphere_provisioning_do_nothing(t *testing.T) {
 		vSphereClient: c,
 		Common: hatchery.Common{
 			Common: service.Common{
-				GoRoutines: sdk.NewGoRoutines(),
+				GoRoutines: sdk.NewGoRoutines(context.Background()),
 				Client:     cdsclient,
 			},
 		},
@@ -537,7 +537,7 @@ func TestHatcheryVSphere_provisioning_start_one(t *testing.T) {
 		vSphereClient: c,
 		Common: hatchery.Common{
 			Common: service.Common{
-				GoRoutines: sdk.NewGoRoutines(),
+				GoRoutines: sdk.NewGoRoutines(context.Background()),
 				Client:     cdsclient,
 			},
 		},
diff --git a/engine/repositories/repositories_test.go b/engine/repositories/repositories_test.go
index 4dcde5667..2e1a6428c 100644
--- a/engine/repositories/repositories_test.go
+++ b/engine/repositories/repositories_test.go
@@ -53,7 +53,7 @@ func newTestService(t *testing.T) (*Service, error) {
 	}
 
 	service := new(Service)
-	service.GoRoutines = sdk.NewGoRoutines()
+	service.GoRoutines = sdk.NewGoRoutines(ctx)
 	if fakeAPIPrivateKey.key == nil {
 		fakeAPIPrivateKey.key, _ = jws.NewRandomRSAKey()
 	}
diff --git a/engine/vcs/vcs_test.go b/engine/vcs/vcs_test.go
index 5a5a3d238..e373d5b5a 100644
--- a/engine/vcs/vcs_test.go
+++ b/engine/vcs/vcs_test.go
@@ -20,7 +20,7 @@ import (
 	"github.com/ovh/cds/sdk"
 	"github.com/ovh/cds/sdk/cdsclient"
 	"github.com/ovh/cds/sdk/jws"
-	"github.com/ovh/cds/sdk/log"
+	cdslog "github.com/ovh/cds/sdk/log"
 )
 
 var (
@@ -57,7 +57,7 @@ func newTestService(t *testing.T) (*Service, error) {
 	}
 
 	service := new(Service)
-	service.GoRoutines = sdk.NewGoRoutines()
+	service.GoRoutines = sdk.NewGoRoutines(ctx)
 	if fakeAPIPrivateKey.key == nil {
 		fakeAPIPrivateKey.key, _ = jws.NewRandomRSAKey()
 	}
diff --git a/sdk/goroutine_test.go b/sdk/goroutine_test.go
index 8ab8758bf..c1bf9a7e7 100644
--- a/sdk/goroutine_test.go
+++ b/sdk/goroutine_test.go
@@ -23,7 +23,7 @@ func Test_GoroutineTools(t *testing.T) {
 		var wg = new(sync.WaitGroup)
 		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 		defer cancel()
-		NewGoRoutines().Exec(ctx, "test_goroutine", func(ctx context.Context) {
+		NewGoRoutines(ctx).Exec(ctx, "test_goroutine", func(ctx context.Context) {
 			wg.Add(1)
 			<-ctx.Done()
 			wg.Done()
@@ -42,7 +42,7 @@ func Test_GoroutineTools(t *testing.T) {
 		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 		defer cancel()
 
-		NewGoRoutines().Exec(ctx, "test_goroutine", func(ctx context.Context) {
+		NewGoRoutines(ctx).Exec(ctx, "test_goroutine", func(ctx context.Context) {
 			wg.Add(1)
 			<-ctx.Done()
 			wg.Done()
@@ -63,18 +63,18 @@ func Test_GoroutineTools(t *testing.T) {
 		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
 		defer cancel()
 
-		m := NewGoRoutines()
+		m := NewGoRoutines(ctx)
 		m.Run(ctx, "test_goroutine_loop", func(ctx context.Context) {
 			wg.Add(1)
-			_, ok := m.status["test_goroutine_loop"]
-			require.True(t, ok)
-			require.True(t, m.status["test_goroutine_loop"])
+			s := m.GoRoutine("test_goroutine_loop")
+			require.NotNil(t, s)
+			require.True(t, s.Active)
 			<-ctx.Done()
 			wg.Done()
 		})
 
-		_, ok := m.status["test_goroutine_loop"]
-		require.True(t, ok)
+		s := m.GoRoutine("test_goroutine_loop")
+		require.NotNil(t, s)
 		require.Equal(t, 1, len(m.GetStatus()))
 	})
 }