You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2023/03/21 16:47:29 UTC

[plc4x] branch develop updated: feat(plc4go/spi): initial dynamic executor for worker pool

This is an automated email from the ASF dual-hosted git repository.

sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/develop by this push:
     new ea79a34ba0 feat(plc4go/spi): initial dynamic executor for worker pool
ea79a34ba0 is described below

commit ea79a34ba0b718dab1432b4ea672719ccfadf94c
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Mar 21 17:47:20 2023 +0100

    feat(plc4go/spi): initial dynamic executor for worker pool
---
 plc4go/spi/utils/WorkerPool.go | 104 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 87 insertions(+), 17 deletions(-)

diff --git a/plc4go/spi/utils/WorkerPool.go b/plc4go/spi/utils/WorkerPool.go
index f00caa2e56..86684dd5a5 100644
--- a/plc4go/spi/utils/WorkerPool.go
+++ b/plc4go/spi/utils/WorkerPool.go
@@ -33,11 +33,21 @@ import (
 type Runnable func()
 
 type worker struct {
-	id          int
-	shutdown    atomic.Bool
-	interrupted atomic.Bool
-	executor    *executor
-	hasEnded    bool
+	id           int
+	shutdown     atomic.Bool
+	interrupted  atomic.Bool
+	interrupter  chan struct{}
+	executor     *executor
+	hasEnded     bool
+	lastReceived time.Time
+}
+
+func (w *worker) initialize() {
+	w.shutdown.Store(false)
+	w.interrupted.Store(false)
+	w.interrupter = make(chan struct{})
+	w.hasEnded = false
+	w.lastReceived = time.Now()
 }
 
 func (w *worker) work() {
@@ -60,6 +70,7 @@ func (w *worker) work() {
 		workerLog.Debug().Msg("Working")
 		select {
 		case _workItem := <-w.executor.queue:
+			w.lastReceived = time.Now()
 			workerLog.Debug().Msgf("Got work item %v", _workItem)
 			if _workItem.completionFuture.cancelRequested.Load() || (w.shutdown.Load() && w.interrupted.Load()) {
 				workerLog.Debug().Msg("We need to stop")
@@ -70,6 +81,8 @@ func (w *worker) work() {
 				_workItem.completionFuture.complete()
 				workerLog.Debug().Msgf("work item %v completed", _workItem)
 			}
+		case <-w.interrupter:
+			log.Debug().Msg("We got interrupted")
 		default:
 			workerLog.Debug().Msgf("Idling")
 			time.Sleep(time.Millisecond * 10)
@@ -96,12 +109,13 @@ type Executor interface {
 }
 
 type executor struct {
-	running      bool
-	shutdown     bool
-	stateChange  sync.Mutex
-	worker       []*worker
-	queue        chan workItem
-	traceWorkers bool
+	maxNumberOfWorkers int
+	running            bool
+	shutdown           bool
+	stateChange        sync.Mutex
+	worker             []*worker
+	queue              chan workItem
+	traceWorkers       bool
 }
 
 func NewFixedSizeExecutor(numberOfWorkers, queueDepth int, options ...ExecutorOption) Executor {
@@ -111,17 +125,71 @@ func NewFixedSizeExecutor(numberOfWorkers, queueDepth int, options ...ExecutorOp
 			id: i,
 		}
 	}
-	executor := &executor{
-		queue:  make(chan workItem, queueDepth),
-		worker: workers,
+	_executor := &executor{
+		maxNumberOfWorkers: numberOfWorkers,
+		queue:              make(chan workItem, queueDepth),
+		worker:             workers,
 	}
 	for _, option := range options {
-		option(executor)
+		option(_executor)
 	}
 	for i := 0; i < numberOfWorkers; i++ {
-		workers[i].executor = executor
+		workers[i].executor = _executor
+	}
+	return _executor
+}
+
+func NewDynamicExecutor(maxNumberOfWorkers, queueDepth int, options ...ExecutorOption) Executor {
+	_executor := &executor{
+		maxNumberOfWorkers: maxNumberOfWorkers,
+		queue:              make(chan workItem, queueDepth),
+		worker:             make([]*worker, 0),
+	}
+	for _, option := range options {
+		option(_executor)
 	}
-	return executor
+	// We spawn one initial worker
+	_executor.worker = append(_executor.worker, &worker{
+		id:       0,
+		executor: _executor,
+	})
+	mutex := sync.Mutex{}
+	// Worker spawner
+	go func() {
+		for {
+			time.Sleep(100 * time.Millisecond)
+			mutex.Lock()
+			if len(_executor.queue) > len(_executor.worker) && len(_executor.worker) < maxNumberOfWorkers {
+				worker := &worker{
+					id:       len(_executor.worker) - 1,
+					executor: _executor,
+				}
+				_executor.worker = append(_executor.worker, worker)
+				worker.initialize()
+				go worker.work()
+			}
+			mutex.Unlock()
+		}
+	}()
+	// Worker killer
+	go func() {
+		for {
+			time.Sleep(5 * time.Second)
+			mutex.Lock()
+			newWorkers := make([]*worker, 0)
+			for _, worker := range _executor.worker {
+				if worker.lastReceived.Before(time.Now().Add(-5 * time.Second)) {
+					worker.interrupted.Store(true)
+					close(worker.interrupter)
+				} else {
+					newWorkers = append(newWorkers, worker)
+				}
+			}
+			_executor.worker = newWorkers
+			mutex.Unlock()
+		}
+	}()
+	return _executor
 }
 
 type ExecutorOption func(*executor)
@@ -160,6 +228,7 @@ func (e *executor) Start() {
 	e.shutdown = false
 	for i := 0; i < len(e.worker); i++ {
 		worker := e.worker[i]
+		worker.initialize()
 		go worker.work()
 	}
 }
@@ -176,6 +245,7 @@ func (e *executor) Stop() {
 		worker := e.worker[i]
 		worker.shutdown.Store(true)
 		worker.interrupted.Store(true)
+		close(worker.interrupter)
 	}
 	e.running = false
 	e.shutdown = false