├── _config.yml ├── docs ├── images │ ├── logo.png │ ├── gopher-pool.png │ ├── process-pool.png │ ├── process-monitoring.gif │ └── worker-monitoring.gif └── README.md ├── .idea ├── vcs.xml ├── .gitignore ├── modules.xml └── gowl.iml ├── go.mod ├── .github ├── scripts │ └── coverage.sh └── workflows │ └── build.yml ├── .fossa.yml ├── .gitignore ├── status ├── worker │ └── status.go ├── pool │ └── status.go └── process │ └── status.go ├── .golangci.yml ├── Makefile ├── LICENSE ├── go.sum ├── map_test.go ├── map.go ├── README.md ├── pool_test.go └── pool.go /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmdsefi/gowl/HEAD/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/gopher-pool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmdsefi/gowl/HEAD/docs/images/gopher-pool.png -------------------------------------------------------------------------------- /docs/images/process-pool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmdsefi/gowl/HEAD/docs/images/process-pool.png -------------------------------------------------------------------------------- /docs/images/process-monitoring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmdsefi/gowl/HEAD/docs/images/process-monitoring.gif -------------------------------------------------------------------------------- /docs/images/worker-monitoring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hmdsefi/gowl/HEAD/docs/images/worker-monitoring.gif -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hamed-yousefi/gowl 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /.github/scripts/coverage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.out 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.out 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /.idea/gowl.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.fossa.yml: -------------------------------------------------------------------------------- 1 | # Generated by FOSSA CLI (https://github.com/fossas/fossa-cli) 2 | # Visit https://fossa.com to learn more 3 | 4 | version: 2 5 | cli: 6 | server: https://app.fossa.com 7 | fetcher: custom 8 | project: https://github.com/hamed-yousefi/gowl.git 9 | analyze: 10 | modules: 11 | - name: github.com/hamed-yousefi/gowl 12 | type: go 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | cmd 17 | app 18 | -------------------------------------------------------------------------------- /status/worker/status.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package worker 6 | 7 | const ( 8 | // Waiting is a worker state when the worker is waiting to consume a process. 9 | Waiting Status = iota 10 | // Busy is a worker state when the worker consumed a process and running it. 11 | Busy 12 | ) 13 | 14 | var ( 15 | status2String = map[Status]string{ 16 | Waiting: "Waiting", 17 | Busy: "Busy", 18 | } 19 | ) 20 | 21 | type ( 22 | // Status represents worker current state. 23 | Status int 24 | ) 25 | 26 | // String returns string value of worker state. 27 | func (s Status) String() string { 28 | return status2String[s] 29 | } 30 | -------------------------------------------------------------------------------- /status/pool/status.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package pool 6 | 7 | const ( 8 | // Created is a pool state after pool has been created and before it starts. 9 | Created Status = iota 10 | // Running is a pool state when the pool started by Start() function. 11 | Running 12 | // Closed is a pool state when the pool stopped by Close() function. 13 | Closed 14 | ) 15 | 16 | var ( 17 | status2string = map[Status]string{ 18 | Created: "Created", 19 | Running: "Running", 20 | Closed: "Closed", 21 | } 22 | ) 23 | 24 | type ( 25 | // Status represents pool current state. 26 | Status int 27 | ) 28 | 29 | // String returns string value of pool state. 30 | func (p Status) String() string { 31 | return status2string[p] 32 | } 33 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: false 4 | gocyclo: 5 | min-complexity: 25 6 | goconst: 7 | min-len: 2 8 | min-occurrences: 2 9 | errcheck: 10 | check-type-assertions: true 11 | gocritic: 12 | disabled-checks: 13 | - ifElseChain 14 | nakedret: 15 | max-func-lines: 15 16 | 17 | run: 18 | skip-dirs: 19 | - mock 20 | 21 | linters: 22 | enable: 23 | - gocritic 24 | - stylecheck 25 | - goimports 26 | - gosec 27 | - unconvert 28 | - unparam 29 | - gochecknoinits 30 | - gosec 31 | - nakedret 32 | - whitespace 33 | - gosimple 34 | - bodyclose 35 | - dogsled 36 | - rowserrcheck 37 | disable: 38 | - maligned 39 | - lll 40 | - dupl 41 | - gochecknoglobals 42 | -------------------------------------------------------------------------------- /status/process/status.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package process 6 | 7 | const ( 8 | // Waiting is a process state when the process is waiting to consume by a worker. 9 | Waiting Status = iota 10 | // Running is a process state when it consumed by a worker. 11 | Running 12 | // Succeeded is a process state when it has been ended without error. 13 | Succeeded 14 | // Failed is a process state when it has been ended with error. 15 | Failed 16 | // Killed is a process state when the process cancelled before running. 17 | Killed 18 | ) 19 | 20 | var ( 21 | status2String = map[Status]string{ 22 | Waiting: "Waiting", 23 | Running: "Running", 24 | Succeeded: "Succeeded", 25 | Failed: "Failed", 26 | Killed: "Killed", 27 | } 28 | ) 29 | 30 | type ( 31 | // Status represents process current state. 32 | Status int 33 | ) 34 | 35 | // String returns string value of process state. 36 | func (s Status) String() string { 37 | return status2String[s] 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO = go 2 | M = $(shell printf "\033[34;1m>>\033[0m") 3 | 4 | # Check richgo does exist. 5 | ifeq (, $(shell which richgo)) 6 | $(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo") 7 | endif 8 | 9 | .PHONY: test sync codecov test-app 10 | 11 | .PHONY: default 12 | default: all 13 | 14 | .PHONY: all 15 | all: test 16 | 17 | .PHONY: test 18 | test: sync 19 | $(info running tests) 20 | richgo test -v ./... 21 | 22 | .PHONY: codecov 23 | codecov: sync 24 | $(info running tests coverage) 25 | sh build/script/coverage.sh 26 | 27 | .PHONY: sync 28 | sync: 29 | $(info downloading dependencies) 30 | go get -v ./... 31 | 32 | .PHONY: fmt 33 | fmt: 34 | $(info $(M) format code) 35 | @ret=0 && for d in $$($(GO) list -f '{{.Dir}}' ./... | grep -v /vendor/); do \ 36 | $(GO) fmt $$d/*.go || ret=$$? ; \ 37 | done ; exit $$ret 38 | 39 | .PHONY: lint 40 | lint: ## Run linters 41 | $(info $(M) running golangci linter) 42 | golangci-lint run --timeout 5m0s ./... 43 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hamed Yousefi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e h1:3i3ny04XV6HbZ2N1oIBw1UBYATHAOpo4tfTF83JM3Z0= 12 | gopkg.in/yaml.v3 v3.0.0-20220521103104-8f96da9f5d5e/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package gowl 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | 14 | "github.com/hamed-yousefi/gowl/status/worker" 15 | ) 16 | 17 | // Test controlPanelMap put and get functions 18 | func TestControlPanelMap(t *testing.T) { 19 | cp := new(controlPanelMap) 20 | ctx, cancel := context.WithCancel(context.Background()) 21 | pc := &processContext{ 22 | ctx: ctx, 23 | cancel: cancel, 24 | } 25 | cp.put("p-11", pc) 26 | 27 | a := assert.New(t) 28 | a.Equal(pc, cp.get("p-11")) 29 | } 30 | 31 | // Test workerStatsMap put and get functions 32 | func TestWorkerStatsMap(t *testing.T) { 33 | ws := new(workerStatsMap) 34 | ws.put("w1", worker.Busy) 35 | 36 | a := assert.New(t) 37 | a.Equal(worker.Busy, ws.get("w1")) 38 | } 39 | 40 | // Test processStatusMap put and get functions 41 | func TestProcessStatusMap(t *testing.T) { 42 | ps := new(processStatusMap) 43 | p := ProcessStats{ 44 | WorkerName: "w1", 45 | StartedAt: time.Now(), 46 | } 47 | ps.put("p-11", p) 48 | 49 | a := assert.New(t) 50 | a.Equal(p, ps.get("p-11")) 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | golangci: 5 | name: lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/setup-go@v3 9 | with: 10 | go-version: 1.19 11 | - uses: actions/checkout@v3 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v3 14 | with: 15 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 16 | version: v1.50.1 17 | 18 | build: 19 | name: Go build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | go-version: 1.19 25 | - name: Build 26 | run: | 27 | git clone --depth=1 https://github.com/${GITHUB_REPOSITORY} 28 | cd $(basename ${GITHUB_REPOSITORY}) 29 | go build -v -race 30 | 31 | test: 32 | name: Go test 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v3 36 | with: 37 | go-version: 1.19 38 | - name: go get & test 39 | run: | 40 | go get -v -t -d ./... 41 | go test -v ./... 42 | 43 | - name: Generate coverage report 44 | run: sh ./.github/scripts/coverage.sh 45 | shell: bash 46 | 47 | - name: Upload coverage to codecov 48 | uses: codecov/codecov-action@v3 49 | with: 50 | token: ${{ secrets.CODECOV_TOKEN }} 51 | files: ./coverage.out 52 | flags: unittests # optional 53 | name: codecov-umbrella # optional 54 | fail_ci_if_error: false # optional (default = false) 55 | verbose: true # optional (default = false) 56 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package gowl 6 | 7 | import ( 8 | "context" 9 | "sync" 10 | 11 | "github.com/hamed-yousefi/gowl/status/worker" 12 | ) 13 | 14 | type ( 15 | // controlPanelMap is a thread safe map for controlling processes. It also 16 | // provides type safety. 17 | // Key: PID 18 | // Value: processContext 19 | controlPanelMap struct { 20 | internal sync.Map 21 | } 22 | 23 | // workerStatsMap is a thread safe map for controlling processes. It also 24 | // provides type safety. 25 | // Key: WorkerName 26 | // Value: worker.Status 27 | workerStatsMap struct { 28 | internal sync.Map 29 | } 30 | 31 | // processStatusMap is a thread safe map for controlling processes. It also 32 | // provides type safety. 33 | // Key: PID 34 | // Value: ProcessStats 35 | processStatusMap struct { 36 | internal sync.Map 37 | } 38 | 39 | // processContext represents a cancellation context by holding a context and 40 | // a cancel function. 41 | processContext struct { 42 | ctx context.Context 43 | cancel context.CancelFunc 44 | } 45 | ) 46 | 47 | func (c *controlPanelMap) put(pid PID, pc *processContext) { 48 | c.internal.Store(pid, pc) 49 | } 50 | 51 | func (c *controlPanelMap) get(pid PID) *processContext { 52 | in, _ := c.internal.Load(pid) 53 | cancel, _ := in.(*processContext) 54 | return cancel 55 | } 56 | 57 | func (c *workerStatsMap) put(name WorkerName, status worker.Status) { 58 | c.internal.Store(name, status) 59 | } 60 | 61 | func (c *workerStatsMap) get(name WorkerName) worker.Status { 62 | in, _ := c.internal.Load(name) 63 | status, _ := in.(worker.Status) 64 | return status 65 | } 66 | 67 | func (c *processStatusMap) put(pid PID, stats ProcessStats) { 68 | c.internal.Store(pid, stats) 69 | } 70 | 71 | func (c *processStatusMap) get(pid PID) ProcessStats { 72 | in, _ := c.internal.Load(pid) 73 | stats, _ := in.(ProcessStats) 74 | return stats 75 | } 76 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Gowl 2 | [![Build Status](https://travis-ci.com/hamed-yousefi/gowl.svg?branch=master)](https://travis-ci.com/hamed-yousefi/gowl) 3 | [![codecov](https://codecov.io/gh/hamed-yousefi/gowl/branch/master/graph/badge.svg?token=1TYYX8IBR0)](https://codecov.io/gh/hamed-yousefi/gowl) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/hamed-yousefi/gowl)](https://goreportcard.com/report/github.com/hamed-yousefi/gowl) 5 | [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B24403%2Fgithub.com%2Fhamed-yousefi%2Fgowl.svg?type=shield)](https://app.fossa.com/projects/custom%2B24403%2Fgithub.com%2Fhamed-yousefi%2Fgowl?ref=badge_shield) 6 | [![Go Reference](https://pkg.go.dev/badge/github.com/hamed-yousefi/gowl.svg)](https://pkg.go.dev/github.com/hamed-yousefi/gowl) 7 |
8 | Gowl is a process management and process monitoring tool at once. 9 | An infinite worker pool gives you the ability to control the pool and processes 10 | and monitor their status. 11 | 12 | ## Table of Contents 13 | 14 | * [Install](#Install) 15 | * [How to use](#How-to-use) 16 | * [Pool](#Pooling) 17 | * [Start](#Start) 18 | * [Register process](#Register-process) 19 | * [Kill process](#Kill-process) 20 | * [Close](#Close) 21 | * [Monitor](#Monitor) 22 | * [License](#License) 23 | 24 | ## Install 25 | Using Gowl is easy. First, use `go get` to install the latest version of the library. 26 | This command will install the `gowl` along with library and its dependencies: 27 | ```shell 28 | go get -u github.com/hamed-yousefi/gowl 29 | ``` 30 | Next, include Gowl in your application: 31 | ```go 32 | import "github.com/hamed-yousefi/gowl" 33 | ``` 34 | 35 | ## How to use 36 | Gowl has three main parts. Process, Pool, and Monitor. The process is the 37 | smallest part of this project. The process is the part of code that the 38 | developer must implement. To do that, Gowl provides an interface to inject 39 | outside code into the pool. The process interface is as follows: 40 | 41 | ```go 42 | Process interface { 43 | Start() error 44 | Name() string 45 | PID() PID 46 | } 47 | ``` 48 | 49 | The process interface has three methods. The Start function contains the 50 | user codes, and the pool workers use this function to run the process. 51 | The Name function returns the process name, and the monitor uses this 52 | function to provide reports. The PID function returns process id. The 53 | process id is unique in the entire pool, and it will use by the pool and 54 | monitor. 55 | 56 | Let's take a look at an example: 57 | ```go 58 | Document struct { 59 | content string 60 | hash string 61 | } 62 | 63 | func (d *Document) Start() error { 64 | hasher := sha1.New() 65 | hasher.Write(bv) 66 | h.hash= base64.URLEncoding.EncodeToString(hasher.Sum(nil)) 67 | } 68 | 69 | func (d *Document) Name() string { 70 | return "hashing-process" 71 | } 72 | 73 | func (d *Document) PID() PID { 74 | return "p-1" 75 | } 76 | 77 | func (d *Document) Hash() string { 78 | return h.hash 79 | } 80 | ``` 81 | 82 | As you can see, in this example, `Document` implements the Process interface. 83 | So now we can register it into the pool. 84 | 85 | ### Pool 86 | Creating Gowl pool is very easy. You must use the `NewPool(size int)` 87 | function and pass the pool size to this function. Pool size indicates 88 | the worker numbers in and the underlying queue size that workers consume 89 | process from it. Look at the following example: 90 | 91 | ```go 92 | pool := gowl.NewPool(4) 93 | ``` 94 | In this example, Gowl will create a new instance of a Pool object with four workers 95 | and an underlying queue with the size of four. 96 | 97 | #### Start 98 | 99 | To start the Gowl, you must call the `Start()` method of the pool 100 | object. It will begin to create the workers, and workers start listening 101 | to the queue to consume process. 102 | 103 | #### Register process 104 | 105 | To register processes to the pool, you must use the `Register(args ...process)` 106 | method. Pass the processes to the register method, and it will create a 107 | new publisher to publish the process list to the queue. You can call multiple 108 | times when Gowl pool is running. 109 | 110 | #### Kill process 111 | 112 | One of the most remarkable features of Gowl is the ability to control the 113 | process after registered it into the pool. You can kill a process before 114 | any worker runs it. Killing a process is simple, and you need the process 115 | id to do it. 116 | 117 | ```go 118 | pool.Kill(PID("p-909")) 119 | ``` 120 | 121 | #### Close 122 | 123 | Gowl is an infinite worker pool. However, you should have control over 124 | the pool and decide when you want to start it, register a new process on 125 | it, kill a process, and `close` the pool and terminate the workers. Gowl 126 | gives you this option to close the pool by the `Close()` method of the 127 | Pool object. 128 | 129 | ## Monitor 130 | 131 | Every process management tool needs a monitoring system to expose the 132 | internal stats to the outside world. Gowl gives you a monitoring API 133 | to see processes and workers stats. 134 | 135 | You can get the Monitor instance by calling the `Monitor()` method of 136 | the Pool. The monitor object is as follows: 137 | 138 | ```go 139 | Monitor interface { 140 | PoolStatus() pool.Status 141 | Error(PID) error 142 | WorkerList() []WorkerName 143 | WorkerStatus(name WorkerName) worker.Status 144 | ProcessStats(pid PID) ProcessStats 145 | } 146 | ``` 147 | 148 | The Monitor gives you this opportunity to get the Pool status, process 149 | error, worker list, worker status, and process stats. Wis Monitor API, 150 | you can create your monitoring app with ease. The following example is 151 | using Monitor API to present the stats in the console in real-time. 152 | 153 | ![process-monitoring](https://github.com/hamed-yousefi/gowl/blob/master/docs/images/process-monitoring.gif) 154 | 155 | Also, you can use the Monitor API to show worker status in the console: 156 | 157 | ![worker-monitoring](https://github.com/hamed-yousefi/gowl/blob/master/docs/images/worker-monitoring.gif) 158 | 159 | ## License 160 | 161 | MIT License, please see [LICENSE](https://github.com/hamed-yousefi/gowl/blob/master/LICENSE) for details. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gowl 2 | 3 | ![build](https://github.com/hmdsefi/gowl/actions/workflows/build.yml/badge.svg) 4 | [![codecov](https://codecov.io/gh/hmdsefi/gowl/branch/master/graph/badge.svg?token=1TYYX8IBR0)](https://codecov.io/gh/hmdsefi/gowl) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/hamed-yousefi/gowl)](https://goreportcard.com/report/github.com/hamed-yousefi/gowl) 6 | [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B24403%2Fgithub.com%2Fhamed-yousefi%2Fgowl.svg?type=shield)](https://app.fossa.com/projects/custom%2B24403%2Fgithub.com%2Fhamed-yousefi%2Fgowl?ref=badge_shield) 7 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 8 | [![Go Reference](https://pkg.go.dev/badge/github.com/hamed-yousefi/gowl.svg)](https://pkg.go.dev/github.com/hamed-yousefi/gowl) 9 |
10 | Gowl is a process management and process monitoring tool at once. 11 | An infinite worker pool gives you the ability to control the pool and processes 12 | and monitor their status. 13 | 14 | ## Table of Contents 15 | 16 | * [Install](#Install) 17 | * [How to use](#How-to-use) 18 | * [Pool](#Pooling) 19 | * [Start](#Start) 20 | * [Register process](#Register-process) 21 | * [Kill process](#Kill-process) 22 | * [Close](#Close) 23 | * [Monitor](#Monitor) 24 | * [License](#License) 25 | 26 | ## Install 27 | 28 | Using Gowl is easy. First, use `go get` to install the latest version of the library. This command will install 29 | the `gowl` along with library and its dependencies: 30 | 31 | ```shell 32 | go get -u github.com/hamed-yousefi/gowl 33 | ``` 34 | 35 | Next, include Gowl in your application: 36 | 37 | ```go 38 | import "github.com/hamed-yousefi/gowl" 39 | ``` 40 | 41 | ## How to use 42 | 43 | Gowl has three main parts. Process, Pool, and Monitor. The process is the smallest part of this project. The process is 44 | the part of code that the developer must implement. To do that, Gowl provides an interface to inject outside code into 45 | the pool. The process interface is as follows: 46 | 47 | ```go 48 | Process interface { 49 | Start(ctx context.Context) error 50 | Name() string 51 | PID() PID 52 | } 53 | ``` 54 | 55 | The process interface has three methods. The Start function contains the user codes, and the pool workers use this 56 | function to run the process. The Name function returns the process name, and the monitor uses this function to provide 57 | reports. The PID function returns process id. The process id is unique in the entire pool, and it will use by the pool 58 | and monitor. 59 | 60 | If properly done it should respond to the context cancellation (if needed) 61 | 62 | Let's take a look at an example: 63 | 64 | ```go 65 | Document struct { 66 | content string 67 | hash string 68 | } 69 | 70 | func (d *Document) Start(ctx context.Context) error { 71 | hasher := sha1.New() 72 | hasher.Write(bv) 73 | h.hash = base64.URLEncoding.EncodeToString(hasher.Sum(nil)) 74 | } 75 | 76 | func (d *Document) Name() string { 77 | return "hashing-process" 78 | } 79 | 80 | func (d *Document) PID() PID { 81 | return "p-1" 82 | } 83 | 84 | func (d *Document) Hash() string { 85 | return h.hash 86 | } 87 | ``` 88 | 89 | As you can see, in this example, `Document` implements the Process interface. So now we can register it into the pool. 90 | 91 | ### Pool 92 | 93 | Creating Gowl pool is very easy. You must use the `NewPool(size int)` 94 | function and pass the pool size to this function. Pool size indicates the worker numbers in and the underlying queue 95 | size that workers consume process from it. Look at the following example: 96 | 97 | ```go 98 | pool := gowl.NewPool(4) 99 | ``` 100 | 101 | In this example, Gowl will create a new instance of a Pool object with four workers and an underlying queue with the 102 | size of four. 103 | 104 | #### Start 105 | 106 | To start the Gowl, you must call the `Start()` method of the pool object. It will begin to create the workers, and 107 | workers start listening to the queue to consume process. 108 | 109 | #### Register process 110 | 111 | To register processes to the pool, you must use the `Register(args ...process)` 112 | method. Pass the processes to the register method, and it will create a new publisher to publish the process list to the 113 | queue. You can call multiple times when Gowl pool is running. 114 | 115 | #### Kill process 116 | 117 | One of the most remarkable features of Gowl is the ability to control the process after registered it into the pool. You 118 | can kill a process before any worker runs it, this also works after the job have started if it consideres the context 119 | cancellation. Killing a process is simple, and you need the process id to do it. 120 | 121 | ```go 122 | pool.Kill(PID("p-909")) 123 | ``` 124 | 125 | #### Close 126 | 127 | Gowl is an infinite worker pool. However, you should have control over the pool and decide when you want to start it, 128 | register a new process on it, kill a process, and `close` the pool and terminate the workers. Gowl gives you this option 129 | to close the pool by the `Close()` method of the Pool object. 130 | 131 | ## Monitor 132 | 133 | Every process management tool needs a monitoring system to expose the internal stats to the outside world. Gowl gives 134 | you a monitoring API to see processes and workers stats. 135 | 136 | You can get the Monitor instance by calling the `Monitor()` method of the Pool. The monitor object is as follows: 137 | 138 | ```go 139 | Monitor interface { 140 | PoolStatus() pool.Status 141 | Error(PID) error 142 | WorkerList() []WorkerName 143 | WorkerStatus(name WorkerName) worker.Status 144 | ProcessStats(pid PID) ProcessStats 145 | } 146 | ``` 147 | 148 | The Monitor gives you this opportunity to get the Pool status, process error, worker list, worker status, and process 149 | stats. Wis Monitor API, you can create your monitoring app with ease. The following example is using Monitor API to 150 | present the stats in the console in real-time. 151 | 152 | ![process-monitoring](https://github.com/hamed-yousefi/gowl/blob/master/docs/images/process-monitoring.gif) 153 | 154 | Also, you can use the Monitor API to show worker status in the console: 155 | 156 | ![worker-monitoring](https://github.com/hamed-yousefi/gowl/blob/master/docs/images/worker-monitoring.gif) 157 | 158 | ## License 159 | 160 | MIT License, please see [LICENSE](https://github.com/hamed-yousefi/gowl/blob/master/LICENSE) for details. 161 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package gowl 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "strconv" 12 | "testing" 13 | "time" 14 | 15 | "github.com/stretchr/testify/assert" 16 | 17 | "github.com/hamed-yousefi/gowl/status/pool" 18 | "github.com/hamed-yousefi/gowl/status/process" 19 | ) 20 | 21 | type ( 22 | pTestFunc func(ctx context.Context, pid PID, duration time.Duration) error 23 | mockProcess struct { 24 | name string 25 | pid PID 26 | sleepTime time.Duration 27 | pFunc pTestFunc 28 | } 29 | ) 30 | 31 | func (t mockProcess) Start(ctx context.Context) error { 32 | return t.pFunc(ctx, t.pid, t.sleepTime) 33 | } 34 | 35 | func (t mockProcess) Name() string { 36 | return t.name 37 | } 38 | 39 | func (t mockProcess) PID() PID { 40 | return t.pid 41 | } 42 | 43 | func newTestProcess(name string, id int, duration time.Duration, f pTestFunc) Process { 44 | return mockProcess{ 45 | name: name, 46 | pid: PID("p-" + strconv.Itoa(id)), 47 | sleepTime: duration, 48 | pFunc: f, 49 | } 50 | } 51 | 52 | var errCancelled = errors.New("task was cancelled") 53 | 54 | // Close pool before adding all processes to the queue 55 | func TestNewPool(t *testing.T) { 56 | a := assert.New(t) 57 | wp := NewPool(2) 58 | 59 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 60 | wp.Register(createProcess(10, 1, 300*time.Millisecond, processFunc)...) 61 | err := wp.Start() 62 | a.NoError(err) 63 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 64 | time.Sleep(500 * time.Millisecond) 65 | err = wp.Close() 66 | a.NoError(err) 67 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 68 | } 69 | 70 | // Four different goroutine will publish processes to the queue 71 | func TestNewPoolMultiPublisher(t *testing.T) { 72 | a := assert.New(t) 73 | wp := NewPool(2) 74 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 75 | err := wp.Start() 76 | a.NoError(err) 77 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 78 | wp.Register(createProcess(10, 1, 300*time.Millisecond, processFunc)...) 79 | wp.Register(createProcess(10, 2, 200*time.Millisecond, processFunc)...) 80 | wp.Register(createProcess(10, 3, 100*time.Millisecond, processFunc)...) 81 | wp.Register(createProcess(10, 4, 500*time.Millisecond, processFunc)...) 82 | 83 | time.Sleep(10 * time.Second) 84 | err = wp.Close() 85 | a.NoError(err) 86 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 87 | } 88 | 89 | // Kill a processFunc before it starts 90 | func TestWorkerPool_Kill(t *testing.T) { 91 | a := assert.New(t) 92 | wp := NewPool(5) 93 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 94 | err := wp.Start() 95 | a.NoError(err) 96 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 97 | wp.Register(createProcess(10, 1, 3*time.Second, processFunc)...) 98 | wp.Kill("p-18") 99 | time.Sleep(7 * time.Second) 100 | err = wp.Close() 101 | a.NoError(err) 102 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 103 | a.Equal(process.Killed, wp.Monitor().ProcessStats("p-18").Status) 104 | } 105 | 106 | // Kill a processFunc after it started 107 | func TestWorkerPoolStarted_Kill(t *testing.T) { 108 | a := assert.New(t) 109 | wp := NewPool(3) 110 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 111 | err := wp.Start() 112 | a.NoError(err) 113 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 114 | wp.Register(createProcess(3, 1, 3*time.Second, processFunc)...) 115 | time.Sleep(2 * time.Second) 116 | wp.Kill("p-12") 117 | err = wp.Close() 118 | a.NoError(err) 119 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 120 | a.Equal(process.Killed, wp.Monitor().ProcessStats("p-12").Status) 121 | a.Error(wp.Monitor().Error("p-12")) 122 | a.Equal("task was cancelled", wp.Monitor().Error("p-12").Error()) 123 | } 124 | 125 | // Process returns error and monitor should cache it 126 | func TestMonitor_Error(t *testing.T) { 127 | a := assert.New(t) 128 | wp := NewPool(5) 129 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 130 | err := wp.Start() 131 | a.NoError(err) 132 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 133 | wp.Register(createProcess(1, 1, 1*time.Second, processFuncWithError)...) 134 | time.Sleep(2 * time.Second) 135 | err = wp.Close() 136 | a.NoError(err) 137 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 138 | a.Equal(process.Failed, wp.Monitor().ProcessStats("p-11").Status) 139 | a.Error(wp.Monitor().Error("p-11")) 140 | a.Equal("unable to start processFunc with id: p-11", wp.Monitor().Error("p-11").Error()) 141 | } 142 | 143 | // Close a created pool should return error 144 | func TestWorkerPool_Close(t *testing.T) { 145 | a := assert.New(t) 146 | wp := NewPool(3) 147 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 148 | err := wp.Close() 149 | a.Error(err) 150 | a.Equal("pool is not running, status "+wp.Monitor().PoolStatus().String(), err.Error()) 151 | err = wp.Start() 152 | a.NoError(err) 153 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 154 | wp.Register(createProcess(1, 1, 100*time.Millisecond, processFunc)...) 155 | time.Sleep(1 * time.Second) 156 | err = wp.Close() 157 | a.NoError(err) 158 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 159 | } 160 | 161 | // Get worker list and check their status 162 | func TestWorkerPool_WorkerList(t *testing.T) { 163 | a := assert.New(t) 164 | wp := NewPool(3) 165 | a.Equal(pool.Created, wp.Monitor().PoolStatus()) 166 | err := wp.Close() 167 | a.Error(err) 168 | a.Equal("pool is not running, status "+wp.Monitor().PoolStatus().String(), err.Error()) 169 | err = wp.Start() 170 | a.NoError(err) 171 | a.Equal(pool.Running, wp.Monitor().PoolStatus()) 172 | wp.Register(createProcess(5, 1, 700*time.Millisecond, processFunc)...) 173 | time.Sleep(1 * time.Second) 174 | err = wp.Start() 175 | a.Error(err) 176 | a.Equal("unable to start the pool, status: "+pool.Running.String(), err.Error()) 177 | wList := wp.Monitor().WorkerList() 178 | for _, wn := range wList { 179 | fmt.Println(wp.Monitor().WorkerStatus(wn)) 180 | } 181 | err = wp.Close() 182 | a.NoError(err) 183 | a.Equal(pool.Closed, wp.Monitor().PoolStatus()) 184 | } 185 | 186 | func createProcess(n int, g int, d time.Duration, f pTestFunc) []Process { 187 | pList := make([]Process, 0) 188 | for i := 1; i <= n; i++ { 189 | pList = append(pList, newTestProcess("p-"+strconv.Itoa(i), (g*10)+i, d, f)) 190 | } 191 | return pList 192 | } 193 | 194 | func processFunc(ctx context.Context, pid PID, d time.Duration) error { 195 | fmt.Printf("process with id %v has been started.\n", pid) 196 | select { 197 | case <-time.After(d): 198 | case <-ctx.Done(): 199 | return errCancelled 200 | } 201 | return nil 202 | } 203 | 204 | func processFuncWithError(ctx context.Context, pid PID, d time.Duration) error { 205 | return errors.New("unable to start processFunc with id: " + pid.String()) 206 | } 207 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © 2019 Hamed Yousefi . 3 | */ 4 | 5 | package gowl 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | "log" 12 | "sync" 13 | "time" 14 | 15 | "github.com/hamed-yousefi/gowl/status/pool" 16 | "github.com/hamed-yousefi/gowl/status/process" 17 | "github.com/hamed-yousefi/gowl/status/worker" 18 | ) 19 | 20 | const ( 21 | // defaultWorkerName is the default worker name prefix. 22 | defaultWorkerName = "W%d" 23 | ) 24 | 25 | type ( 26 | // WorkerName is a custom type of string that represents worker's name. 27 | WorkerName string 28 | 29 | // PID is a custom type of string that represents process id. 30 | PID string 31 | 32 | // Process is an interface that represents a process 33 | Process interface { 34 | // Start runs the process. It returns an error object if any thing wrong 35 | // happens in runtime. 36 | Start(ctx context.Context) error 37 | // Name returns process name. 38 | Name() string 39 | // PID returns process id. 40 | PID() PID 41 | } 42 | 43 | // Pool is a mechanism to dispatch processes between a group of workers. 44 | Pool interface { 45 | // Start runs the pool. 46 | Start() error 47 | // Register adds the process to the pool queue. 48 | Register(p ...Process) 49 | // Close stops a running pool. 50 | Close() error 51 | // Kill cancels a process before it starts. 52 | Kill(pid PID) 53 | // Monitor returns pool monitor. 54 | Monitor() Monitor 55 | } 56 | 57 | // Monitor is a mechanism for observation processes and pool stats. 58 | Monitor interface { 59 | // PoolStatus returns pool status 60 | PoolStatus() pool.Status 61 | // Error returns process's error by process id. 62 | Error(PID) error 63 | // WorkerList returns the list of worker names of the pool. 64 | WorkerList() []WorkerName 65 | // WorkerStatus returns worker status. It accepts worker name as input. 66 | WorkerStatus(name WorkerName) worker.Status 67 | // ProcessStats returns process stats. It accepts process id as input. 68 | ProcessStats(pid PID) ProcessStats 69 | } 70 | 71 | // ProcessStats represents process statistics. 72 | ProcessStats struct { 73 | // WorkerName is the name of the worker that this process belongs to. 74 | WorkerName WorkerName 75 | 76 | // Process is process that this stats belongs to. 77 | Process Process 78 | 79 | // Status represents the current state of the process. 80 | Status process.Status 81 | 82 | // StartedAt represents the start date time of the process. 83 | StartedAt time.Time 84 | 85 | // FinishedAt represents the end date time of the process. 86 | FinishedAt time.Time 87 | 88 | err error 89 | } 90 | 91 | // workerPool is an implementation of Pool and Monitor interfaces. 92 | workerPool struct { 93 | status pool.Status 94 | size int 95 | queue chan Process 96 | wg *sync.WaitGroup 97 | processes *processStatusMap 98 | workers []WorkerName 99 | workersStats *workerStatsMap 100 | controlPanel *controlPanelMap 101 | mutex *sync.Mutex 102 | isClosed bool 103 | } 104 | ) 105 | 106 | // NewPool makes a new instance of Pool. I accept an integer value as input 107 | // that represents pool size. 108 | func NewPool(size int) Pool { 109 | return &workerPool{ 110 | status: pool.Created, 111 | size: size, 112 | queue: make(chan Process, size), 113 | workers: []WorkerName{}, 114 | processes: new(processStatusMap), 115 | workersStats: new(workerStatsMap), 116 | controlPanel: new(controlPanelMap), 117 | mutex: new(sync.Mutex), 118 | wg: new(sync.WaitGroup), 119 | } 120 | } 121 | 122 | // Start runs the pool. It returns error if pool is already in running state. 123 | // It changes the pool state to Running and calls workerPool.run() function to 124 | // run the pool. 125 | func (w *workerPool) Start() error { 126 | if w.status == pool.Running { 127 | return errors.New("unable to start the pool, status: " + w.status.String()) 128 | } 129 | 130 | w.status = pool.Running 131 | w.run() 132 | 133 | return nil 134 | } 135 | 136 | // run is the function that creates worker and starts the pool. 137 | func (w *workerPool) run() { 138 | // Create workers 139 | for i := 0; i < w.size; i++ { 140 | // For each worker add one to the waitGroup. 141 | w.wg.Add(1) 142 | wName := WorkerName(fmt.Sprintf(defaultWorkerName, i)) 143 | w.workers = append(w.workers, wName) 144 | 145 | // Create worker. 146 | go func(wn WorkerName) { 147 | defer w.wg.Done() 148 | 149 | // Consume process from the queue. 150 | for p := range w.queue { 151 | w.workersStats.put(wn, worker.Busy) 152 | pStats := w.processes.get(p.PID()) 153 | pStats.Status = process.Running 154 | pStats.StartedAt = time.Now() 155 | pStats.WorkerName = wn 156 | w.processes.put(p.PID(), pStats) 157 | wgp := new(sync.WaitGroup) 158 | wgp.Add(1) 159 | 160 | go func() { 161 | stats := w.processes.get(p.PID()) 162 | defer func() { 163 | w.processes.put(p.PID(), stats) 164 | wgp.Done() 165 | }() 166 | pContext := w.controlPanel.get(p.PID()) 167 | select { 168 | case <-pContext.ctx.Done(): 169 | log.Printf("processFunc with id %s has been killed.\n", p.PID().String()) 170 | stats.Status = process.Killed 171 | return 172 | default: 173 | if err := p.Start(pContext.ctx); err != nil { //nolint:typecheck 174 | stats.err = err 175 | stats.Status = process.Failed 176 | if errors.Is(pContext.ctx.Err(), context.Canceled) { 177 | stats.Status = process.Killed 178 | } 179 | } else { 180 | stats.Status = process.Succeeded 181 | } 182 | pContext.cancel() 183 | } 184 | }() 185 | 186 | wgp.Wait() 187 | pStats = w.processes.get(p.PID()) 188 | pStats.FinishedAt = time.Now() 189 | w.processes.put(p.PID(), pStats) 190 | w.workersStats.put(wn, worker.Waiting) 191 | } 192 | }(wName) 193 | } 194 | } 195 | 196 | // Register adds the process to the pool queue. It accept a list of processes 197 | // and adds them to the queue. It publishes the process to queue in a separate 198 | // goroutine. It means that Register function provides multi-publisher that 199 | // each of them works asynchronously. 200 | func (w *workerPool) Register(args ...Process) { 201 | // Create control panel for each process and make process stat for each of them. 202 | for _, p := range args { 203 | ctx, cancel := context.WithCancel(context.Background()) 204 | w.controlPanel.put(p.PID(), &processContext{ 205 | ctx: ctx, 206 | cancel: cancel, 207 | }) 208 | w.processes.put(p.PID(), ProcessStats{ 209 | Process: p, 210 | Status: process.Waiting, 211 | }) 212 | } 213 | 214 | // Publish processes to the queue. 215 | go func(args ...Process) { 216 | for i := range args { 217 | w.mutex.Lock() 218 | if w.isClosed { 219 | break 220 | } 221 | w.queue <- args[i] 222 | w.mutex.Unlock() 223 | } 224 | }(args...) 225 | } 226 | 227 | // Close stops a running pool. It returns an error if the pool is not running. 228 | // Close waits for all workers to finish their current job and then closes the 229 | // pool. 230 | func (w *workerPool) Close() error { 231 | if w.status != pool.Running { 232 | return errors.New("pool is not running, status " + w.status.String()) 233 | } 234 | 235 | w.mutex.Lock() 236 | w.isClosed = true 237 | close(w.queue) 238 | w.mutex.Unlock() 239 | 240 | w.wg.Wait() 241 | w.status = pool.Closed 242 | 243 | return nil 244 | } 245 | 246 | // WorkerList returns the list of worker names of the pool. 247 | func (w *workerPool) WorkerList() []WorkerName { 248 | return w.workers 249 | } 250 | 251 | // Kill cancel a process before it starts. 252 | func (w *workerPool) Kill(pid PID) { 253 | w.controlPanel.get(pid).cancel() 254 | } 255 | 256 | // Monitor returns pool monitor. 257 | func (w *workerPool) Monitor() Monitor { 258 | return w 259 | } 260 | 261 | // String returns the string value of process id. 262 | func (p PID) String() string { 263 | return string(p) 264 | } 265 | 266 | // PoolStatus returns pool status 267 | func (w *workerPool) PoolStatus() pool.Status { 268 | return w.status 269 | } 270 | 271 | // Error returns process's error by process id. 272 | func (w *workerPool) Error(pid PID) error { 273 | return w.processes.get(pid).err 274 | } 275 | 276 | // WorkerStatus returns worker status. It accepts worker name as input. 277 | func (w *workerPool) WorkerStatus(name WorkerName) worker.Status { 278 | return w.workersStats.get(name) 279 | } 280 | 281 | // ProcessStats returns process stats. It accepts process id as input. 282 | func (w *workerPool) ProcessStats(pid PID) ProcessStats { 283 | return w.processes.get(pid) 284 | } 285 | --------------------------------------------------------------------------------