Skip to content

Commit

Permalink
Add solution for yr-channels-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
skosovsky committed Dec 15, 2023
1 parent 2920a4c commit c482b87
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 0 deletions.
34 changes: 34 additions & 0 deletions yr-channels-bug/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
APP=algo/yr-channels-bug
.PHONY: help
help: Makefile ## Show this help
@echo
@echo "Choose a command run in "$(APP)":"
@echo
@fgrep -h "##" $(MAKEFILE_LIST) | sed -e 's/\(\:.*\#\#\)/\:\ /' | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'

.PHONY: build
build: ## Build an application
@echo "Building ${APP} ..."
mkdir -p build
go build -o build/${APP} main.go

run: ## Run an application
@echo "Starting ${APP} ..."
go run main.go

test: ## Run an application
@echo "Testing ${APP} ..."
go test

bench: ## Run an application
@echo "Benchmarking ${APP} ..."
go test -bench=. .

clean: ## Clean a garbage
@echo "Cleaning"
go clean
rm -rf build

lint: ## Check a code by golangci-lint
@echo "Linter checking..."
golangci-lint run -c golangci.yml ./...
65 changes: 65 additions & 0 deletions yr-channels-bug/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Yuri Romanov
From chat

Есть код, нужно найти ошибку и исправить:

```Go
package main

import (
"fmt"
)

func main() {
work := []int{1, 2, 3, 4, 5, 6, 7, 8}
in := generateWork(work)

out1 := fanOut(in)
out2 := fanOut(in)
out3 := fanOut(in)
out4 := fanOut(in)

for range work {
select {
case value := <-out1:
fmt.Println("Output 1 got:", value)
case value := <-out2:
fmt.Println("Output 2 got:", value)
case value := <-out3:
fmt.Println("Output 3 got:", value)
case value := <-out4:
fmt.Println("Output 4 got:", value)
}
}

}

func fanOut(in <-chan int) <-chan int {
out := make(chan int)

go func() {
defer close(out)

for data := range in {
out <- data
}
}()

return out
}

func generateWork(work []int) <-chan int {
ch := make(chan int)

go func() {
defer close(ch)

for _, w := range work {
ch <- w
}
return
}()

return ch
}
```
3 changes: 3 additions & 0 deletions yr-channels-bug/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/skosovsky/algo/yr-channels-bug

go 1.21.5
160 changes: 160 additions & 0 deletions yr-channels-bug/golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
linters-settings:
depguard:
# new configuration
rules:
logger:
deny:
# logging is allowed only by logutils.Log,
# logrus is allowed to use only in logutils package.
- pkg: "github.com/sirupsen/logrus"
desc: logging is allowed only by logutils.Log
dupl:
threshold: 100
funlen:
lines: -1 # the number of lines (code + empty lines) is not a right metric and leads to code without empty line or one-liner.
statements: 50
goconst:
min-len: 2
min-occurrences: 3
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
gocyclo:
min-complexity: 15
gofmt:
rewrite-rules:
- pattern: 'interface{}'
replacement: 'any'
goimports:
local-prefixes: github.com/golangci/golangci-lint
gomnd:
# don't include the "operation" and "assign"
checks:
- argument
- case
- condition
- return
ignored-numbers:
- '0'
- '1'
- '2'
- '3'
ignored-functions:
- strings.SplitN

govet:
check-shadowing: true
settings:
printf:
funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
lll:
line-length: 140
misspell:
locale: US
nolintlint:
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
revive:
rules:
- name: unexported-return
disabled: true
- name: unused-parameter

linters:
disable-all: true
enable:
- bodyclose
- depguard
- dogsled
- dupl
- errcheck
- exportloopref
- funlen
- gocheckcompilerdirectives
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- noctx
- nolintlint
- revive
- staticcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- whitespace

# don't enable:
# - asciicheck
# - scopelint
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - interfacer
# - maligned
# - nestif
# - prealloc
# - testpackage
# - wsl

issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd

- path: pkg/golinters/errcheck.go
text: "SA1019: errCfg.Exclude is deprecated: use ExcludeFunctions instead"
- path: pkg/commands/run.go
text: "SA1019: lsc.Errcheck.Exclude is deprecated: use ExcludeFunctions instead"
- path: pkg/commands/run.go
text: "SA1019: e.cfg.Run.Deadline is deprecated: Deadline exists for historical compatibility and should not be used."

- path: pkg/golinters/gofumpt.go
text: "SA1019: settings.LangVersion is deprecated: use the global `run.go` instead."
- path: pkg/golinters/staticcheck_common.go
text: "SA1019: settings.GoVersion is deprecated: use the global `run.go` instead."
- path: pkg/lint/lintersdb/manager.go
text: "SA1019: (.+).(GoVersion|LangVersion) is deprecated: use the global `run.go` instead."
- path: pkg/golinters/unused.go
text: "rangeValCopy: each iteration copies 160 bytes \\(consider pointers or indexing\\)"
- path: test/(fix|linters)_test.go
text: "string `gocritic.go` has 3 occurrences, make it a constant"

run:
timeout: 5m
skip-dirs:
- test/testdata_etc # test files
- internal/cache # extracted from Go code
- internal/renameio # extracted from Go code
- internal/robustio # extracted from Go code
108 changes: 108 additions & 0 deletions yr-channels-bug/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

import (
"fmt"
)

func main() {
work := []int{1, 2, 3, 4, 5, 6, 7, 8}
in := generateWork(work)

out1 := fanOut(in)
out2 := fanOut(in)
out3 := fanOut(in)
out4 := fanOut(in)

// Simple
for i := 0; i < len(work); {
select {
case value, ok := <-out1:
if ok {
i++
fmt.Println("Output 1 got:", value)
}

case value, ok := <-out2:
if ok {
i++
fmt.Println("Output 2 got:", value)
}

case value, ok := <-out3:
if ok {
i++
fmt.Println("Output 3 got:", value)
}

case value, ok := <-out4:
if ok {
i++
fmt.Println("Output 4 got:", value)
}

}
}

// Spinlock - более оптимальный варианты, т.к. nil каналы не проверяет select
for i := 0; i < len(work); i++ {
select {
case value, ok := <-out1:
if !ok {
i--
out1 = nil
continue
}
fmt.Println("Output 1 got:", value)
case value, ok := <-out2:
if !ok {
i--
out2 = nil
continue
}
fmt.Println("Output 2 got:", value)
case value, ok := <-out3:
if !ok {
i--
out3 = nil
continue
}
fmt.Println("Output 3 got:", value)
case value, ok := <-out4:
if !ok {
i--
out4 = nil
continue
}
fmt.Println("Output 4 got:", value)
}
}
}

func fanOut(in <-chan int) <-chan int {
out := make(chan int)

go func() {
defer close(out)

for data := range in {
out <- data
}
}()

return out
}

func generateWork(work []int) <-chan int {
ch := make(chan int)

go func() {
defer close(ch)

for _, w := range work {
ch <- w
}
return
}()

return ch
}

0 comments on commit c482b87

Please sign in to comment.