├── CODEOWNERS ├── .github ├── dependabot.yaml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── go.yml │ └── codeql-analysis.yml ├── diodes_suite_test.go ├── .gitignore ├── go.mod ├── NOTICE ├── .golangci.yml ├── waiter.go ├── poller_test.go ├── poller.go ├── waiter_test.go ├── many_to_one_test.go ├── one_to_one_test.go ├── one_to_one.go ├── many_to_one.go ├── README.md ├── benchmarks_test.go ├── go.sum └── LICENSE /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cloudfoundry/wg-app-runtime-platform-logging-and-metrics-approvers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /diodes_suite_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo/v2" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestDiodes(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Diodes Suite") 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Builds 2 | bin 3 | 4 | # Vendored dependencies 5 | vendor 6 | 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # IntelliJ 15 | .idea/ 16 | 17 | # macOS 18 | .DS_Store 19 | 20 | # Vim files 21 | [._]*.s[a-v][a-z] 22 | !*.svg # comment out if you don't need vector files 23 | [._]*.sw[a-p] 24 | [._]s[a-rt-v][a-z] 25 | [._]ss[a-gi-z] 26 | [._]sw[a-p] 27 | Session.vim 28 | Sessionx.vim 29 | .netrwhist 30 | *~ 31 | tags 32 | [._]*.un~ 33 | 34 | # Test binary, built with `go test -c` 35 | *.test 36 | 37 | # Output of the go coverage tool, specifically when used with LiteIDE 38 | *.out 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change. 4 | 5 | ## Type of change 6 | 7 | - [ ] Bug fix (non-breaking change which fixes an issue) 8 | - [ ] New feature (non-breaking change which adds functionality) 9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 10 | - [ ] This change requires a documentation update 11 | 12 | ## Testing performed? 13 | 14 | - [ ] Unit tests 15 | - [ ] Integration tests 16 | - [ ] Acceptance tests 17 | 18 | ## Checklist: 19 | 20 | - [ ] This PR is being made against the `main` branch, or relevant version branch 21 | - [ ] I have made corresponding changes to the documentation 22 | - [ ] I have added testing for my changes 23 | 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module code.cloudfoundry.org/go-diodes 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/onsi/ginkgo/v2 v2.27.3 7 | github.com/onsi/gomega v1.38.3 8 | ) 9 | 10 | require ( 11 | github.com/Masterminds/semver/v3 v3.4.0 // indirect 12 | github.com/go-logr/logr v1.4.3 // indirect 13 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 14 | github.com/google/go-cmp v0.7.0 // indirect 15 | github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect 16 | go.yaml.in/yaml/v3 v3.0.4 // indirect 17 | golang.org/x/mod v0.27.0 // indirect 18 | golang.org/x/net v0.43.0 // indirect 19 | golang.org/x/sync v0.16.0 // indirect 20 | golang.org/x/sys v0.35.0 // indirect 21 | golang.org/x/text v0.28.0 // indirect 22 | golang.org/x/tools v0.36.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | This project may include a number of subcomponents with separate 16 | copyright notices and license terms. Your use of these subcomponents 17 | is subject to the terms and conditions of each subcomponent's license, 18 | as noted in the LICENSE file. 19 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | # Checks for non-ASCII identifiers. 5 | - asciicheck 6 | # Computes and checks the cyclomatic complexity of functions. 7 | - gocyclo 8 | # Inspects source code for security problems. 9 | - gosec 10 | settings: 11 | gocyclo: 12 | # Minimal code complexity to report. 13 | # Default: 30 (but 10-20 recommended). 14 | min-complexity: 20 15 | exclusions: 16 | generated: lax 17 | presets: 18 | - comments 19 | - common-false-positives 20 | - legacy 21 | - std-error-handling 22 | paths: 23 | - third_party$ 24 | - builtin$ 25 | - examples$ 26 | issues: 27 | # Disable max issues per linter. 28 | max-issues-per-linter: 0 29 | # Disable max same issues. 30 | max-same-issues: 0 31 | formatters: 32 | exclusions: 33 | generated: lax 34 | paths: 35 | - third_party$ 36 | - builtin$ 37 | - examples$ 38 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | schedule: 9 | - cron: '00 16 * * 1' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v6 16 | - uses: actions/setup-go@v6 17 | with: 18 | go-version-file: 'go.mod' 19 | check-latest: true 20 | - run: go run github.com/onsi/ginkgo/v2/ginkgo -r --procs=2 --compilers=2 --randomize-all --randomize-suites --fail-on-pending --keep-going --race --trace 21 | 22 | vet: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v6 26 | - uses: actions/setup-go@v6 27 | with: 28 | go-version-file: 'go.mod' 29 | check-latest: true 30 | - run: go vet ./... 31 | 32 | lint: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v6 36 | - uses: actions/setup-go@v6 37 | with: 38 | go-version-file: 'go.mod' 39 | check-latest: true 40 | - uses: golangci/golangci-lint-action@v9.2.0 41 | with: 42 | args: --config .golangci.yml 43 | -------------------------------------------------------------------------------- /waiter.go: -------------------------------------------------------------------------------- 1 | package diodes 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Waiter will use a channel signal to alert the reader to when data is 8 | // available. 9 | type Waiter struct { 10 | Diode 11 | c chan struct{} 12 | ctx context.Context 13 | } 14 | 15 | // WaiterConfigOption can be used to setup the waiter. 16 | type WaiterConfigOption func(*Waiter) 17 | 18 | // WithWaiterContext sets the context to cancel any retrieval (Next()). It 19 | // will not change any results for adding data (Set()). Default is 20 | // context.Background(). 21 | func WithWaiterContext(ctx context.Context) WaiterConfigOption { 22 | return WaiterConfigOption(func(c *Waiter) { 23 | c.ctx = ctx 24 | }) 25 | } 26 | 27 | // NewWaiter returns a new Waiter that wraps the given diode. 28 | func NewWaiter(d Diode, opts ...WaiterConfigOption) *Waiter { 29 | w := new(Waiter) 30 | w.Diode = d 31 | w.c = make(chan struct{}, 1) 32 | w.ctx = context.Background() 33 | 34 | for _, opt := range opts { 35 | opt(w) 36 | } 37 | 38 | return w 39 | } 40 | 41 | // Set invokes the wrapped diode's Set with the given data and uses broadcast 42 | // to wake up any readers. 43 | func (w *Waiter) Set(data GenericDataType) { 44 | w.Diode.Set(data) 45 | w.broadcast() 46 | } 47 | 48 | // broadcast sends to the channel if it can. 49 | func (w *Waiter) broadcast() { 50 | select { 51 | case w.c <- struct{}{}: 52 | default: 53 | } 54 | } 55 | 56 | // Next returns the next data point on the wrapped diode. If there is no new 57 | // data, it will wait for Set to be called or the context to be done. If the 58 | // context is done, then nil will be returned. 59 | func (w *Waiter) Next() GenericDataType { 60 | for { 61 | data, ok := w.Diode.TryNext() // nolint:staticcheck 62 | if ok { 63 | return data 64 | } 65 | select { 66 | case <-w.ctx.Done(): 67 | return nil 68 | case <-w.c: 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /poller_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | 8 | "code.cloudfoundry.org/go-diodes" 9 | 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Poller", func() { 15 | var ( 16 | spy *spyDiode 17 | p *diodes.Poller 18 | ) 19 | 20 | BeforeEach(func() { 21 | spy = new(spyDiode) 22 | p = diodes.NewPoller(spy, diodes.WithPollingInterval(time.Millisecond)) 23 | }) 24 | 25 | It("returns the available result", func() { 26 | spy.dataList = [][]byte{[]byte("a"), []byte("b")} 27 | 28 | Expect(*(*[]byte)(p.Next())).To(Equal([]byte("a"))) 29 | Expect(*(*[]byte)(p.Next())).To(Equal([]byte("b"))) 30 | }) 31 | 32 | It("polls the given diode until data is available", func() { 33 | go func() { 34 | time.Sleep(250 * time.Millisecond) 35 | spy.mu.Lock() 36 | defer spy.mu.Unlock() 37 | spy.dataList = [][]byte{[]byte("a")} 38 | }() 39 | 40 | Expect(*(*[]byte)(p.Next())).To(Equal([]byte("a"))) 41 | }) 42 | 43 | It("cancels Next() with context", func() { 44 | ctx, cancel := context.WithCancel(context.Background()) 45 | p = diodes.NewPoller(spy, diodes.WithPollingContext(ctx)) 46 | cancel() 47 | done := make(chan struct{}) 48 | go func() { 49 | defer close(done) 50 | p.Next() 51 | }() 52 | 53 | Eventually(done).Should(BeClosed()) 54 | }) 55 | }) 56 | 57 | type spyDiode struct { 58 | diodes.Diode 59 | mu sync.Mutex 60 | dataList [][]byte 61 | called int 62 | } 63 | 64 | func (s *spyDiode) Set(data diodes.GenericDataType) { 65 | s.mu.Lock() 66 | defer s.mu.Unlock() 67 | s.dataList = append(s.dataList, *(*[]byte)(data)) 68 | } 69 | 70 | func (s *spyDiode) TryNext() (diodes.GenericDataType, bool) { 71 | s.mu.Lock() 72 | defer s.mu.Unlock() 73 | 74 | s.called++ 75 | if len(s.dataList) == 0 { 76 | return nil, false 77 | } 78 | 79 | next := s.dataList[0] 80 | s.dataList = s.dataList[1:] 81 | return diodes.GenericDataType(&next), true 82 | } 83 | -------------------------------------------------------------------------------- /poller.go: -------------------------------------------------------------------------------- 1 | package diodes 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Diode is any implementation of a diode. 9 | type Diode interface { 10 | Set(GenericDataType) 11 | TryNext() (GenericDataType, bool) 12 | } 13 | 14 | // Poller will poll a diode until a value is available. 15 | type Poller struct { 16 | Diode 17 | interval time.Duration 18 | ctx context.Context 19 | } 20 | 21 | // PollerConfigOption can be used to setup the poller. 22 | type PollerConfigOption func(*Poller) 23 | 24 | // WithPollingInterval sets the interval at which the diode is queried 25 | // for new data. The default is 10ms. 26 | func WithPollingInterval(interval time.Duration) PollerConfigOption { 27 | return PollerConfigOption(func(c *Poller) { 28 | c.interval = interval 29 | }) 30 | } 31 | 32 | // WithPollingContext sets the context to cancel any retrieval (Next()). It 33 | // will not change any results for adding data (Set()). Default is 34 | // context.Background(). 35 | func WithPollingContext(ctx context.Context) PollerConfigOption { 36 | return PollerConfigOption(func(c *Poller) { 37 | c.ctx = ctx 38 | }) 39 | } 40 | 41 | // NewPoller returns a new Poller that wraps the given diode. 42 | func NewPoller(d Diode, opts ...PollerConfigOption) *Poller { 43 | p := &Poller{ 44 | Diode: d, 45 | interval: 10 * time.Millisecond, 46 | ctx: context.Background(), 47 | } 48 | 49 | for _, o := range opts { 50 | o(p) 51 | } 52 | 53 | return p 54 | } 55 | 56 | // Next polls the diode until data is available or until the context is done. 57 | // If the context is done, then nil will be returned. 58 | func (p *Poller) Next() GenericDataType { 59 | for { 60 | data, ok := p.Diode.TryNext() // nolint:staticcheck 61 | if !ok { 62 | if p.isDone() { 63 | return nil 64 | } 65 | 66 | time.Sleep(p.interval) 67 | continue 68 | } 69 | return data 70 | } 71 | } 72 | 73 | func (p *Poller) isDone() bool { 74 | select { 75 | case <-p.ctx.Done(): 76 | return true 77 | default: 78 | return false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /waiter_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "code.cloudfoundry.org/go-diodes" 8 | 9 | . "github.com/onsi/ginkgo/v2" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | var _ = Describe("Waiter", func() { 14 | var ( 15 | spy *spyDiode 16 | w *diodes.Waiter 17 | ) 18 | 19 | BeforeEach(func() { 20 | spy = &spyDiode{} 21 | w = diodes.NewWaiter(spy) 22 | }) 23 | 24 | Describe("Next", func() { 25 | BeforeEach(func() { 26 | spy.dataList = [][]byte{[]byte("a"), []byte("b")} 27 | }) 28 | 29 | It("returns available data points from the wrapped diode", func() { 30 | Expect(spy.called).To(Equal(0)) 31 | Expect(*(*[]byte)(w.Next())).To(Equal([]byte("a"))) 32 | Expect(spy.called).To(Equal(1)) 33 | Expect(*(*[]byte)(w.Next())).To(Equal([]byte("b"))) 34 | Expect(spy.called).To(Equal(2)) 35 | }) 36 | 37 | Context("when there is no new data", func() { 38 | BeforeEach(func() { 39 | spy.dataList = nil 40 | }) 41 | 42 | It("waits for Set to be called", func() { 43 | go func() { 44 | time.Sleep(250 * time.Millisecond) 45 | data := []byte("c") 46 | w.Set(diodes.GenericDataType(&data)) 47 | }() 48 | Expect(spy.called).To(Equal(0)) 49 | Expect(*(*[]byte)(w.Next())).To(Equal([]byte("c"))) 50 | Expect(spy.called).To(Equal(2)) // Calls TryNext twice during wait loop 51 | }) 52 | 53 | Context("when the context is cancelled", func() { 54 | var cancel context.CancelFunc 55 | 56 | BeforeEach(func() { 57 | var ctx context.Context 58 | ctx, cancel = context.WithCancel(context.Background()) 59 | w = diodes.NewWaiter(spy, diodes.WithWaiterContext(ctx)) 60 | }) 61 | 62 | Context("beforehand", func() { 63 | It("returns nil", func() { 64 | cancel() 65 | Expect(spy.called).To(Equal(0)) 66 | Expect(w.Next() == nil).To(BeTrue()) 67 | Expect(spy.called).To(Equal(1)) 68 | }) 69 | }) 70 | 71 | Context("while waiting", func() { 72 | It("returns nil", func() { 73 | go func() { 74 | time.Sleep(250 * time.Millisecond) 75 | cancel() 76 | }() 77 | Expect(spy.called).To(Equal(0)) 78 | Expect(w.Next() == nil).To(BeTrue()) 79 | Expect(spy.called).To(Equal(1)) 80 | }) 81 | }) 82 | }) 83 | }) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 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 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '33 6 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v6 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v4 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v4 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /many_to_one_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | "code.cloudfoundry.org/go-diodes" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("ManyToOne", func() { 11 | var ( 12 | d *diodes.ManyToOne 13 | data []byte 14 | 15 | spy *spyAlerter 16 | ) 17 | 18 | BeforeEach(func() { 19 | spy = newSpyAlerter() 20 | 21 | d = diodes.NewManyToOne(5, spy) 22 | 23 | data = []byte("some-data") 24 | d.Set(diodes.GenericDataType(&data)) 25 | }) 26 | 27 | Context("multiple data slices", func() { 28 | var ( 29 | secondData []byte 30 | ) 31 | 32 | BeforeEach(func() { 33 | secondData = []byte("some-other-data") 34 | d.Set(diodes.GenericDataType(&secondData)) 35 | }) 36 | 37 | Describe("TryNext()", func() { 38 | It("returns true", func() { 39 | _, ok := d.TryNext() 40 | 41 | Expect(ok).To(BeTrue()) 42 | }) 43 | 44 | Context("reads exceed writes", func() { 45 | BeforeEach(func() { 46 | d.TryNext() 47 | d.TryNext() 48 | }) 49 | 50 | It("returns false", func() { 51 | _, ok := d.TryNext() 52 | 53 | Expect(ok).To(BeFalse()) 54 | }) 55 | }) 56 | }) 57 | 58 | Context("buffer size exceeded", func() { 59 | BeforeEach(func() { 60 | for i := 0; i < 4; i++ { 61 | d.Set(diodes.GenericDataType(&secondData)) 62 | } 63 | }) 64 | 65 | It("wraps", func() { 66 | data, _ := d.TryNext() 67 | Expect(*(*[]byte)(data)).To(Equal(secondData)) 68 | }) 69 | 70 | It("alerts for each dropped point", func() { 71 | d.TryNext() 72 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 73 | }) 74 | 75 | It("it updates the read index", func() { 76 | d.TryNext() 77 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 78 | 79 | for i := 0; i < 6; i++ { 80 | j := i 81 | d.Set(diodes.GenericDataType(&j)) 82 | } 83 | 84 | data, _ := d.TryNext() 85 | Expect(*(*int)(data)).To(Equal(5)) 86 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 87 | }) 88 | 89 | Context("read catches up with write", func() { 90 | BeforeEach(func() { 91 | d.TryNext() 92 | <-spy.AlertInput.Missed 93 | }) 94 | 95 | It("does not alert", func() { 96 | d.TryNext() 97 | Expect(spy.AlertInput.Missed).To(Not(Receive())) 98 | }) 99 | }) 100 | 101 | Context("writer laps reader", func() { 102 | BeforeEach(func() { 103 | for i := 0; i < 5; i++ { 104 | d.Set(diodes.GenericDataType(&secondData)) 105 | } 106 | d.TryNext() 107 | }) 108 | 109 | It("sends an alert for each set", func() { 110 | Expect(spy.AlertInput.Missed).To(Receive(Equal(10))) 111 | }) 112 | }) 113 | 114 | Context("writer laps reader with nil alerter", func() { 115 | It("drops the alert", func() { 116 | d = diodes.NewManyToOne(5, nil) 117 | for i := 0; i < 10; i++ { 118 | d.Set(diodes.GenericDataType(&secondData)) 119 | } 120 | 121 | Expect(func() { 122 | d.TryNext() 123 | }).ToNot(Panic()) 124 | }) 125 | }) 126 | }) 127 | }) 128 | }) 129 | 130 | var _ = Describe("reader ahead of writer", func() { 131 | It("must not occur after alerting", func() { 132 | length := 4 133 | spy := newSpyAlerter() 134 | d := diodes.NewManyToOne(length, spy) 135 | data := []byte("some-data") 136 | genData := diodes.GenericDataType(&data) 137 | 138 | By("filling up the buffer") 139 | for i := 0; i < length; i++ { 140 | d.Set(genData) 141 | } 142 | 143 | By("overwriting the first index") 144 | d.Set(genData) 145 | 146 | By("having reader fast forward") 147 | Expect(spy.AlertInput.Missed).To(BeEmpty()) 148 | _, ok := d.TryNext() 149 | Expect(ok).To(BeTrue()) 150 | Expect(spy.AlertInput.Missed).To(Receive(Equal(4))) 151 | 152 | By("failing reads until the writer writes over skipped values") 153 | _, ok = d.TryNext() 154 | Expect(ok).To(BeFalse()) 155 | d.Set(genData) 156 | _, ok = d.TryNext() 157 | Expect(ok).To(BeTrue()) 158 | }) 159 | }) 160 | -------------------------------------------------------------------------------- /one_to_one_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | "code.cloudfoundry.org/go-diodes" 5 | 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | var _ = Describe("OneToOne", func() { 11 | var ( 12 | d *diodes.OneToOne 13 | data []byte 14 | 15 | spy *spyAlerter 16 | ) 17 | 18 | BeforeEach(func() { 19 | spy = newSpyAlerter() 20 | 21 | d = diodes.NewOneToOne(5, spy) 22 | 23 | data = []byte("some-data") 24 | d.Set(diodes.GenericDataType(&data)) 25 | }) 26 | 27 | Context("multiple data slices", func() { 28 | var ( 29 | secondData []byte 30 | ) 31 | 32 | BeforeEach(func() { 33 | secondData = []byte("some-other-data") 34 | d.Set(diodes.GenericDataType(&secondData)) 35 | }) 36 | 37 | Describe("TryNext()", func() { 38 | It("returns true", func() { 39 | _, ok := d.TryNext() 40 | 41 | Expect(ok).To(BeTrue()) 42 | }) 43 | 44 | Context("reads exceed writes", func() { 45 | BeforeEach(func() { 46 | d.TryNext() 47 | d.TryNext() 48 | }) 49 | 50 | It("returns false", func() { 51 | _, ok := d.TryNext() 52 | 53 | Expect(ok).To(BeFalse()) 54 | }) 55 | }) 56 | }) 57 | 58 | Context("buffer size exceeded", func() { 59 | BeforeEach(func() { 60 | for i := 0; i < 4; i++ { 61 | d.Set(diodes.GenericDataType(&secondData)) 62 | } 63 | }) 64 | 65 | It("wraps", func() { 66 | data, _ := d.TryNext() 67 | Expect(*(*[]byte)(data)).To(Equal(secondData)) 68 | }) 69 | 70 | It("alerts for each dropped point", func() { 71 | d.TryNext() 72 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 73 | }) 74 | 75 | It("it updates the read index", func() { 76 | d.TryNext() 77 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 78 | 79 | for i := 0; i < 6; i++ { 80 | d.Set(diodes.GenericDataType(&secondData)) 81 | } 82 | 83 | d.TryNext() 84 | Expect(spy.AlertInput.Missed).To(Receive(Equal(5))) 85 | }) 86 | 87 | Context("read catches up with write", func() { 88 | BeforeEach(func() { 89 | d.TryNext() 90 | <-spy.AlertInput.Missed 91 | }) 92 | 93 | It("does not alert", func() { 94 | d.TryNext() 95 | Expect(spy.AlertInput.Missed).To(Not(Receive())) 96 | }) 97 | }) 98 | 99 | Context("writer laps reader", func() { 100 | BeforeEach(func() { 101 | for i := 0; i < 5; i++ { 102 | d.Set(diodes.GenericDataType(&secondData)) 103 | } 104 | d.TryNext() 105 | }) 106 | 107 | It("sends an alert for each set", func() { 108 | Expect(spy.AlertInput.Missed).To(Receive(Equal(10))) 109 | }) 110 | }) 111 | 112 | Context("writer laps reader with nil alerter", func() { 113 | It("drops the alert", func() { 114 | d = diodes.NewOneToOne(5, nil) 115 | for i := 0; i < 10; i++ { 116 | d.Set(diodes.GenericDataType(&secondData)) 117 | } 118 | 119 | Expect(func() { 120 | d.TryNext() 121 | }).ToNot(Panic()) 122 | }) 123 | }) 124 | }) 125 | }) 126 | 127 | }) 128 | 129 | var _ = Describe("reader ahead of writer", func() { 130 | It("must not occur after alerting", func() { 131 | length := 4 132 | spy := newSpyAlerter() 133 | d := diodes.NewOneToOne(length, spy) 134 | data := []byte("some-data") 135 | genData := diodes.GenericDataType(&data) 136 | 137 | By("filling up the buffer") 138 | for i := 0; i < length; i++ { 139 | d.Set(genData) 140 | } 141 | 142 | By("overwriting the first index") 143 | d.Set(genData) 144 | 145 | By("having reader fast forward") 146 | Expect(spy.AlertInput.Missed).To(BeEmpty()) 147 | _, ok := d.TryNext() 148 | Expect(ok).To(BeTrue()) 149 | Expect(spy.AlertInput.Missed).To(Receive(Equal(4))) 150 | 151 | By("failing reads until the writer writes over skipped values") 152 | _, ok = d.TryNext() 153 | Expect(ok).To(BeFalse()) 154 | d.Set(genData) 155 | _, ok = d.TryNext() 156 | Expect(ok).To(BeTrue()) 157 | }) 158 | }) 159 | 160 | type spyAlerter struct { 161 | AlertCalled chan bool 162 | AlertInput struct { 163 | Missed chan int 164 | } 165 | } 166 | 167 | func newSpyAlerter() *spyAlerter { 168 | m := &spyAlerter{} 169 | m.AlertCalled = make(chan bool, 100) 170 | m.AlertInput.Missed = make(chan int, 100) 171 | return m 172 | } 173 | func (m *spyAlerter) Alert(missed int) { 174 | m.AlertCalled <- true 175 | m.AlertInput.Missed <- missed 176 | } 177 | -------------------------------------------------------------------------------- /one_to_one.go: -------------------------------------------------------------------------------- 1 | package diodes 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | // GenericDataType is the data type the diodes operate on. 9 | type GenericDataType unsafe.Pointer 10 | 11 | // Alerter is used to report how many values were overwritten since the 12 | // last write. 13 | type Alerter interface { 14 | Alert(missed int) 15 | } 16 | 17 | // AlertFunc type is an adapter to allow the use of ordinary functions as 18 | // Alert handlers. 19 | type AlertFunc func(missed int) 20 | 21 | // Alert calls f(missed) 22 | func (f AlertFunc) Alert(missed int) { 23 | f(missed) 24 | } 25 | 26 | type bucket struct { 27 | data GenericDataType 28 | seq uint64 // seq is the recorded write index at the time of writing 29 | } 30 | 31 | // OneToOne diode is meant to be used by a single reader and a single writer. 32 | // It is not thread safe if used otherwise. 33 | type OneToOne struct { 34 | buffer []unsafe.Pointer 35 | writeIndex uint64 36 | readIndex uint64 37 | alerter Alerter 38 | } 39 | 40 | // NewOneToOne creates a new diode is meant to be used by a single reader and 41 | // a single writer. The alerter is invoked on the read's go-routine. It is 42 | // called when it notices that the writer go-routine has passed it and wrote 43 | // over data. A nil can be used to ignore alerts. 44 | func NewOneToOne(size int, alerter Alerter) *OneToOne { 45 | if alerter == nil { 46 | alerter = AlertFunc(func(int) {}) 47 | } 48 | 49 | return &OneToOne{ 50 | buffer: make([]unsafe.Pointer, size), 51 | alerter: alerter, 52 | } 53 | } 54 | 55 | // Set sets the data in the next slot of the ring buffer. 56 | func (d *OneToOne) Set(data GenericDataType) { 57 | idx := d.writeIndex % uint64(len(d.buffer)) 58 | 59 | newBucket := &bucket{ 60 | data: data, 61 | seq: d.writeIndex, 62 | } 63 | d.writeIndex++ 64 | 65 | atomic.StorePointer(&d.buffer[idx], unsafe.Pointer(newBucket)) 66 | } 67 | 68 | // TryNext will attempt to read from the next slot of the ring buffer. 69 | // If there is no data available, it will return (nil, false). 70 | func (d *OneToOne) TryNext() (data GenericDataType, ok bool) { 71 | // Read a value from the ring buffer based on the readIndex. 72 | idx := d.readIndex % uint64(len(d.buffer)) 73 | result := (*bucket)(atomic.SwapPointer(&d.buffer[idx], nil)) 74 | 75 | // When the result is nil that means the writer has not had the 76 | // opportunity to write a value into the diode. This value must be ignored 77 | // and the read head must not increment. 78 | if result == nil { 79 | return nil, false 80 | } 81 | 82 | // When the seq value is less than the current read index that means a 83 | // value was read from idx that was previously written but has since has 84 | // been dropped. This value must be ignored and the read head must not 85 | // increment. 86 | // 87 | // The simulation for this scenario assumes the fast forward occurred as 88 | // detailed below. 89 | // 90 | // 5. The reader reads again getting seq 5. It then reads again expecting 91 | // seq 6 but gets seq 2. This is a read of a stale value that was 92 | // effectively "dropped" so the read fails and the read head stays put. 93 | // `| 4 | 5 | 2 | 3 |` r: 7, w: 6 94 | // 95 | if result.seq < d.readIndex { 96 | return nil, false 97 | } 98 | 99 | // When the seq value is greater than the current read index that means a 100 | // value was read from idx that overwrote the value that was expected to 101 | // be at this idx. This happens when the writer has lapped the reader. The 102 | // reader needs to catch up to the writer so it moves its write head to 103 | // the new seq, effectively dropping the messages that were not read in 104 | // between the two values. 105 | // 106 | // Here is a simulation of this scenario: 107 | // 108 | // 1. Both the read and write heads start at 0. 109 | // `| nil | nil | nil | nil |` r: 0, w: 0 110 | // 2. The writer fills the buffer. 111 | // `| 0 | 1 | 2 | 3 |` r: 0, w: 4 112 | // 3. The writer laps the read head. 113 | // `| 4 | 5 | 2 | 3 |` r: 0, w: 6 114 | // 4. The reader reads the first value, expecting a seq of 0 but reads 4, 115 | // this forces the reader to fast forward to 5. 116 | // `| 4 | 5 | 2 | 3 |` r: 5, w: 6 117 | // 118 | if result.seq > d.readIndex { 119 | dropped := result.seq - d.readIndex 120 | d.readIndex = result.seq 121 | d.alerter.Alert(int(dropped)) // nolint:gosec 122 | } 123 | 124 | // Only increment read index if a regular read occurred (where seq was 125 | // equal to readIndex) or a value was read that caused a fast forward 126 | // (where seq was greater than readIndex). 127 | d.readIndex++ 128 | return result.data, true 129 | } 130 | -------------------------------------------------------------------------------- /many_to_one.go: -------------------------------------------------------------------------------- 1 | package diodes 2 | 3 | import ( 4 | "log" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | // ManyToOne diode is optimal for many writers (go-routines B-n) and a single 10 | // reader (go-routine A). It is not thread safe for multiple readers. 11 | type ManyToOne struct { 12 | writeIndex uint64 13 | buffer []unsafe.Pointer 14 | readIndex uint64 15 | alerter Alerter 16 | } 17 | 18 | // NewManyToOne creates a new diode (ring buffer). The ManyToOne diode 19 | // is optimzed for many writers (on go-routines B-n) and a single reader 20 | // (on go-routine A). The alerter is invoked on the read's go-routine. It is 21 | // called when it notices that the writer go-routine has passed it and wrote 22 | // over data. A nil can be used to ignore alerts. 23 | func NewManyToOne(size int, alerter Alerter) *ManyToOne { 24 | if alerter == nil { 25 | alerter = AlertFunc(func(int) {}) 26 | } 27 | 28 | d := &ManyToOne{ 29 | buffer: make([]unsafe.Pointer, size), 30 | alerter: alerter, 31 | } 32 | 33 | // Start write index at the value before 0 34 | // to allow the first write to use AddUint64 35 | // and still have a beginning index of 0 36 | d.writeIndex = ^d.writeIndex 37 | return d 38 | } 39 | 40 | // Set sets the data in the next slot of the ring buffer. 41 | func (d *ManyToOne) Set(data GenericDataType) { 42 | for { 43 | writeIndex := atomic.AddUint64(&d.writeIndex, 1) 44 | idx := writeIndex % uint64(len(d.buffer)) 45 | old := atomic.LoadPointer(&d.buffer[idx]) 46 | 47 | if old != nil && 48 | (*bucket)(old) != nil && 49 | (*bucket)(old).seq > writeIndex-uint64(len(d.buffer)) { 50 | log.Println("Diode set collision: consider using a larger diode") 51 | continue 52 | } 53 | 54 | newBucket := &bucket{ 55 | data: data, 56 | seq: writeIndex, 57 | } 58 | 59 | if !atomic.CompareAndSwapPointer(&d.buffer[idx], old, unsafe.Pointer(newBucket)) { 60 | log.Println("Diode set collision: consider using a larger diode") 61 | continue 62 | } 63 | 64 | return 65 | } 66 | } 67 | 68 | // TryNext will attempt to read from the next slot of the ring buffer. 69 | // If there is not data available, it will return (nil, false). 70 | func (d *ManyToOne) TryNext() (data GenericDataType, ok bool) { 71 | // Read a value from the ring buffer based on the readIndex. 72 | idx := d.readIndex % uint64(len(d.buffer)) 73 | result := (*bucket)(atomic.SwapPointer(&d.buffer[idx], nil)) 74 | 75 | // When the result is nil that means the writer has not had the 76 | // opportunity to write a value into the diode. This value must be ignored 77 | // and the read head must not increment. 78 | if result == nil { 79 | return nil, false 80 | } 81 | 82 | // When the seq value is less than the current read index that means a 83 | // value was read from idx that was previously written but has since has 84 | // been dropped. This value must be ignored and the read head must not 85 | // increment. 86 | // 87 | // The simulation for this scenario assumes the fast forward occurred as 88 | // detailed below. 89 | // 90 | // 5. The reader reads again getting seq 5. It then reads again expecting 91 | // seq 6 but gets seq 2. This is a read of a stale value that was 92 | // effectively "dropped" so the read fails and the read head stays put. 93 | // `| 4 | 5 | 2 | 3 |` r: 7, w: 6 94 | // 95 | if result.seq < d.readIndex { 96 | return nil, false 97 | } 98 | 99 | // When the seq value is greater than the current read index that means a 100 | // value was read from idx that overwrote the value that was expected to 101 | // be at this idx. This happens when the writer has lapped the reader. The 102 | // reader needs to catch up to the writer so it moves its write head to 103 | // the new seq, effectively dropping the messages that were not read in 104 | // between the two values. 105 | // 106 | // Here is a simulation of this scenario: 107 | // 108 | // 1. Both the read and write heads start at 0. 109 | // `| nil | nil | nil | nil |` r: 0, w: 0 110 | // 2. The writer fills the buffer. 111 | // `| 0 | 1 | 2 | 3 |` r: 0, w: 4 112 | // 3. The writer laps the read head. 113 | // `| 4 | 5 | 2 | 3 |` r: 0, w: 6 114 | // 4. The reader reads the first value, expecting a seq of 0 but reads 4, 115 | // this forces the reader to fast forward to 5. 116 | // `| 4 | 5 | 2 | 3 |` r: 5, w: 6 117 | // 118 | if result.seq > d.readIndex { 119 | dropped := result.seq - d.readIndex 120 | d.readIndex = result.seq 121 | d.alerter.Alert(int(dropped)) // nolint:gosec 122 | } 123 | 124 | // Only increment read index if a regular read occurred (where seq was 125 | // equal to readIndex) or a value was read that caused a fast forward 126 | // (where seq was greater than readIndex). 127 | // 128 | d.readIndex++ 129 | return result.data, true 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![diode][diode-logo] 2 | 3 | [![GoDoc][go-doc-badge]][go-doc] 4 | 5 | If you have any questions, or want to get attention for a PR or issue please reach out on the [#logging-and-metrics channel in the cloudfoundry slack](https://cloudfoundry.slack.com/archives/CUW93AF3M) 6 | 7 | Diodes are ring buffers manipulated via atomics. 8 | 9 | Diodes are optimized for high throughput scenarios where losing data is 10 | acceptable. Unlike a channel, a diode will overwrite data on writes in lieu 11 | of blocking. A diode does its best to not "push back" on the producer. 12 | In other words, invoking `Set()` on a diode never blocks. 13 | 14 | ### Installation 15 | 16 | ```bash 17 | go get code.cloudfoundry.org/go-diodes 18 | ``` 19 | 20 | ### Example: Basic Use 21 | 22 | ```go 23 | d := diodes.NewOneToOne(1024, diodes.AlertFunc(func(missed int) { 24 | log.Printf("Dropped %d messages", missed) 25 | })) 26 | 27 | // writer 28 | go func() { 29 | for i := 0; i < 2048; i++ { 30 | // Warning: Do not use i. By taking the address, 31 | // you would not get each value 32 | j := i 33 | d.Set(diodes.GenericDataType(&j)) 34 | } 35 | }() 36 | 37 | // reader 38 | poller := diodes.NewPoller(d) 39 | for { 40 | i := poller.Next() 41 | fmt.Println(*(*int)(i)) 42 | } 43 | ``` 44 | 45 | ### Example: Creating a Concrete Shell 46 | 47 | Diodes accept and return `diodes.GenericDataType`. It is recommended to not 48 | use these generic pointers directly. Rather, it is a much better experience to 49 | wrap the diode in a concrete shell that accepts the types your program works 50 | with and does the type casting for you. Here is an example of how to create a 51 | concrete shell for `[]byte`: 52 | 53 | ```go 54 | type OneToOne struct { 55 | d *diodes.Poller 56 | } 57 | 58 | func NewOneToOne(size int, alerter diodes.Alerter) *OneToOne { 59 | return &OneToOne{ 60 | d: diodes.NewPoller(diodes.NewOneToOne(size, alerter)), 61 | } 62 | } 63 | 64 | func (d *OneToOne) Set(data []byte) { 65 | d.d.Set(diodes.GenericDataType(&data)) 66 | } 67 | 68 | func (d *OneToOne) TryNext() ([]byte, bool) { 69 | data, ok := d.d.TryNext() 70 | if !ok { 71 | return nil, ok 72 | } 73 | 74 | return *(*[]byte)(data), true 75 | } 76 | 77 | func (d *OneToOne) Next() []byte { 78 | data := d.d.Next() 79 | return *(*[]byte)(data) 80 | } 81 | ``` 82 | 83 | Creating a concrete shell gives you the following advantages: 84 | 85 | - The compiler will tell you if you use a diode to read or write data of the 86 | wrong type. 87 | - The type casting syntax in go is not common and should be hidden. 88 | - It prevents the generic pointer type from escaping in to client code. 89 | 90 | ### Dropping Data 91 | 92 | The diode takes an `Alerter` as an argument to alert the user code to when 93 | the read noticed it missed data. It is important to note that the go-routine 94 | consuming from the diode is used to signal the alert. 95 | 96 | When the diode notices it has fallen behind, it will move the read index to 97 | the new write index and therefore drop more than a single message. 98 | 99 | There are two things to consider when choosing a diode: 100 | 101 | 1. Storage layer 102 | 2. Access layer 103 | 104 | ### Storage Layer 105 | 106 | ##### OneToOne 107 | 108 | The OneToOne diode is meant to be used by one producing (invoking `Set()`) 109 | go-routine and a (different) consuming (invoking `TryNext()`) go-routine. It 110 | is not thread safe for multiple readers or writers. 111 | 112 | ##### ManyToOne 113 | 114 | The ManyToOne diode is optimized for many producing (invoking `Set()`) 115 | go-routines and a single consuming (invoking `TryNext()`) go-routine. It is 116 | not thread safe for multiple readers. 117 | 118 | It is recommended to have a larger diode buffer size if the number of producers 119 | is high. This is to avoid the diode from having to mitigate write collisions 120 | (it will call its alert function if this occurs). 121 | 122 | ### Access Layer 123 | 124 | ##### Poller 125 | 126 | The Poller uses polling via `time.Sleep(...)` when `Next()` is invoked. While 127 | polling might seem sub-optimal, it allows the producer to be completely 128 | decoupled from the consumer. If you require very minimal push back on the 129 | producer, then the Poller is a better choice. However, if you require several 130 | diodes (e.g. one per connected client), then having several go-routines 131 | polling (sleeping) may be hard on the scheduler. 132 | 133 | ##### Waiter 134 | 135 | The Waiter uses a conditional mutex to manage when the reader is alerted 136 | of new data. While this method is great for the scheduler, it does have 137 | extra overhead for the producer. Therefore, it is better suited for situations 138 | where you have several diodes and can afford slightly slower producers. 139 | 140 | ### Benchmarks 141 | 142 | There are benchmarks that compare the various storage and access layers to 143 | channels. To run them: 144 | 145 | ``` 146 | go test -bench=. -run=NoTest 147 | ``` 148 | 149 | ### Known Issues 150 | 151 | If a diode was to be written to `18446744073709551615+1` times it would overflow 152 | a `uint64`. This will cause problems if the size of the diode is not a power 153 | of two (`2^x`). If you write into a diode at the rate of one message every 154 | nanosecond, without restarting your process, it would take you 584.54 years to 155 | encounter this issue. 156 | 157 | [diode-logo]: https://raw.githubusercontent.com/cloudfoundry/go-diodes/gh-pages/diode-logo.png 158 | [go-doc-badge]: https://godoc.org/code.cloudfoundry.org/go-diodes?status.svg 159 | [go-doc]: https://godoc.org/code.cloudfoundry.org/go-diodes 160 | -------------------------------------------------------------------------------- /benchmarks_test.go: -------------------------------------------------------------------------------- 1 | package diodes_test 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | "log" 7 | "os" 8 | "sync" 9 | "testing" 10 | "time" 11 | 12 | "code.cloudfoundry.org/go-diodes" 13 | ) 14 | 15 | var randData = randDataGen() 16 | 17 | func TestMain(m *testing.M) { 18 | log.SetOutput(io.Discard) 19 | 20 | os.Exit(m.Run()) 21 | } 22 | 23 | func BenchmarkOneToOnePoller(b *testing.B) { 24 | d := diodes.NewPoller(diodes.NewOneToOne(b.N, diodes.AlertFunc(func(missed int) { 25 | panic("Oops...") 26 | }))) 27 | 28 | var wg sync.WaitGroup 29 | wg.Add(1) 30 | defer wg.Wait() 31 | 32 | go func() { 33 | defer wg.Done() 34 | for i := 0; i < b.N; i++ { 35 | data := randData(i) 36 | d.Set(diodes.GenericDataType(data)) 37 | } 38 | }() 39 | 40 | b.ResetTimer() 41 | 42 | for i := 0; i < b.N; i++ { 43 | d.Next() 44 | } 45 | } 46 | 47 | func BenchmarkOneToOneWaiter(b *testing.B) { 48 | d := diodes.NewWaiter(diodes.NewOneToOne(b.N, diodes.AlertFunc(func(missed int) { 49 | panic("Oops...") 50 | }))) 51 | 52 | var wg sync.WaitGroup 53 | wg.Add(1) 54 | defer wg.Wait() 55 | 56 | go func() { 57 | defer wg.Done() 58 | for i := 0; i < b.N; i++ { 59 | data := randData(i) 60 | d.Set(diodes.GenericDataType(data)) 61 | } 62 | }() 63 | 64 | b.ResetTimer() 65 | 66 | for i := 0; i < b.N; i++ { 67 | d.Next() 68 | } 69 | } 70 | 71 | func BenchmarkManyToOnePoller(b *testing.B) { 72 | d := diodes.NewPoller(diodes.NewManyToOne(b.N, diodes.AlertFunc(func(missed int) { 73 | panic("Oops...") 74 | }))) 75 | 76 | var wg sync.WaitGroup 77 | wg.Add(1) 78 | defer wg.Wait() 79 | 80 | go func() { 81 | defer wg.Done() 82 | for i := 0; i < b.N; i++ { 83 | data := randData(i) 84 | d.Set(diodes.GenericDataType(data)) 85 | } 86 | }() 87 | 88 | b.ResetTimer() 89 | 90 | for i := 0; i < b.N; i++ { 91 | d.Next() 92 | } 93 | } 94 | 95 | func BenchmarkManyToOneWaiter(b *testing.B) { 96 | d := diodes.NewWaiter(diodes.NewManyToOne(b.N, diodes.AlertFunc(func(missed int) { 97 | panic("Oops...") 98 | }))) 99 | 100 | var wg sync.WaitGroup 101 | wg.Add(1) 102 | defer wg.Wait() 103 | 104 | go func() { 105 | defer wg.Done() 106 | for i := 0; i < b.N; i++ { 107 | data := randData(i) 108 | d.Set(diodes.GenericDataType(data)) 109 | } 110 | }() 111 | 112 | b.ResetTimer() 113 | 114 | for i := 0; i < b.N; i++ { 115 | d.Next() 116 | } 117 | } 118 | 119 | func BenchmarkChannel(b *testing.B) { 120 | c := make(chan []byte, b.N) 121 | 122 | var wg sync.WaitGroup 123 | wg.Add(1) 124 | defer wg.Wait() 125 | 126 | go func() { 127 | defer wg.Done() 128 | for i := 0; i < b.N; i++ { 129 | data := randData(i) 130 | c <- *data 131 | } 132 | }() 133 | 134 | b.ResetTimer() 135 | 136 | for i := 0; i < b.N; i++ { 137 | <-c 138 | } 139 | } 140 | 141 | func BenchmarkOneToOnePollerDrain(b *testing.B) { 142 | d := diodes.NewPoller(diodes.NewOneToOne(100, diodes.AlertFunc(func(missed int) { 143 | // NOP 144 | }))) 145 | 146 | var wg sync.WaitGroup 147 | wg.Add(1) 148 | 149 | go func() { 150 | wg.Done() 151 | for { 152 | d.Next() 153 | } 154 | }() 155 | 156 | wg.Wait() 157 | b.ResetTimer() 158 | 159 | for i := 0; i < b.N; i++ { 160 | data := randData(i) 161 | d.Set(diodes.GenericDataType(data)) 162 | } 163 | } 164 | 165 | func BenchmarkOneToOneWaiterDrain(b *testing.B) { 166 | d := diodes.NewWaiter(diodes.NewOneToOne(100, diodes.AlertFunc(func(missed int) { 167 | // NOP 168 | }))) 169 | 170 | var wg sync.WaitGroup 171 | wg.Add(1) 172 | go func() { 173 | wg.Done() 174 | for { 175 | d.Next() 176 | } 177 | }() 178 | 179 | wg.Wait() 180 | b.ResetTimer() 181 | 182 | for i := 0; i < b.N; i++ { 183 | data := randData(i) 184 | d.Set(diodes.GenericDataType(data)) 185 | } 186 | } 187 | 188 | func BenchmarkChannelDrain(b *testing.B) { 189 | c := make(chan []byte, 100) 190 | var wg sync.WaitGroup 191 | wg.Add(1) 192 | 193 | go func() { 194 | wg.Done() 195 | for range c { 196 | } 197 | }() 198 | 199 | wg.Wait() 200 | b.ResetTimer() 201 | 202 | for i := 0; i < b.N; i++ { 203 | data := randData(i) 204 | select { 205 | case c <- *data: 206 | default: 207 | drainChannel(c) 208 | } 209 | } 210 | } 211 | 212 | func BenchmarkManyWritersDiode(b *testing.B) { 213 | d := diodes.NewWaiter(diodes.NewManyToOne(10000, diodes.AlertFunc(func(int) { 214 | // NOP 215 | }))) 216 | 217 | var wg sync.WaitGroup 218 | wg.Add(1) 219 | 220 | go func() { 221 | wg.Done() 222 | for { 223 | d.Next() 224 | time.Sleep(100 * time.Millisecond) 225 | } 226 | }() 227 | 228 | wg.Wait() 229 | b.ResetTimer() 230 | b.RunParallel(func(pb *testing.PB) { 231 | var i int 232 | for pb.Next() { 233 | data := randData(i) 234 | i++ 235 | d.Set(diodes.GenericDataType(data)) 236 | } 237 | }) 238 | } 239 | 240 | func BenchmarkManyWritersChannel(b *testing.B) { 241 | c := make(chan []byte, 10000) 242 | 243 | var wg sync.WaitGroup 244 | wg.Add(1) 245 | 246 | go func() { 247 | wg.Done() 248 | for range c { 249 | time.Sleep(100 * time.Millisecond) 250 | } 251 | }() 252 | 253 | wg.Wait() 254 | b.ResetTimer() 255 | b.RunParallel(func(pb *testing.PB) { 256 | var i int 257 | for pb.Next() { 258 | data := randData(i) 259 | i++ 260 | select { 261 | case c <- *data: 262 | default: 263 | drainChannel(c) 264 | } 265 | } 266 | }) 267 | } 268 | 269 | func drainChannel(c chan []byte) { 270 | for { 271 | select { 272 | case <-c: 273 | default: 274 | return 275 | } 276 | } 277 | } 278 | 279 | func randDataGen() func(int) *[]byte { 280 | var ( 281 | data [][]byte 282 | ) 283 | 284 | for j := 0; j < 5; j++ { 285 | buffer := make([]byte, 100) 286 | rand.Read(buffer) //nolint:errcheck 287 | data = append(data, buffer) 288 | } 289 | 290 | return func(i int) *[]byte { 291 | return &data[i%len(data)] 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= 2 | github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= 6 | github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= 7 | github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= 8 | github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= 9 | github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= 10 | github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= 11 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 12 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 13 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 14 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 15 | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= 16 | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= 17 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 18 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 19 | github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= 20 | github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= 21 | github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= 22 | github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= 23 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 24 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 25 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= 28 | github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= 29 | github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= 30 | github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= 31 | github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8= 32 | github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= 33 | github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= 34 | github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= 35 | github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= 36 | github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= 37 | github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= 38 | github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 42 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 43 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 44 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 45 | github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= 46 | github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 47 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 48 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 49 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 50 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 51 | github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= 52 | github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= 53 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 54 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 55 | golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= 56 | golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= 57 | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 58 | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 59 | golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= 60 | golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 61 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 62 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 63 | golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 64 | golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 65 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 66 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 67 | google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= 68 | google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 71 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 73 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------