diff --git a/semaphore/Makefile b/semaphore/Makefile new file mode 100644 index 0000000..977c1c9 --- /dev/null +++ b/semaphore/Makefile @@ -0,0 +1,53 @@ +.DEFAULT_GOAL := help + +# ==================================================================================== # +# HELPERS +# ==================================================================================== # + +## help: print this help message +.PHONY: help +help: + @echo 'Usage:' + @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' + +## all: tidy + audit + test/cover +all: generate tidy test lint test/cover + + +# ==================================================================================== # +# QUALITY CONTROL +# ==================================================================================== # + +## tidy: format code and tidy modfile +.PHONY: tidy +tidy: + go fmt ./... + goimports -local=$(shell cat go.mod | grep module | awk '{print $$2}')/ -w . + go mod tidy -v + +## audit: run quality control checks +.PHONY: lint +lint: + go mod verify + go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run -v ./... + + +# ==================================================================================== # +# DEVELOPMENT +# ==================================================================================== # + +## generate: run all generators +.PHONY: generate +generate: + go generate ./... + +## test: run all tests +.PHONY: test +test: + go test -race -buildvcs ./... + +## test/cover: run all tests and display coverage +.PHONY: test/cover +test/cover: + go test -race -buildvcs -coverprofile=/tmp/coverage.out ./... + go tool cover -html=/tmp/coverage.out diff --git a/semaphore/go.mod b/semaphore/go.mod new file mode 100644 index 0000000..5d89c1e --- /dev/null +++ b/semaphore/go.mod @@ -0,0 +1,3 @@ +module semaphore + +go 1.21.6 diff --git a/semaphore/main.go b/semaphore/main.go new file mode 100644 index 0000000..e3ba151 --- /dev/null +++ b/semaphore/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "context" + "sync/atomic" + "time" +) + +type Semaphore interface { + Acquire(ctx context.Context) error + Release() +} + +// ChanSemaphore implementation +type ChanSemaphore struct { + C chan struct{} +} + +func (s *ChanSemaphore) Acquire(ctx context.Context) error { + select { + case s.C <- struct{}{}: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (s *ChanSemaphore) Release() { + <-s.C +} + +// AtomicSemaphore implementation +type AtomicSemaphore struct { + acquired atomic.Bool + TryDelay time.Duration +} + +func (s *AtomicSemaphore) Acquire(ctx context.Context) error { + if s.acquired.CompareAndSwap(false, true) { + return nil + } + + return s.slowAcquire(ctx) +} + +func (s *AtomicSemaphore) slowAcquire(ctx context.Context) error { + ticker := time.NewTicker(s.TryDelay) + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-ticker.C: + if s.acquired.CompareAndSwap(false, true) { + return nil + } + } + } +} + +func (s *AtomicSemaphore) Release() { + s.acquired.Store(false) +} diff --git a/semaphore/main_test.go b/semaphore/main_test.go new file mode 100644 index 0000000..3858ffe --- /dev/null +++ b/semaphore/main_test.go @@ -0,0 +1,9 @@ +package main + +import ( + "testing" +) + +func TestPipe(t *testing.T) { + +}