├── .github ├── dependabot.yml └── workflows │ ├── codeql.yaml │ ├── go.yml │ └── goreleaser.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── _example ├── producer-consumer │ ├── README.md │ ├── consumer │ │ └── main.go │ ├── go.mod │ ├── go.sum │ └── producer │ │ └── main.go └── worker │ ├── go.mod │ ├── go.sum │ └── main.go ├── go.mod ├── go.sum ├── images └── redis-stream.png ├── options.go ├── redis.go └── redis_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gomod 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: "30 1 * * 0" 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | # required for all workflows 24 | security-events: write 25 | 26 | # only required for workflows in private repositories 27 | actions: read 28 | contents: read 29 | 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | # Override automatic language detection by changing the below list 34 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 35 | # TODO: Enable for javascript later 36 | language: ["go"] 37 | 38 | steps: 39 | - name: Checkout repository 40 | uses: actions/checkout@v4 41 | 42 | # Initializes the CodeQL tools for scanning. 43 | - name: Initialize CodeQL 44 | uses: github/codeql-action/init@v3 45 | with: 46 | languages: ${{ matrix.language }} 47 | # If you wish to specify custom queries, you can do so here or in a config file. 48 | # By default, queries listed here will override any specified in a config file. 49 | # Prefix the list here with "+" to use these queries and those in the config file. 50 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 51 | 52 | - name: Perform CodeQL Analysis 53 | uses: github/codeql-action/analyze@v3 54 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Run Testing 2 | on: push 3 | 4 | jobs: 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Setup go 9 | uses: actions/setup-go@v5 10 | with: 11 | go-version: "^1" 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | - name: Setup golangci-lint 15 | uses: golangci/golangci-lint-action@v6 16 | with: 17 | version: latest 18 | args: --verbose 19 | 20 | # Label of the container job 21 | test: 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest] 25 | go: [1.22, 1.23, 1.24] 26 | include: 27 | - os: ubuntu-latest 28 | go-build: ~/.cache/go-build 29 | name: ${{ matrix.os }} @ Go ${{ matrix.go }} 30 | runs-on: ${{ matrix.os }} 31 | 32 | steps: 33 | - name: Set up Go ${{ matrix.go }} 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: ${{ matrix.go }} 37 | 38 | - name: Checkout Code 39 | uses: actions/checkout@v4 40 | with: 41 | ref: ${{ github.ref }} 42 | 43 | - uses: actions/cache@v4 44 | with: 45 | path: | 46 | ${{ matrix.go-build }} 47 | ~/go/pkg/mod 48 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 49 | restore-keys: | 50 | ${{ runner.os }}-go- 51 | - name: Run Tests 52 | run: | 53 | go test -v -covermode=atomic -coverprofile=coverage.out 54 | 55 | - name: Upload coverage to Codecov 56 | uses: codecov/codecov-action@v5 57 | with: 58 | flags: ${{ matrix.os }},go-${{ matrix.go }} 59 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: Goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | goreleaser: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: "^1" 23 | - name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v6 25 | with: 26 | # either 'goreleaser' (default) or 'goreleaser-pro' 27 | distribution: goreleaser 28 | version: latest 29 | args: release --clean 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: false 3 | disable-all: true 4 | fast: false 5 | enable: 6 | - bodyclose 7 | - dogsled 8 | - dupl 9 | - errcheck 10 | - exportloopref 11 | - exhaustive 12 | - gochecknoinits 13 | - goconst 14 | - gocritic 15 | - gocyclo 16 | - gofmt 17 | - goimports 18 | - goprintffuncname 19 | - gosec 20 | - gosimple 21 | - govet 22 | - ineffassign 23 | - lll 24 | - misspell 25 | - nakedret 26 | - noctx 27 | - nolintlint 28 | - staticcheck 29 | - stylecheck 30 | - typecheck 31 | - unconvert 32 | - unparam 33 | - unused 34 | - whitespace 35 | - gofumpt 36 | 37 | run: 38 | timeout: 3m 39 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - # If true, skip the build. 3 | # Useful for library projects. 4 | # Default is false 5 | skip: true 6 | 7 | changelog: 8 | # Set it to true if you wish to skip the changelog generation. 9 | # This may result in an empty release notes on GitHub/GitLab/Gitea. 10 | disable: false 11 | 12 | # Changelog generation implementation to use. 13 | # 14 | # Valid options are: 15 | # - `git`: uses `git log`; 16 | # - `github`: uses the compare GitHub API, appending the author login to the changelog. 17 | # - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog. 18 | # - `github-native`: uses the GitHub release notes generation API, disables the groups feature. 19 | # 20 | # Defaults to `git`. 21 | use: github 22 | 23 | # Sorts the changelog by the commit's messages. 24 | # Could either be asc, desc or empty 25 | # Default is empty 26 | sort: asc 27 | 28 | # Group commits messages by given regex and title. 29 | # Order value defines the order of the groups. 30 | # Proving no regex means all commits will be grouped under the default group. 31 | # Groups are disabled when using github-native, as it already groups things by itself. 32 | # 33 | # Default is no groups. 34 | groups: 35 | - title: Features 36 | regexp: "^.*feat[(\\w-)]*:+.*$" 37 | order: 0 38 | - title: "Bug fixes" 39 | regexp: "^.*fix[(\\w-)]*:+.*$" 40 | order: 1 41 | - title: "Enhancements" 42 | regexp: "^.*chore[(\\w-)]*:+.*$" 43 | order: 2 44 | - title: "Refactor" 45 | regexp: "^.*refactor[(\\w-)]*:+.*$" 46 | order: 3 47 | - title: "Build process updates" 48 | regexp: ^.*?(build|ci)(\(.+\))??!?:.+$ 49 | order: 4 50 | - title: "Documentation updates" 51 | regexp: ^.*?docs?(\(.+\))??!?:.+$ 52 | order: 4 53 | - title: Others 54 | order: 999 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 golang-queue 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # redisdb-stream 2 | 3 | [![Run Testing](https://github.com/golang-queue/redisdb-stream/actions/workflows/go.yml/badge.svg)](https://github.com/golang-queue/redisdb-stream/actions/workflows/go.yml) 4 | [![codecov](https://codecov.io/gh/golang-queue/redisdb-stream/branch/main/graph/badge.svg?token=FFZN8E2ZZB)](https://codecov.io/gh/golang-queue/redisdb-stream) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/golang-queue/redisdb-stream)](https://goreportcard.com/report/github.com/golang-queue/redisdb-stream) 6 | 7 | [Redis Streams](https://redis.io/docs/data-types/streams-tutorial/) as backend for Queue Package 8 | 9 | ## System Flow 10 | 11 | ![flow](./images/redis-stream.png) 12 | -------------------------------------------------------------------------------- /_example/producer-consumer/README.md: -------------------------------------------------------------------------------- 1 | # Producer-Consumer 2 | 3 | This example demonstrates how to use the `Producer` and `Consumer` classes to create a simple producer-consumer system. 4 | 5 | ## Producer 6 | 7 | The producer is responsible for creating tasks and adding them to the queue. Below is an example of a producer implementation: 8 | 9 | ```go 10 | package main 11 | 12 | import ( 13 | "encoding/json" 14 | "fmt" 15 | "log" 16 | "time" 17 | 18 | "github.com/golang-queue/queue" 19 | "github.com/golang-queue/redisdb-stream" 20 | ) 21 | 22 | type job struct { 23 | Message string 24 | } 25 | 26 | func (j *job) Bytes() []byte { 27 | b, err := json.Marshal(j) 28 | if err != nil { 29 | panic(err) 30 | } 31 | return b 32 | } 33 | 34 | func main() { 35 | taskN := 5 36 | 37 | // define the worker 38 | w := redisdb.NewWorker( 39 | redisdb.WithAddr("127.0.0.1:6379"), 40 | redisdb.WithStreamName("foobar"), 41 | ) 42 | 43 | // define the queue 44 | q := queue.NewPool( 45 | 0, 46 | queue.WithWorker(w), 47 | ) 48 | 49 | // assign tasks in queue 50 | for i := 0; i < taskN; i++ { 51 | go func(i int) { 52 | if err := q.Queue(&job{ 53 | Message: fmt.Sprintf("handle the job: %d", i+1), 54 | }); err != nil { 55 | log.Fatal(err) 56 | } 57 | }(i) 58 | } 59 | 60 | time.Sleep(2 * time.Second) 61 | // shutdown the service and notify all the worker 62 | q.Release() 63 | } 64 | ``` 65 | 66 | ## Consumer 67 | 68 | The consumer is responsible for processing the tasks. Below is an example of a consumer implementation: 69 | 70 | ```go 71 | package main 72 | 73 | import ( 74 | "context" 75 | "encoding/json" 76 | "fmt" 77 | "time" 78 | 79 | "github.com/appleboy/graceful" 80 | "github.com/golang-queue/queue" 81 | "github.com/golang-queue/queue/core" 82 | "github.com/golang-queue/redisdb-stream" 83 | ) 84 | 85 | type job struct { 86 | Message string 87 | } 88 | 89 | func (j *job) Bytes() []byte { 90 | b, err := json.Marshal(j) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return b 95 | } 96 | 97 | func main() { 98 | taskN := 10000 99 | rets := make(chan string, taskN) 100 | 101 | m := graceful.NewManager() 102 | 103 | // define the worker 104 | w := redisdb.NewWorker( 105 | redisdb.WithAddr("127.0.0.1:6379"), 106 | redisdb.WithStreamName("foobar"), 107 | redisdb.WithGroup("foobar"), 108 | redisdb.WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 109 | var v job 110 | if err := json.Unmarshal(m.Payload(), &v); err != nil { 111 | return err 112 | } 113 | rets <- v.Message 114 | time.Sleep(2 * time.Second) 115 | return nil 116 | }), 117 | ) 118 | 119 | // define the queue 120 | q := queue.NewPool( 121 | 1, 122 | queue.WithWorker(w), 123 | ) 124 | 125 | m.AddRunningJob(func(ctx context.Context) error { 126 | for { 127 | select { 128 | case <-ctx.Done(): 129 | select { 130 | case m := <-rets: 131 | fmt.Println("message:", m) 132 | default: 133 | } 134 | return nil 135 | case m := <-rets: 136 | fmt.Println("message:", m) 137 | time.Sleep(50 * time.Millisecond) 138 | } 139 | } 140 | }) 141 | 142 | m.AddShutdownJob(func() error { 143 | // shutdown the service and notify all the worker 144 | q.Release() 145 | return nil 146 | }) 147 | 148 | <-m.Done() 149 | } 150 | ``` 151 | 152 | This example demonstrates how to set up a producer-consumer system using the `Producer` and `Consumer` classes. The producer creates tasks and adds them to the queue, while the consumer processes the tasks from the queue. 153 | -------------------------------------------------------------------------------- /_example/producer-consumer/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/appleboy/graceful" 10 | "github.com/golang-queue/queue" 11 | "github.com/golang-queue/queue/core" 12 | "github.com/golang-queue/redisdb-stream" 13 | ) 14 | 15 | type job struct { 16 | Message string 17 | } 18 | 19 | func (j *job) Bytes() []byte { 20 | b, err := json.Marshal(j) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return b 25 | } 26 | 27 | func main() { 28 | taskN := 10000 29 | rets := make(chan string, taskN) 30 | 31 | m := graceful.NewManager() 32 | 33 | // define the worker 34 | w := redisdb.NewWorker( 35 | redisdb.WithAddr("127.0.0.1:6379"), 36 | redisdb.WithStreamName("foobar"), 37 | redisdb.WithGroup("foobar"), 38 | redisdb.WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 39 | var v job 40 | if err := json.Unmarshal(m.Payload(), &v); err != nil { 41 | return err 42 | } 43 | rets <- v.Message 44 | time.Sleep(2 * time.Second) 45 | return nil 46 | }), 47 | ) 48 | 49 | // define the queue 50 | q := queue.NewPool( 51 | 1, 52 | queue.WithWorker(w), 53 | ) 54 | 55 | m.AddRunningJob(func(ctx context.Context) error { 56 | for { 57 | select { 58 | case <-ctx.Done(): 59 | select { 60 | case m := <-rets: 61 | fmt.Println("message:", m) 62 | default: 63 | } 64 | return nil 65 | case m := <-rets: 66 | fmt.Println("message:", m) 67 | time.Sleep(50 * time.Millisecond) 68 | } 69 | } 70 | }) 71 | 72 | m.AddShutdownJob(func() error { 73 | // shutdown the service and notify all the worker 74 | q.Release() 75 | return nil 76 | }) 77 | 78 | <-m.Done() 79 | } 80 | -------------------------------------------------------------------------------- /_example/producer-consumer/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/appleboy/graceful v1.1.1 7 | github.com/golang-queue/queue v0.3.0 8 | github.com/golang-queue/redisdb-stream v0.0.0-20220424021550-bac6de373624 9 | ) 10 | 11 | require ( 12 | github.com/appleboy/com v0.2.1 // indirect 13 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 14 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 15 | github.com/jpillora/backoff v1.0.0 // indirect 16 | github.com/redis/go-redis/v9 v9.7.0 // indirect 17 | ) 18 | 19 | replace github.com/golang-queue/redisdb-stream => ../../ 20 | -------------------------------------------------------------------------------- /_example/producer-consumer/go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 6 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 7 | github.com/appleboy/com v0.2.1 h1:dHAHauX3eYDuheAahI83HIGFxpi0SEb2ZAu9EZ9hbUM= 8 | github.com/appleboy/com v0.2.1/go.mod h1:kByEI3/vzI5GM1+O5QdBHLsXaOsmFsJcOpCSgASi4sg= 9 | github.com/appleboy/graceful v1.1.1 h1:vYqfpBdyEFztXofmw9iL8aohnxbcE+2Nq2j67SCoMBo= 10 | github.com/appleboy/graceful v1.1.1/go.mod h1:gjCrWFsKhOX2N6jNY0QyQhK+WE8Z3TFSen4fExHg3wk= 11 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 12 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 13 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 14 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 15 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 16 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 17 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 18 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 19 | github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= 20 | github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 21 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 22 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 23 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 24 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 25 | github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 26 | github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 27 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 28 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 30 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 31 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 32 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 33 | github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= 34 | github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 35 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 36 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 37 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 38 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 39 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 40 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 41 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 42 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 43 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 44 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 45 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 46 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 47 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 48 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 49 | github.com/golang-queue/queue v0.3.0 h1:gyBLNT9EDOsChazYScp8iLiwLfG0SdnCDmNUybcHig4= 50 | github.com/golang-queue/queue v0.3.0/go.mod h1:SkjMwz1TjxZOrF7kABvbar1CagcMxwRtXt5Tx00wb4g= 51 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 52 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 53 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 54 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 55 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 56 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 57 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 58 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 59 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 60 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 61 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 62 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 63 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 64 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 65 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= 66 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= 67 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= 68 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= 69 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 70 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 71 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 72 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 73 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 74 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 75 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 76 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 77 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 78 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 79 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 80 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 81 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 82 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 83 | github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= 84 | github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= 85 | github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= 86 | github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 87 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 88 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 89 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 90 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 91 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 92 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 93 | github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= 94 | github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= 95 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 96 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 97 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 98 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 99 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 100 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 101 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= 102 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 103 | go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= 104 | go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 105 | go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= 106 | go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 107 | go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= 108 | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 109 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 110 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 111 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 112 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 113 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 114 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 115 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 116 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 117 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 118 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 119 | -------------------------------------------------------------------------------- /_example/producer-consumer/producer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "time" 8 | 9 | "github.com/golang-queue/queue" 10 | "github.com/golang-queue/redisdb-stream" 11 | ) 12 | 13 | type job struct { 14 | Message string 15 | } 16 | 17 | func (j *job) Bytes() []byte { 18 | b, err := json.Marshal(j) 19 | if err != nil { 20 | panic(err) 21 | } 22 | return b 23 | } 24 | 25 | func main() { 26 | taskN := 5 27 | 28 | // define the worker 29 | w := redisdb.NewWorker( 30 | redisdb.WithAddr("127.0.0.1:6379"), 31 | redisdb.WithStreamName("foobar"), 32 | ) 33 | 34 | // define the queue 35 | q := queue.NewPool( 36 | 0, 37 | queue.WithWorker(w), 38 | ) 39 | 40 | // assign tasks in queue 41 | for i := 0; i < taskN; i++ { 42 | go func(i int) { 43 | if err := q.Queue(&job{ 44 | Message: fmt.Sprintf("handle the job: %d", i+1), 45 | }); err != nil { 46 | log.Fatal(err) 47 | } 48 | }(i) 49 | } 50 | 51 | time.Sleep(2 * time.Second) 52 | // shutdown the service and notify all the worker 53 | q.Release() 54 | } 55 | -------------------------------------------------------------------------------- /_example/worker/go.mod: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/golang-queue/queue v0.3.0 7 | github.com/golang-queue/redisdb-stream v0.0.0-20220424021550-bac6de373624 8 | ) 9 | 10 | require ( 11 | github.com/appleboy/com v0.2.1 // indirect 12 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 13 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 14 | github.com/jpillora/backoff v1.0.0 // indirect 15 | github.com/redis/go-redis/v9 v9.7.0 // indirect 16 | ) 17 | 18 | replace github.com/golang-queue/redisdb-stream => ../../ 19 | -------------------------------------------------------------------------------- /_example/worker/go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 4 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 5 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 6 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 7 | github.com/appleboy/com v0.2.1 h1:dHAHauX3eYDuheAahI83HIGFxpi0SEb2ZAu9EZ9hbUM= 8 | github.com/appleboy/com v0.2.1/go.mod h1:kByEI3/vzI5GM1+O5QdBHLsXaOsmFsJcOpCSgASi4sg= 9 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 10 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 11 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 12 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 13 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 14 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 15 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 16 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 17 | github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= 18 | github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 19 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 20 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 21 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 22 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 23 | github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 24 | github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 25 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 26 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 27 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 28 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 29 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 30 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 31 | github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= 32 | github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 33 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 34 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 35 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 36 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 37 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 38 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 39 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 40 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 41 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 42 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 43 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 44 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 45 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 46 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 47 | github.com/golang-queue/queue v0.3.0 h1:gyBLNT9EDOsChazYScp8iLiwLfG0SdnCDmNUybcHig4= 48 | github.com/golang-queue/queue v0.3.0/go.mod h1:SkjMwz1TjxZOrF7kABvbar1CagcMxwRtXt5Tx00wb4g= 49 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 50 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 51 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 52 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 53 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 54 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 55 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 56 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 57 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 58 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 59 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 60 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 61 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 62 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 63 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= 64 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= 65 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= 66 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= 67 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 68 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 69 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 70 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 71 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 72 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 73 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 74 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 75 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 76 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 77 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 78 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 79 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 80 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 81 | github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= 82 | github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= 83 | github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= 84 | github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 85 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 86 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 87 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 88 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 89 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 90 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 91 | github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= 92 | github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= 93 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 94 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 95 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 96 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 97 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 98 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 99 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= 100 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 101 | go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= 102 | go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 103 | go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= 104 | go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 105 | go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= 106 | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 107 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 108 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 109 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 110 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 111 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 112 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 113 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 114 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 115 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 116 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 117 | -------------------------------------------------------------------------------- /_example/worker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "github.com/golang-queue/queue" 11 | "github.com/golang-queue/queue/core" 12 | redisdb "github.com/golang-queue/redisdb-stream" 13 | ) 14 | 15 | type job struct { 16 | Message string 17 | } 18 | 19 | func (j *job) Bytes() []byte { 20 | b, err := json.Marshal(j) 21 | if err != nil { 22 | panic(err) 23 | } 24 | return b 25 | } 26 | 27 | func main() { 28 | taskN := 100 29 | rets := make(chan string, taskN) 30 | 31 | // define the worker 32 | w := redisdb.NewWorker( 33 | redisdb.WithAddr("127.0.0.1:6379"), 34 | redisdb.WithStreamName("foobar"), 35 | redisdb.WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 36 | var v job 37 | if err := json.Unmarshal(m.Payload(), &v); err != nil { 38 | return err 39 | } 40 | rets <- v.Message 41 | return nil 42 | }), 43 | ) 44 | 45 | // define the queue 46 | q := queue.NewPool( 47 | 5, 48 | queue.WithWorker(w), 49 | ) 50 | 51 | // assign tasks in queue 52 | for i := 0; i < taskN; i++ { 53 | go func(i int) { 54 | if err := q.Queue(&job{ 55 | Message: fmt.Sprintf("handle the job: %d", i+1), 56 | }); err != nil { 57 | log.Fatal(err) 58 | } 59 | }(i) 60 | } 61 | 62 | // wait until all tasks done 63 | for i := 0; i < taskN; i++ { 64 | fmt.Println("message:", <-rets) 65 | time.Sleep(50 * time.Millisecond) 66 | } 67 | 68 | // shutdown the service and notify all the worker 69 | q.Release() 70 | } 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/golang-queue/redisdb-stream 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/appleboy/com v0.3.0 7 | github.com/golang-queue/queue v0.3.0 8 | github.com/redis/go-redis/v9 v9.7.1 9 | github.com/stretchr/testify v1.10.0 10 | github.com/testcontainers/testcontainers-go v0.35.0 11 | go.uber.org/goleak v1.3.0 12 | ) 13 | 14 | require ( 15 | dario.cat/mergo v1.0.0 // indirect 16 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 17 | github.com/Microsoft/go-winio v0.6.2 // indirect 18 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 20 | github.com/containerd/containerd v1.7.18 // indirect 21 | github.com/containerd/log v0.1.0 // indirect 22 | github.com/containerd/platforms v0.2.1 // indirect 23 | github.com/cpuguy83/dockercfg v0.3.2 // indirect 24 | github.com/davecgh/go-spew v1.1.1 // indirect 25 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 26 | github.com/distribution/reference v0.6.0 // indirect 27 | github.com/docker/docker v27.1.1+incompatible // indirect 28 | github.com/docker/go-connections v0.5.0 // indirect 29 | github.com/docker/go-units v0.5.0 // indirect 30 | github.com/felixge/httpsnoop v1.0.4 // indirect 31 | github.com/go-logr/logr v1.4.1 // indirect 32 | github.com/go-logr/stdr v1.2.2 // indirect 33 | github.com/go-ole/go-ole v1.2.6 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/google/uuid v1.6.0 // indirect 36 | github.com/jpillora/backoff v1.0.0 // indirect 37 | github.com/klauspost/compress v1.17.4 // indirect 38 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 39 | github.com/magiconair/properties v1.8.7 // indirect 40 | github.com/moby/docker-image-spec v1.3.1 // indirect 41 | github.com/moby/patternmatcher v0.6.0 // indirect 42 | github.com/moby/sys/sequential v0.5.0 // indirect 43 | github.com/moby/sys/user v0.1.0 // indirect 44 | github.com/moby/term v0.5.0 // indirect 45 | github.com/morikuni/aec v1.0.0 // indirect 46 | github.com/opencontainers/go-digest v1.0.0 // indirect 47 | github.com/opencontainers/image-spec v1.1.0 // indirect 48 | github.com/pkg/errors v0.9.1 // indirect 49 | github.com/pmezard/go-difflib v1.0.0 // indirect 50 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 51 | github.com/shirou/gopsutil/v3 v3.23.12 // indirect 52 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 53 | github.com/sirupsen/logrus v1.9.3 // indirect 54 | github.com/tklauser/go-sysconf v0.3.12 // indirect 55 | github.com/tklauser/numcpus v0.6.1 // indirect 56 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 57 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect 58 | go.opentelemetry.io/otel v1.24.0 // indirect 59 | go.opentelemetry.io/otel/metric v1.24.0 // indirect 60 | go.opentelemetry.io/otel/trace v1.24.0 // indirect 61 | golang.org/x/crypto v0.31.0 // indirect 62 | golang.org/x/sys v0.28.0 // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 2 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= 4 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 5 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 6 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 7 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 8 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 9 | github.com/appleboy/com v0.3.0 h1:omze/tJPyi2YVH+m23GSrCGt90A+4vQNpEYBW+GuSr4= 10 | github.com/appleboy/com v0.3.0/go.mod h1:kByEI3/vzI5GM1+O5QdBHLsXaOsmFsJcOpCSgASi4sg= 11 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 12 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 13 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 14 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 15 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 16 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 17 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 18 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 19 | github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao= 20 | github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4= 21 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 22 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 23 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 24 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 25 | github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 26 | github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 27 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 28 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 33 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 34 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 35 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 36 | github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= 37 | github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 38 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 39 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 40 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 41 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 42 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 43 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 44 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 45 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 46 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 47 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 48 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 49 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 50 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 51 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 52 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 53 | github.com/golang-queue/queue v0.3.0 h1:gyBLNT9EDOsChazYScp8iLiwLfG0SdnCDmNUybcHig4= 54 | github.com/golang-queue/queue v0.3.0/go.mod h1:SkjMwz1TjxZOrF7kABvbar1CagcMxwRtXt5Tx00wb4g= 55 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 57 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 58 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 59 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 60 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 61 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= 62 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= 63 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 64 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 65 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 66 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 67 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 68 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 69 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 70 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 71 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 72 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 73 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 74 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 75 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 76 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 77 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 78 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 79 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 80 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 81 | github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= 82 | github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= 83 | github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= 84 | github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= 85 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 86 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 87 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 88 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 89 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 90 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 91 | github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= 92 | github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 93 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 94 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 95 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 96 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 97 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 98 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 99 | github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc= 100 | github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= 101 | github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= 102 | github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 103 | github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= 104 | github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 105 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 106 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 107 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 108 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 109 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 110 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 111 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 112 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 113 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 114 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 115 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 116 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 118 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 119 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 120 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 121 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 122 | github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= 123 | github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= 124 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 125 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 126 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 127 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 128 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 129 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 130 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 131 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 132 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= 133 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 134 | go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= 135 | go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 136 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= 137 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= 138 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= 139 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= 140 | go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= 141 | go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 142 | go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= 143 | go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= 144 | go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= 145 | go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 146 | go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= 147 | go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 148 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 149 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 150 | go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= 151 | go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 152 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 153 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 154 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 155 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 156 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 157 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 158 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 159 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 160 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 161 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 162 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 163 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 164 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 165 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 166 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 167 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 169 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 172 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 173 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 177 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 178 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 179 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 180 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 181 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 182 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 183 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 184 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 185 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 186 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 187 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 188 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 189 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 190 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 191 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 192 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 193 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 194 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 195 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 196 | google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= 197 | google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1:lK0oleSc7IQsUxO3U5TjL9DWlsxpEBemh+zpB7IqhWI= 198 | google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= 199 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= 200 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= 201 | google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= 202 | google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= 203 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 204 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 205 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 206 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 207 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 208 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 209 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 210 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 211 | gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= 212 | gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= 213 | -------------------------------------------------------------------------------- /images/redis-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/golang-queue/redisdb-stream/ab589828ba58ab98d2f1b1bf357f63a4eed55e48/images/redis-stream.png -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package redisdb 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "time" 7 | 8 | "github.com/golang-queue/queue" 9 | "github.com/golang-queue/queue/core" 10 | ) 11 | 12 | // Option for queue system 13 | type Option func(*options) 14 | 15 | type options struct { 16 | runFunc func(context.Context, core.TaskMessage) error 17 | logger queue.Logger 18 | addr string 19 | db int 20 | connectionString string 21 | username string 22 | password string 23 | streamName string 24 | cluster bool 25 | group string 26 | consumer string 27 | maxLength int64 28 | blockTime time.Duration 29 | tls *tls.Config 30 | } 31 | 32 | // WithAddr setup the addr of redis 33 | func WithAddr(addr string) Option { 34 | return func(w *options) { 35 | w.addr = addr 36 | } 37 | } 38 | 39 | // WithMaxLength setup the max length for publish messages 40 | func WithMaxLength(m int64) Option { 41 | return func(w *options) { 42 | w.maxLength = m 43 | } 44 | } 45 | 46 | // WithBlockTime setup the block time for publish messages 47 | // we use the block command to make sure if no entry is found we wait 48 | // until an entry is found 49 | func WithBlockTime(m time.Duration) Option { 50 | return func(w *options) { 51 | w.blockTime = m 52 | } 53 | } 54 | 55 | // WithPassword redis password 56 | func WithDB(db int) Option { 57 | return func(w *options) { 58 | w.db = db 59 | } 60 | } 61 | 62 | // WithCluster redis cluster 63 | func WithCluster() Option { 64 | return func(w *options) { 65 | w.cluster = true 66 | } 67 | } 68 | 69 | // WithStreamName Stream name 70 | func WithStreamName(name string) Option { 71 | return func(w *options) { 72 | w.streamName = name 73 | } 74 | } 75 | 76 | // WithGroup group name 77 | func WithGroup(name string) Option { 78 | return func(w *options) { 79 | w.group = name 80 | } 81 | } 82 | 83 | // WithConsumer consumer name 84 | func WithConsumer(name string) Option { 85 | return func(w *options) { 86 | w.consumer = name 87 | } 88 | } 89 | 90 | // WithUsername redis username 91 | // This is only used for redis cluster 92 | func WithUsername(username string) Option { 93 | return func(w *options) { 94 | w.username = username 95 | } 96 | } 97 | 98 | // WithPassword redis password 99 | func WithPassword(passwd string) Option { 100 | return func(w *options) { 101 | w.password = passwd 102 | } 103 | } 104 | 105 | // WithConnectionString redis connection string 106 | func WithConnectionString(connectionString string) Option { 107 | return func(w *options) { 108 | w.connectionString = connectionString 109 | } 110 | } 111 | 112 | // WithRunFunc setup the run func of queue 113 | func WithRunFunc(fn func(context.Context, core.TaskMessage) error) Option { 114 | return func(w *options) { 115 | w.runFunc = fn 116 | } 117 | } 118 | 119 | // WithLogger set custom logger 120 | func WithLogger(l queue.Logger) Option { 121 | return func(w *options) { 122 | w.logger = l 123 | } 124 | } 125 | 126 | // WithTLS returns an Option that configures the use of TLS for the connection. 127 | // It sets the minimum TLS version to TLS 1.2. 128 | func WithTLS() Option { 129 | return func(w *options) { 130 | w.tls = &tls.Config{ 131 | MinVersion: tls.VersionTLS12, 132 | } 133 | } 134 | } 135 | 136 | // WithSkipTLSVerify returns an Option that configures the TLS settings to skip 137 | // verification of the server's certificate. This is useful for connecting to 138 | // servers with self-signed certificates or when certificate verification is 139 | // not required. Use this option with caution as it makes the connection 140 | // susceptible to man-in-the-middle attacks. 141 | func WithSkipTLSVerify() Option { 142 | return func(w *options) { 143 | if w.tls == nil { 144 | w.tls = &tls.Config{ 145 | InsecureSkipVerify: true, //nolint: gosec 146 | 147 | } 148 | return 149 | } 150 | w.tls.InsecureSkipVerify = true 151 | } 152 | } 153 | 154 | func newOptions(opts ...Option) options { 155 | defaultOpts := options{ 156 | streamName: "golang-queue", 157 | group: "golang-queue", 158 | consumer: "golang-queue", 159 | logger: queue.NewLogger(), 160 | runFunc: func(context.Context, core.TaskMessage) error { 161 | return nil 162 | }, 163 | blockTime: 60 * time.Second, 164 | } 165 | 166 | // Loop through each option 167 | for _, opt := range opts { 168 | // Call the option giving the instantiated 169 | opt(&defaultOpts) 170 | } 171 | 172 | return defaultOpts 173 | } 174 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package redisdb 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strings" 7 | "sync" 8 | "sync/atomic" 9 | "time" 10 | 11 | "github.com/golang-queue/queue" 12 | "github.com/golang-queue/queue/core" 13 | "github.com/golang-queue/queue/job" 14 | 15 | "github.com/appleboy/com/bytesconv" 16 | "github.com/redis/go-redis/v9" 17 | ) 18 | 19 | var _ core.Worker = (*Worker)(nil) 20 | 21 | // Worker for Redis 22 | type Worker struct { 23 | // redis config 24 | rdb redis.Cmdable 25 | tasks chan redis.XMessage 26 | stopFlag int32 27 | stopOnce sync.Once 28 | startOnce sync.Once 29 | stop chan struct{} 30 | exit chan struct{} 31 | opts options 32 | } 33 | 34 | // NewWorker for struc 35 | func NewWorker(opts ...Option) *Worker { 36 | var err error 37 | w := &Worker{ 38 | opts: newOptions(opts...), 39 | stop: make(chan struct{}), 40 | exit: make(chan struct{}), 41 | tasks: make(chan redis.XMessage), 42 | } 43 | 44 | if w.opts.connectionString != "" { 45 | options, err := redis.ParseURL(w.opts.connectionString) 46 | if err != nil { 47 | w.opts.logger.Fatal(err) 48 | } 49 | w.rdb = redis.NewClient(options) 50 | } else if w.opts.addr != "" { 51 | if w.opts.cluster { 52 | w.rdb = redis.NewClusterClient(&redis.ClusterOptions{ 53 | Addrs: strings.Split(w.opts.addr, ","), 54 | Username: w.opts.username, 55 | Password: w.opts.password, 56 | TLSConfig: w.opts.tls, 57 | }) 58 | } else { 59 | options := &redis.Options{ 60 | Addr: w.opts.addr, 61 | Username: w.opts.username, 62 | Password: w.opts.password, 63 | DB: w.opts.db, 64 | TLSConfig: w.opts.tls, 65 | } 66 | w.rdb = redis.NewClient(options) 67 | } 68 | } 69 | 70 | _, err = w.rdb.Ping(context.Background()).Result() 71 | if err != nil { 72 | w.opts.logger.Fatal(err) 73 | } 74 | 75 | return w 76 | } 77 | 78 | func (w *Worker) startConsumer() { 79 | w.startOnce.Do(func() { 80 | if err := w.rdb.XGroupCreateMkStream( 81 | context.Background(), 82 | w.opts.streamName, 83 | w.opts.group, 84 | "$", 85 | ).Err(); err != nil { 86 | w.opts.logger.Error(err) 87 | } 88 | 89 | go w.fetchTask() 90 | }) 91 | } 92 | 93 | func (w *Worker) fetchTask() { 94 | for { 95 | select { 96 | case <-w.stop: 97 | return 98 | default: 99 | } 100 | 101 | ctx := context.Background() 102 | data, err := w.rdb.XReadGroup(ctx, &redis.XReadGroupArgs{ 103 | Group: w.opts.group, 104 | Consumer: w.opts.consumer, 105 | Streams: []string{w.opts.streamName, ">"}, 106 | // count is number of entries we want to read from redis 107 | Count: 1, 108 | // we use the block command to make sure if no entry is found we wait 109 | // until an entry is found 110 | Block: w.opts.blockTime, 111 | }).Result() 112 | if err != nil { 113 | w.opts.logger.Errorf("error while reading from redis %v", err) 114 | continue 115 | } 116 | // we have received the data we should loop it and queue the messages 117 | // so that our tasks can start processing 118 | for _, result := range data { 119 | for _, message := range result.Messages { 120 | select { 121 | case w.tasks <- message: 122 | if err := w.rdb.XAck(ctx, w.opts.streamName, w.opts.group, message.ID).Err(); err != nil { 123 | w.opts.logger.Errorf("can't ack message: %s", message.ID) 124 | } 125 | case <-w.stop: 126 | // Todo: re-queue the task 127 | w.opts.logger.Info("re-queue the task: ", message.ID) 128 | if err := w.queue(message.Values); err != nil { 129 | w.opts.logger.Error("error to re-queue the task: ", message.ID) 130 | } 131 | close(w.exit) 132 | return 133 | } 134 | } 135 | } 136 | } 137 | } 138 | 139 | // Shutdown worker 140 | func (w *Worker) Shutdown() error { 141 | if !atomic.CompareAndSwapInt32(&w.stopFlag, 0, 1) { 142 | return queue.ErrQueueShutdown 143 | } 144 | 145 | w.stopOnce.Do(func() { 146 | close(w.stop) 147 | 148 | // wait requeue 149 | select { 150 | case <-w.exit: 151 | case <-time.After(200 * time.Millisecond): 152 | } 153 | 154 | switch v := w.rdb.(type) { 155 | case *redis.Client: 156 | v.Close() 157 | case *redis.ClusterClient: 158 | v.Close() 159 | } 160 | close(w.tasks) 161 | }) 162 | return nil 163 | } 164 | 165 | func (w *Worker) queue(data interface{}) error { 166 | ctx := context.Background() 167 | 168 | // Publish a message. 169 | err := w.rdb.XAdd(ctx, &redis.XAddArgs{ 170 | Stream: w.opts.streamName, 171 | MaxLen: w.opts.maxLength, 172 | Values: data, 173 | }).Err() 174 | 175 | return err 176 | } 177 | 178 | // Queue send notification to queue 179 | func (w *Worker) Queue(task core.TaskMessage) error { 180 | if atomic.LoadInt32(&w.stopFlag) == 1 { 181 | return queue.ErrQueueShutdown 182 | } 183 | 184 | return w.queue(map[string]interface{}{"body": bytesconv.BytesToStr(task.Bytes())}) 185 | } 186 | 187 | // Run start the worker 188 | func (w *Worker) Run(ctx context.Context, task core.TaskMessage) error { 189 | return w.opts.runFunc(ctx, task) 190 | } 191 | 192 | // Request a new task 193 | func (w *Worker) Request() (core.TaskMessage, error) { 194 | clock := 0 195 | w.startConsumer() 196 | loop: 197 | for { 198 | select { 199 | case task, ok := <-w.tasks: 200 | if !ok { 201 | return nil, queue.ErrQueueHasBeenClosed 202 | } 203 | var data job.Message 204 | _ = json.Unmarshal(bytesconv.StrToBytes(task.Values["body"].(string)), &data) 205 | return &data, nil 206 | case <-time.After(1 * time.Second): 207 | if clock == 5 { 208 | break loop 209 | } 210 | clock += 1 211 | } 212 | } 213 | 214 | return nil, queue.ErrNoTaskInQueue 215 | } 216 | -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | package redisdb 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "runtime" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | "github.com/golang-queue/queue" 14 | "github.com/golang-queue/queue/core" 15 | "github.com/golang-queue/queue/job" 16 | 17 | "github.com/stretchr/testify/assert" 18 | "github.com/stretchr/testify/require" 19 | "github.com/testcontainers/testcontainers-go" 20 | "github.com/testcontainers/testcontainers-go/wait" 21 | "go.uber.org/goleak" 22 | ) 23 | 24 | func TestMain(m *testing.M) { 25 | goleak.VerifyTestMain(m) 26 | } 27 | 28 | func setupRedisCluserContainer(ctx context.Context, t *testing.T) (testcontainers.Container, string) { 29 | req := testcontainers.ContainerRequest{ 30 | Image: "vishnunair/docker-redis-cluster:latest", 31 | ExposedPorts: []string{ 32 | "6379/tcp", 33 | "6380/tcp", 34 | "6381/tcp", 35 | "6382/tcp", 36 | "6383/tcp", 37 | "6384/tcp", 38 | }, 39 | WaitingFor: wait.NewExecStrategy( 40 | []string{"redis-cli", "-h", "localhost", "-p", "6379", "cluster", "info"}, 41 | ), 42 | } 43 | redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 44 | ContainerRequest: req, 45 | Started: true, 46 | }) 47 | require.NoError(t, err) 48 | 49 | endpoint, err := redisC.Endpoint(ctx, "") 50 | require.NoError(t, err) 51 | 52 | return redisC, endpoint 53 | } 54 | 55 | func setupRedisContainer(ctx context.Context, t *testing.T) (testcontainers.Container, string) { 56 | req := testcontainers.ContainerRequest{ 57 | Image: "redis:6", 58 | ExposedPorts: []string{"6379/tcp"}, 59 | WaitingFor: wait.NewExecStrategy( 60 | []string{"redis-cli", "-h", "localhost", "-p", "6379", "ping"}, 61 | ), 62 | } 63 | redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 64 | ContainerRequest: req, 65 | Started: true, 66 | }) 67 | require.NoError(t, err) 68 | 69 | endpoint, err := redisC.Endpoint(ctx, "") 70 | require.NoError(t, err) 71 | 72 | return redisC, endpoint 73 | } 74 | 75 | func TestWithRedis(t *testing.T) { 76 | ctx := context.Background() 77 | redisC, _ := setupRedisContainer(ctx, t) 78 | testcontainers.CleanupContainer(t, redisC) 79 | } 80 | 81 | type mockMessage struct { 82 | Message string 83 | } 84 | 85 | func (m mockMessage) Bytes() []byte { 86 | return []byte(m.Message) 87 | } 88 | 89 | func TestRedisDefaultFlow(t *testing.T) { 90 | ctx := context.Background() 91 | redisC, endpoint := setupRedisContainer(ctx, t) 92 | defer testcontainers.CleanupContainer(t, redisC) 93 | 94 | m := &mockMessage{ 95 | Message: "foo", 96 | } 97 | w := NewWorker( 98 | WithAddr(endpoint), 99 | WithStreamName("test"), 100 | ) 101 | q, err := queue.NewQueue( 102 | queue.WithWorker(w), 103 | queue.WithWorkerCount(2), 104 | ) 105 | assert.NoError(t, err) 106 | assert.NoError(t, q.Queue(m)) 107 | q.Start() 108 | time.Sleep(100 * time.Millisecond) 109 | q.Release() 110 | } 111 | 112 | func TestRedisShutdown(t *testing.T) { 113 | ctx := context.Background() 114 | redisC, endpoint := setupRedisContainer(ctx, t) 115 | defer testcontainers.CleanupContainer(t, redisC) 116 | w := NewWorker( 117 | WithAddr(endpoint), 118 | WithStreamName("test2"), 119 | ) 120 | q, err := queue.NewQueue( 121 | queue.WithWorker(w), 122 | queue.WithWorkerCount(2), 123 | ) 124 | assert.NoError(t, err) 125 | q.Start() 126 | time.Sleep(1 * time.Second) 127 | q.Shutdown() 128 | // check shutdown once 129 | assert.Error(t, w.Shutdown()) 130 | assert.Equal(t, queue.ErrQueueShutdown, w.Shutdown()) 131 | q.Wait() 132 | } 133 | 134 | func TestCustomFuncAndWait(t *testing.T) { 135 | ctx := context.Background() 136 | redisC, endpoint := setupRedisContainer(ctx, t) 137 | defer testcontainers.CleanupContainer(t, redisC) 138 | m := &mockMessage{ 139 | Message: "foo", 140 | } 141 | w := NewWorker( 142 | WithAddr(endpoint), 143 | WithStreamName("test3"), 144 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 145 | time.Sleep(500 * time.Millisecond) 146 | return nil 147 | }), 148 | ) 149 | q := queue.NewPool( 150 | 5, 151 | queue.WithWorker(w), 152 | ) 153 | time.Sleep(100 * time.Millisecond) 154 | assert.NoError(t, q.Queue(m)) 155 | assert.NoError(t, q.Queue(m)) 156 | assert.NoError(t, q.Queue(m)) 157 | assert.NoError(t, q.Queue(m)) 158 | time.Sleep(1000 * time.Millisecond) 159 | q.Release() 160 | // you will see the execute time > 1000ms 161 | } 162 | 163 | func TestRedisCluster(t *testing.T) { 164 | t.Skip() 165 | 166 | ctx := context.Background() 167 | redisC, _ := setupRedisCluserContainer(ctx, t) 168 | defer testcontainers.CleanupContainer(t, redisC) 169 | 170 | masterPort, err := redisC.MappedPort(ctx, "6379") 171 | assert.NoError(t, err) 172 | 173 | slavePort, err := redisC.MappedPort(ctx, "6382") 174 | assert.NoError(t, err) 175 | 176 | hostIP, err := redisC.Host(ctx) 177 | assert.NoError(t, err) 178 | 179 | m := &mockMessage{ 180 | Message: "foo", 181 | } 182 | 183 | masterName := fmt.Sprintf("%s:%s", hostIP, masterPort.Port()) 184 | slaveName := fmt.Sprintf("%s:%s", hostIP, slavePort.Port()) 185 | 186 | hosts := []string{masterName, slaveName} 187 | 188 | w := NewWorker( 189 | WithAddr(strings.Join(hosts, ",")), 190 | WithStreamName("testCluster"), 191 | WithCluster(), 192 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 193 | time.Sleep(500 * time.Millisecond) 194 | return nil 195 | }), 196 | ) 197 | q := queue.NewPool( 198 | 5, 199 | queue.WithWorker(w), 200 | ) 201 | time.Sleep(100 * time.Millisecond) 202 | assert.NoError(t, q.Queue(m)) 203 | assert.NoError(t, q.Queue(m)) 204 | assert.NoError(t, q.Queue(m)) 205 | assert.NoError(t, q.Queue(m)) 206 | time.Sleep(1000 * time.Millisecond) 207 | q.Release() 208 | // you will see the execute time > 1000ms 209 | } 210 | 211 | func TestEnqueueJobAfterShutdown(t *testing.T) { 212 | ctx := context.Background() 213 | redisC, endpoint := setupRedisContainer(ctx, t) 214 | defer testcontainers.CleanupContainer(t, redisC) 215 | m := mockMessage{ 216 | Message: "foo", 217 | } 218 | w := NewWorker( 219 | WithAddr(endpoint), 220 | ) 221 | q, err := queue.NewQueue( 222 | queue.WithWorker(w), 223 | queue.WithWorkerCount(2), 224 | ) 225 | assert.NoError(t, err) 226 | q.Start() 227 | time.Sleep(50 * time.Millisecond) 228 | q.Shutdown() 229 | // can't queue task after shutdown 230 | err = q.Queue(m) 231 | assert.Error(t, err) 232 | assert.Equal(t, queue.ErrQueueShutdown, err) 233 | q.Wait() 234 | } 235 | 236 | func TestJobReachTimeout(t *testing.T) { 237 | ctx := context.Background() 238 | redisC, endpoint := setupRedisContainer(ctx, t) 239 | defer testcontainers.CleanupContainer(t, redisC) 240 | m := mockMessage{ 241 | Message: "foo", 242 | } 243 | w := NewWorker( 244 | WithAddr(endpoint), 245 | WithStreamName("timeout"), 246 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 247 | for { 248 | select { 249 | case <-ctx.Done(): 250 | log.Println("get data:", string(m.Payload())) 251 | if errors.Is(ctx.Err(), context.Canceled) { 252 | log.Println("queue has been shutdown and cancel the job") 253 | } else if errors.Is(ctx.Err(), context.DeadlineExceeded) { 254 | log.Println("job deadline exceeded") 255 | } 256 | return nil 257 | default: 258 | } 259 | time.Sleep(50 * time.Millisecond) 260 | } 261 | }), 262 | ) 263 | q, err := queue.NewQueue( 264 | queue.WithWorker(w), 265 | queue.WithWorkerCount(2), 266 | ) 267 | assert.NoError(t, err) 268 | q.Start() 269 | time.Sleep(50 * time.Millisecond) 270 | assert.NoError(t, q.Queue(m, job.AllowOption{ 271 | Timeout: job.Time(20 * time.Millisecond), 272 | })) 273 | time.Sleep(2 * time.Second) 274 | q.Shutdown() 275 | q.Wait() 276 | } 277 | 278 | func TestCancelJobAfterShutdown(t *testing.T) { 279 | ctx := context.Background() 280 | redisC, endpoint := setupRedisContainer(ctx, t) 281 | defer testcontainers.CleanupContainer(t, redisC) 282 | m := mockMessage{ 283 | Message: "test", 284 | } 285 | w := NewWorker( 286 | WithAddr(endpoint), 287 | WithStreamName("cancel"), 288 | WithLogger(queue.NewLogger()), 289 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 290 | for { 291 | select { 292 | case <-ctx.Done(): 293 | log.Println("get data:", string(m.Payload())) 294 | if errors.Is(ctx.Err(), context.Canceled) { 295 | log.Println("queue has been shutdown and cancel the job") 296 | } else if errors.Is(ctx.Err(), context.DeadlineExceeded) { 297 | log.Println("job deadline exceeded") 298 | } 299 | return nil 300 | default: 301 | } 302 | time.Sleep(50 * time.Millisecond) 303 | } 304 | }), 305 | ) 306 | q, err := queue.NewQueue( 307 | queue.WithWorker(w), 308 | queue.WithWorkerCount(2), 309 | ) 310 | assert.NoError(t, err) 311 | q.Start() 312 | time.Sleep(50 * time.Millisecond) 313 | assert.NoError(t, q.Queue(m, job.AllowOption{ 314 | Timeout: job.Time(3 * time.Second), 315 | })) 316 | time.Sleep(2 * time.Second) 317 | q.Shutdown() 318 | q.Wait() 319 | } 320 | 321 | func TestGoroutineLeak(t *testing.T) { 322 | ctx := context.Background() 323 | redisC, endpoint := setupRedisContainer(ctx, t) 324 | defer testcontainers.CleanupContainer(t, redisC) 325 | m := mockMessage{ 326 | Message: "foo", 327 | } 328 | w := NewWorker( 329 | WithAddr(endpoint), 330 | WithStreamName("GoroutineLeak"), 331 | WithLogger(queue.NewEmptyLogger()), 332 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 333 | for { 334 | select { 335 | case <-ctx.Done(): 336 | log.Println("get data:", string(m.Payload())) 337 | if errors.Is(ctx.Err(), context.Canceled) { 338 | log.Println("queue has been shutdown and cancel the job") 339 | } else if errors.Is(ctx.Err(), context.DeadlineExceeded) { 340 | log.Println("job deadline exceeded") 341 | } 342 | return nil 343 | default: 344 | log.Println("get data:", string(m.Payload())) 345 | time.Sleep(50 * time.Millisecond) 346 | return nil 347 | } 348 | } 349 | }), 350 | ) 351 | q, err := queue.NewQueue( 352 | queue.WithLogger(queue.NewEmptyLogger()), 353 | queue.WithWorker(w), 354 | queue.WithWorkerCount(10), 355 | ) 356 | assert.NoError(t, err) 357 | q.Start() 358 | time.Sleep(50 * time.Millisecond) 359 | for i := 0; i < 50; i++ { 360 | m.Message = fmt.Sprintf("foobar: %d", i+1) 361 | assert.NoError(t, q.Queue(m)) 362 | } 363 | time.Sleep(1 * time.Second) 364 | q.Release() 365 | time.Sleep(1 * time.Second) 366 | fmt.Println("number of goroutines:", runtime.NumGoroutine()) 367 | } 368 | 369 | func TestGoroutinePanic(t *testing.T) { 370 | ctx := context.Background() 371 | redisC, endpoint := setupRedisContainer(ctx, t) 372 | defer testcontainers.CleanupContainer(t, redisC) 373 | m := mockMessage{ 374 | Message: "foo", 375 | } 376 | w := NewWorker( 377 | WithAddr(endpoint), 378 | WithStreamName("GoroutinePanic"), 379 | WithRunFunc(func(ctx context.Context, m core.TaskMessage) error { 380 | panic("missing something") 381 | }), 382 | ) 383 | q, err := queue.NewQueue( 384 | queue.WithWorker(w), 385 | queue.WithWorkerCount(2), 386 | ) 387 | assert.NoError(t, err) 388 | q.Start() 389 | time.Sleep(50 * time.Millisecond) 390 | assert.NoError(t, q.Queue(m)) 391 | assert.NoError(t, q.Queue(m)) 392 | time.Sleep(200 * time.Millisecond) 393 | q.Shutdown() 394 | assert.Error(t, q.Queue(m)) 395 | q.Wait() 396 | } 397 | --------------------------------------------------------------------------------