├── .DS_Store ├── additional ├── lesson_testing_and_profiling │ ├── benchmark_profile │ │ ├── bench_test.go │ │ ├── go.mod │ │ └── go.sum │ ├── black_box_testing │ │ └── main_test.go │ ├── profile_from_code │ │ └── main.go │ ├── profile_from_web │ │ └── main.go │ ├── testing_in_any_packages │ │ ├── first_package │ │ │ └── order_test.go │ │ ├── go.mod │ │ ├── go.sum │ │ └── second_package │ │ │ └── order_test.go │ ├── testing_in_one_package │ │ ├── go.mod │ │ ├── go.sum │ │ └── order_test.go │ ├── testing_in_one_package_parallel │ │ ├── go.mod │ │ ├── go.sum │ │ └── order_test.go │ ├── testing_in_one_package_parallel_with_subtests │ │ ├── go.mod │ │ ├── go.sum │ │ └── order_test.go │ ├── testing_leaks │ │ ├── go.mod │ │ ├── go.sum │ │ └── leak_test.go │ └── white_box_testing │ │ └── main_test.go └── open_lessons │ └── mutex_internals │ ├── broken_mutex_1 │ └── main.go │ ├── broken_mutex_2 │ └── main.go │ ├── broken_mutex_3 │ └── main.go │ ├── correct_increment │ └── main.go │ ├── incorrect_increment │ └── main.go │ ├── memory_barriers │ └── main.cpp │ ├── mutex_without_owner │ └── main.go │ ├── peterson_mutex │ └── main.go │ ├── rw_mutex_implementation │ └── main.go │ ├── spinlock │ └── main.go │ ├── spinlock_combined │ └── main.go │ ├── spinlock_with_yield │ └── main.go │ └── ticket_lock │ └── main.go ├── database ├── .gitignore ├── Makefile ├── README.md ├── cmd │ ├── cli │ │ └── main.go │ └── server │ │ └── main.go ├── config.yml ├── go.mod ├── go.sum ├── internal │ ├── common │ │ ├── contexts.go │ │ ├── size_parser.go │ │ └── size_parser_test.go │ ├── concurrency │ │ ├── future.go │ │ ├── lock.go │ │ ├── promise.go │ │ └── semaphore.go │ ├── configuration │ │ ├── config.go │ │ └── config_test.go │ ├── database │ │ ├── compute │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── compute.go │ │ │ ├── compute_test.go │ │ │ ├── query.go │ │ │ └── query_test.go │ │ ├── database.go │ │ ├── database_mock.go │ │ ├── database_test.go │ │ ├── filesystem │ │ │ ├── segment.go │ │ │ ├── segment_test.go │ │ │ ├── segments_directory.go │ │ │ ├── segments_directory_test.go │ │ │ ├── test_data │ │ │ │ ├── wal_1000.log │ │ │ │ ├── wal_2000.log │ │ │ │ └── wal_3000.log │ │ │ ├── utils.go │ │ │ └── utils_test.go │ │ └── storage │ │ │ ├── engine │ │ │ └── in_memory │ │ │ │ ├── engine.go │ │ │ │ ├── engine_options.go │ │ │ │ ├── engine_options_test.go │ │ │ │ ├── engine_test.go │ │ │ │ ├── hash_table.go │ │ │ │ └── hash_table_test.go │ │ │ ├── id_generator.go │ │ │ ├── id_generator_test.go │ │ │ ├── replication │ │ │ ├── master.go │ │ │ ├── protocol.go │ │ │ ├── protocol_test.go │ │ │ └── slave.go │ │ │ ├── storage.go │ │ │ ├── storage_mock.go │ │ │ ├── storage_options.go │ │ │ ├── storage_options_test.go │ │ │ ├── storage_test.go │ │ │ └── wal │ │ │ ├── log.go │ │ │ ├── log_test.go │ │ │ ├── logs_reader.go │ │ │ ├── logs_reader_mock.go │ │ │ ├── logs_reader_test.go │ │ │ ├── logs_writer.go │ │ │ ├── logs_writer_mock.go │ │ │ ├── logs_writer_test.go │ │ │ ├── wal.go │ │ │ ├── wal_mock.go │ │ │ ├── wal_test.go │ │ │ ├── write_request.go │ │ │ └── write_request_test.go │ ├── initialization │ │ ├── engine_initializer.go │ │ ├── engine_initializer_test.go │ │ ├── initializer.go │ │ ├── initializer_test.go │ │ ├── logger_initializer.go │ │ ├── logger_initializer_test.go │ │ ├── network_initializer.go │ │ ├── network_initializer_test.go │ │ ├── replication_initializer.go │ │ ├── replication_initializer_test.go │ │ ├── wal_initializer.go │ │ └── wal_initializer_test.go │ └── network │ │ ├── network_options.go │ │ ├── network_options_test.go │ │ ├── tcp_client.go │ │ ├── tcp_client_test.go │ │ ├── tcp_server.go │ │ └── tcp_server_test.go └── test │ ├── e2e_network_test.go │ ├── e2e_wal_test.go │ └── wal │ └── .gitkeep └── lessons ├── 10_lesson_live_coding_practise ├── batcher │ └── main.go ├── cache │ └── main.go ├── scheduler │ └── main.go └── worker_pool │ └── main.go ├── 2_lesson_gorutines_and_scheduler ├── async_preemptible │ └── main.go ├── endless_loop │ └── main.go ├── gmp_model │ └── main.go ├── goroutine_index │ └── main.go ├── goroutines_number │ └── main.go ├── never_exit │ └── main.go ├── panic_from_other_goroutine │ └── main.go ├── runtime_exit │ └── main.go ├── tcp_server_with_panic │ └── main.go └── tcp_server_without_panic │ └── main.go ├── 3_lesson_sync_primitives_1 ├── .DS_Store ├── correct_increment │ └── main.go ├── data_race │ └── main.go ├── deadlock │ └── main.go ├── deadlock_with_work │ └── main.go ├── defer_performance │ └── perf_test.go ├── defer_with_mutex │ └── main.go ├── defer_with_panic │ └── main.go ├── incorrect_buffer_design │ └── main.go ├── incorrect_defer_with_loop │ └── main.go ├── incorrect_defer_with_mutex │ └── main.go ├── incorrect_increment │ └── main.go ├── incorrect_struct_design │ └── main.go ├── lazy_initialization │ └── main.go ├── livelock │ └── main.go ├── local_mutex │ └── main.go ├── lock_granularity │ └── main.go ├── lockable_struct │ └── main.go ├── mutex_different_operations │ └── main.go ├── mutex_for_order │ └── main.go ├── mutex_operations │ └── main.go ├── mutex_with_common_values │ └── main.go ├── mutex_with_local_values │ └── main.go ├── once │ └── main.go ├── print_foo_bar_alternately │ └── main.go ├── recursive_lock │ └── main.go ├── starvation │ └── main.go ├── sync_singleton │ └── main.go ├── sync_stack │ └── main.go ├── wait_group_copying │ └── main.go ├── wait_group_operations │ └── main.go ├── wait_group_order │ └── main.go └── waiting_goroutines │ └── main.go ├── 4_lesson_sync_primitives_2 ├── .DS_Store ├── action_during_actions │ └── main.go ├── atomic_performance │ └── perf_test.go ├── atomic_pointer │ └── main.go ├── atomic_value │ └── main.go ├── broken_mutex_1 │ └── main.go ├── broken_mutex_2 │ └── main.go ├── broken_mutex_3 │ └── main.go ├── cas_loop │ └── main.go ├── compare_and_swap │ └── main.go ├── concurrent_map_writes │ └── main.go ├── cond │ └── main.go ├── cond_operations │ └── main.go ├── false_sharing │ └── perf_test.go ├── incorrect_increment │ └── main.go ├── once_implementation │ └── once.go ├── peterson_mutex │ └── main.go ├── pool │ └── pool_test.go ├── pool_implementation │ └── main.go ├── print_in_order │ └── main.go ├── recursive_mutex_implementation │ └── main.go ├── rlocker │ └── main.go ├── rw_mutex_implementation │ └── main.go ├── rw_mutex_operations │ └── main.go ├── rw_mutex_performance │ └── perf_test.go ├── rw_mutex_with_map │ └── main.go ├── semaphore │ └── main.go ├── spinlock │ └── main.go ├── spinlock_combined │ └── main.go ├── spinlock_with_yield │ └── main.go ├── sync_map │ └── main.go ├── sync_map_implementation │ └── main.go ├── ticket_lock │ └── main.go └── timed_mutex_implementation │ └── main.go ├── 5_lesson_channels ├── broadcast │ └── main.go ├── buffered_channel │ └── main.go ├── channel_data_race │ └── main.go ├── channel_interaction │ └── main.go ├── copy_channel │ └── main.go ├── deadlock │ └── main.go ├── goroutine_leak_1 │ └── main.go ├── goroutine_leak_2 │ └── main.go ├── increment_with_channel │ └── main.go ├── increment_with_mutex │ └── main.go ├── is_closed_1 │ └── main.go ├── is_closed_2 │ └── main.go ├── nil_channel │ └── main.go ├── nil_channel_task │ └── main.go ├── non_blocking_channels_correct │ └── main.go ├── non_blocking_channels_incorrect │ └── main.go ├── operations_with_channel │ └── main.go ├── prioritization │ └── main.go ├── prioritization_weight │ └── main.go ├── producer_consumer │ └── main.go ├── select │ └── main.go ├── select_forever │ └── main.go ├── select_with_break_and_continue │ └── main.go ├── select_with_main_goroutine │ └── main.go ├── sequential_execution │ └── main.go ├── signals │ └── main.go ├── unidirectional_channels │ └── main.go └── write_after_close │ └── main.go ├── 6_lesson_channel_patterns ├── after_and_tick │ └── main.go ├── barrier │ └── main.go ├── bridge │ └── main.go ├── done_channel │ └── main.go ├── done_channel_with_struct │ └── main.go ├── dynamic_select_with_reflection │ └── main.go ├── errgroup_implementation │ └── main.go ├── fan_in │ └── main.go ├── fan_out │ └── main.go ├── filter │ └── main.go ├── future │ └── main.go ├── future_with_promise │ └── main.go ├── generator │ └── main.go ├── graceful_shutdown │ └── main.go ├── moving_later │ └── main.go ├── or_channel │ └── main.go ├── or_done │ └── main.go ├── parallel_pipeline │ └── main.go ├── parallel_pipeline_with_fan_out │ └── main.go ├── pipeline │ └── main.go ├── promise │ └── main.go ├── rate_limiter │ └── main.go ├── request_with_timeout │ └── main.go ├── semaphore │ └── main.go ├── single_flight │ └── main.go ├── tee │ └── main.go ├── ticker │ └── main.go ├── ticker_after_tick │ └── main.go ├── ticker_implementation │ └── main.go ├── timer │ └── main.go └── transformer │ └── main.go ├── 7_lesson_contexts_and_memory_barriers ├── after_done │ └── main.go ├── chech_cancel │ └── main.go ├── context_handling │ └── main.go ├── context_inheritance_1 │ └── main.go ├── context_inheritance_2 │ └── main.go ├── context_with_cancel │ └── main.go ├── context_with_cancel_cause │ └── main.go ├── context_with_http_client │ └── main.go ├── context_with_http_server │ └── main.go ├── context_with_timeout │ └── main.go ├── context_with_timeout_cause │ └── main.go ├── context_with_timeout_implementation │ └── main.go ├── context_with_value │ └── main.go ├── context_with_value_inheritance │ └── main.go ├── context_with_value_type │ └── main.go ├── context_without_cancel │ └── main.go ├── correct_memory_model_usage │ └── main.go ├── errgroup_with_ctx │ ├── go.mod │ ├── go.sum │ └── main.go ├── graceful_shutdown │ └── main.go ├── incorrect_memory_model_usage │ └── main.go ├── memory_reordering │ └── main.go ├── nil_context │ └── main.go ├── with_barriers_sync │ └── main.go ├── with_ctx_check │ └── main.go └── without_barriers_sync │ └── main.go ├── 8_lesson_sync_algorithms_and_lock_free ├── lock_free │ ├── michael_scott_queue │ │ └── main.go │ ├── queue │ │ └── main.go │ ├── stack │ │ └── main.go │ └── treiber_stack │ │ └── main.go ├── rcu │ ├── cache_with_atomic.go │ └── cache_with_mutex.go ├── set │ ├── fine_sync │ │ └── main.go │ ├── lazy_sync │ │ └── main.go │ ├── optimistic_sync │ │ └── main.go │ ├── rough_sync │ │ └── main.go │ └── without_sync │ │ └── main.go └── sharded_map │ └── main.go └── 9_lesson_concurrency_patterns ├── 2pl ├── go.mod ├── go.sum ├── main.go ├── scheduler.go ├── scheduler_test.go ├── storage.go └── transaction.go ├── actors ├── actor.go ├── actor_manager.go ├── go.mod └── main.go ├── amdala └── perf_test.go └── mvcc ├── go.mod ├── go.sum ├── main.go ├── scheduler.go ├── scheduler_test.go ├── storage.go ├── storage_test.go └── transaction.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/.DS_Store -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/benchmark_profile/bench_test.go: -------------------------------------------------------------------------------- 1 | package perftest 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // go test -bench=. 8 | // go test -bench=. -benchmem 9 | // go test -bench=. -cpuprofile=profile.out 10 | // go tool pprof profile.out 11 | // top 12 | // top 20 13 | // top 20 -cum 14 | // list cp 15 | // list runtime.mallocgc 16 | // web 17 | // trim=false 18 | // web 19 | // go tool pprof -http :8081 profile.out 20 | 21 | // go test -bench=. -memprofile=profile.out 22 | // go tool pprof profile.out 23 | 24 | // go test -bench=. -benchmem --count=6 > profile.out 25 | // go test -bench=. -benchmem --count=6 > profile_new.out 26 | // benchstat profile_new.out profile.out 27 | 28 | func cp(input []string) []string { 29 | var output []string 30 | for _, value := range input { 31 | output = append(output, value) 32 | } 33 | 34 | return output 35 | } 36 | 37 | func cpOptimized(input []string) []string { 38 | output := make([]string, 0, len(input)) 39 | for _, value := range input { 40 | output = append(output, value) 41 | } 42 | 43 | return output 44 | } 45 | 46 | func BenchmarkCopy(b *testing.B) { 47 | data := []string{"1", "2", "3", "4", "5"} 48 | for i := 0; i < b.N; i++ { 49 | cpOptimized(data) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/benchmark_profile/go.mod: -------------------------------------------------------------------------------- 1 | module benchmark_profile 2 | 3 | go 1.22.3 4 | 5 | require ( 6 | github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 // indirect 7 | golang.org/x/perf v0.0.0-20240510023725-bedb9135df6d // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/benchmark_profile/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794 h1:xlwdaKcTNVW4PtpQb8aKA4Pjy0CdJHEqvFbAnvR5m2g= 2 | github.com/aclements/go-moremath v0.0.0-20210112150236-f10218a38794/go.mod h1:7e+I0LQFUI9AXWxOfsQROs9xPhoJtbsyWcjJqDd4KPY= 3 | golang.org/x/perf v0.0.0-20240510023725-bedb9135df6d h1:QTr4HWRb3m3+xRitZtKm9CqwiROVnNCMfL+U2P9C0Vc= 4 | golang.org/x/perf v0.0.0-20240510023725-bedb9135df6d/go.mod h1:ipWOGiEQ0J5j74LbJ1iNKP2gTl4oge+Djuh2sTOqiRc= 5 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/black_box_testing/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func ParallelFunction() int { 9 | var wg sync.WaitGroup 10 | var result int 11 | numWorkers := 4 12 | 13 | wg.Add(numWorkers) 14 | for i := 0; i < numWorkers; i++ { 15 | go func(id int) { 16 | defer wg.Done() 17 | result += id 18 | }(i) 19 | } 20 | 21 | wg.Wait() 22 | return result 23 | } 24 | 25 | func TestParallelFunction(t *testing.T) { 26 | expected := 6 27 | result := ParallelFunction() 28 | 29 | if result != expected { 30 | t.Errorf("Expected %d, but got %d", expected, result) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/profile_from_code/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime/pprof" 6 | ) 7 | 8 | func main() { 9 | fd, _ := os.Create("./cpu_profile.out") 10 | _ = pprof.StartCPUProfile(fd) 11 | 12 | // ... 13 | 14 | pprof.StopCPUProfile() 15 | _ = fd.Close() 16 | } 17 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/profile_from_web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "hash/fnv" 6 | "net/http" 7 | _ "net/http/pprof" // important 8 | "runtime" 9 | "sync" 10 | ) 11 | 12 | var mutex sync.Mutex 13 | 14 | // ab -n 100000 -c100 http://localhost:6060/headers 15 | 16 | // go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=5" 17 | // go tool pprof "http://localhost:6060/debug/pprof/mutex?seconds=5" 18 | // go tool pprof "http://localhost:6060/debug/pprof/block?seconds=5" 19 | 20 | // http://localhost:6060/debug/pprof 21 | // curl "http://localhost:6060/debug/pprof/profile?seconds=5" > profile.out 22 | 23 | func Handle(w http.ResponseWriter, req *http.Request) { 24 | mutex.Lock() 25 | 26 | var hashes []uint32 27 | for i := 0; i < 1000000; i++ { 28 | hash := fnv.New32a() 29 | _, _ = hash.Write([]byte("value")) 30 | hashes = append(hashes, hash.Sum32()) 31 | } 32 | 33 | mutex.Unlock() 34 | 35 | _, _ = fmt.Fprintf(w, "response\n") 36 | } 37 | 38 | func main() { 39 | runtime.SetBlockProfileRate(1) // by default turned off 40 | runtime.SetMutexProfileFraction(1) // by default turned off 41 | 42 | http.HandleFunc("/headers", Handle) 43 | 44 | /* 45 | http.HandleFunc("/debug/pprof/", pprof.Index) 46 | http.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 47 | http.HandleFunc("/debug/pprof/profile", pprof.Profile) 48 | http.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 49 | http.HandleFunc("/debug/pprof/trace", pprof.Trace) 50 | */ 51 | 52 | _ = http.ListenAndServe("localhost:6060", nil) 53 | } 54 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_any_packages/first_package/order_test.go: -------------------------------------------------------------------------------- 1 | package first_package 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | // go test -p 4 -v ./... 9 | 10 | // -p n 11 | // the number of programs, such as build commands or 12 | // test binaries, that can be run in parallel. 13 | // The default is the number of CPUs available. 14 | 15 | // What happens if we specify -p=1? 16 | // There would be only one process running tests, so all tests would be run sequentially, one package at a time. 17 | 18 | func Test1(t *testing.T) { 19 | assert.Equal(t, 10, 5+5) 20 | } 21 | 22 | func Test2(t *testing.T) { 23 | assert.Equal(t, 10, 5+5) 24 | } 25 | 26 | func Test5(t *testing.T) { 27 | assert.Equal(t, 10, 5+5) 28 | } 29 | 30 | func Test4(t *testing.T) { 31 | assert.Equal(t, 10, 5+5) 32 | } 33 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_any_packages/go.mod: -------------------------------------------------------------------------------- 1 | module testing_in_any_packages 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_any_packages/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_any_packages/second_package/order_test.go: -------------------------------------------------------------------------------- 1 | package second_package 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | // go test -p 4 -v ./... 9 | 10 | // -p n 11 | // the number of programs, such as build commands or 12 | // test binaries, that can be run in parallel. 13 | // The default is the number of CPUs available. 14 | 15 | // What happens if we specify -p=1? 16 | // There would be only one process running tests, so all tests would be run sequentially, one package at a time. 17 | 18 | func Test1(t *testing.T) { 19 | assert.Equal(t, 10, 5+5) 20 | } 21 | 22 | func Test2(t *testing.T) { 23 | assert.Equal(t, 10, 5+5) 24 | } 25 | 26 | func Test5(t *testing.T) { 27 | assert.Equal(t, 10, 5+5) 28 | } 29 | 30 | func Test4(t *testing.T) { 31 | assert.Equal(t, 10, 5+5) 32 | } 33 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package/go.mod: -------------------------------------------------------------------------------- 1 | module testing_in_one_package 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package/order_test.go: -------------------------------------------------------------------------------- 1 | package order_test 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | // go test -v 9 | 10 | func Test1(t *testing.T) { 11 | assert.Equal(t, 10, 5+5) 12 | } 13 | 14 | func Test2(t *testing.T) { 15 | assert.Equal(t, 10, 5+5) 16 | } 17 | 18 | func Test5(t *testing.T) { 19 | assert.Equal(t, 10, 5+5) 20 | } 21 | 22 | func Test4(t *testing.T) { 23 | assert.Equal(t, 10, 5+5) 24 | } 25 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel/go.mod: -------------------------------------------------------------------------------- 1 | module testing_in_one_package 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel/order_test.go: -------------------------------------------------------------------------------- 1 | package order_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // go test -v 9 | 10 | func trace(name string) func() { 11 | fmt.Printf("%s entered\n", name) 12 | return func() { 13 | fmt.Printf("%s returned\n", name) 14 | } 15 | } 16 | 17 | func Test1(t *testing.T) { 18 | defer trace("Test1")() 19 | } 20 | 21 | func Test2(t *testing.T) { 22 | defer trace("Test2")() 23 | t.Parallel() 24 | } 25 | 26 | func Test3(t *testing.T) { 27 | defer trace("Test3")() 28 | } 29 | 30 | func Test5(t *testing.T) { 31 | defer trace("Test5")() 32 | t.Parallel() 33 | } 34 | 35 | func Test4(t *testing.T) { 36 | defer trace("Test4")() 37 | } 38 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel_with_subtests/go.mod: -------------------------------------------------------------------------------- 1 | module testing_in_one_package 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel_with_subtests/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_in_one_package_parallel_with_subtests/order_test.go: -------------------------------------------------------------------------------- 1 | package order_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // go test -v 9 | 10 | func trace(name string) func() { 11 | fmt.Printf("%s entered\n", name) 12 | return func() { 13 | fmt.Printf("%s returned\n", name) 14 | } 15 | } 16 | 17 | func Test1(t *testing.T) { 18 | defer trace("Test1")() 19 | 20 | t.Run("Test1_Sub1", func(t *testing.T) { 21 | defer trace("Test1_Sub1")() 22 | t.Parallel() 23 | }) 24 | 25 | t.Run("Test1_Sub2", func(t *testing.T) { 26 | defer trace("Test1_Sub2")() 27 | t.Parallel() 28 | }) 29 | } 30 | 31 | func Test2(t *testing.T) { 32 | defer trace("Test2")() 33 | t.Parallel() 34 | } 35 | 36 | func Test3(t *testing.T) { 37 | defer trace("Test3")() 38 | } 39 | 40 | func Test5(t *testing.T) { 41 | defer trace("Test5")() 42 | t.Parallel() 43 | } 44 | 45 | func Test4(t *testing.T) { 46 | defer trace("Test4")() 47 | } 48 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_leaks/go.mod: -------------------------------------------------------------------------------- 1 | module testing_leaks 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.0 7 | go.uber.org/goleak v1.3.0 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/kr/text v0.2.0 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/testing_leaks/leak_test.go: -------------------------------------------------------------------------------- 1 | package testing_leaks 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "github.com/stretchr/testify/require" 7 | "go.uber.org/goleak" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func work(result chan int) { 13 | time.Sleep(2 * time.Second) 14 | result <- 100 15 | } 16 | 17 | func Manage() error { 18 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 19 | defer cancel() 20 | 21 | result := make(chan int) 22 | go work(result) 23 | 24 | select { 25 | case <-result: 26 | return nil 27 | case <-ctx.Done(): 28 | return errors.New("error") 29 | } 30 | } 31 | 32 | func TestGoroutineLeak(t *testing.T) { 33 | defer goleak.VerifyNone(t) 34 | 35 | err := Manage() 36 | require.Error(t, err, "error") 37 | } 38 | -------------------------------------------------------------------------------- /additional/lesson_testing_and_profiling/white_box_testing/main_test.go: -------------------------------------------------------------------------------- 1 | package white_box_testing 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func ProcessNumber(input int, output chan<- int) { 9 | go func() { 10 | time.Sleep(2 * time.Second) 11 | output <- input * 2 12 | }() 13 | } 14 | 15 | func TestProcessNumber(t *testing.T) { 16 | input := 5 17 | expected := 10 18 | output := make(chan int) 19 | 20 | ProcessNumber(input, output) 21 | 22 | select { 23 | case result := <-output: 24 | if result != expected { 25 | t.Errorf("Expected %d, got %d", expected, result) 26 | } 27 | case <-time.After(3 * time.Second): 28 | t.Fatal("Test timed out") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/broken_mutex_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | unlocked = false 10 | locked = true 11 | ) 12 | 13 | type BrokenMutex struct { 14 | state bool 15 | } 16 | 17 | // Здесь есть data race и нет гарантии взаимного исключения (safety), 18 | // так как несколько горутин могут попасть совместно в критическую секцию 19 | 20 | func (m *BrokenMutex) Lock() { 21 | for m.state { 22 | // iteration by iteration... 23 | } 24 | 25 | m.state = locked 26 | } 27 | 28 | func (m *BrokenMutex) Unlock() { 29 | m.state = unlocked 30 | } 31 | 32 | const goroutinesNumber = 1000 33 | 34 | func main() { 35 | var mutex BrokenMutex 36 | wg := sync.WaitGroup{} 37 | wg.Add(goroutinesNumber) 38 | 39 | value := 0 40 | for i := 0; i < goroutinesNumber; i++ { 41 | go func() { 42 | defer wg.Done() 43 | 44 | mutex.Lock() 45 | value++ 46 | mutex.Unlock() 47 | }() 48 | } 49 | 50 | wg.Wait() 51 | 52 | fmt.Println(value) 53 | } 54 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/broken_mutex_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | unlocked = false 11 | locked = true 12 | ) 13 | 14 | type BrokenMutex struct { 15 | state atomic.Bool 16 | } 17 | 18 | // Здесь нет гарантии взаимного исключения (safety), так как несколько 19 | // горутин могут попасть совместно в критическую секцию 20 | 21 | func (m *BrokenMutex) Lock() { 22 | for m.state.Load() { 23 | // iteration by iteration... 24 | } 25 | 26 | m.state.Store(locked) 27 | } 28 | 29 | func (m *BrokenMutex) Unlock() { 30 | m.state.Store(unlocked) 31 | } 32 | 33 | const goroutinesNumber = 1000 34 | 35 | func main() { 36 | var mutex BrokenMutex 37 | wg := sync.WaitGroup{} 38 | wg.Add(goroutinesNumber) 39 | 40 | value := 0 41 | for i := 0; i < goroutinesNumber; i++ { 42 | go func() { 43 | defer wg.Done() 44 | 45 | mutex.Lock() 46 | value++ 47 | mutex.Unlock() 48 | }() 49 | } 50 | 51 | wg.Wait() 52 | 53 | fmt.Println(value) 54 | } 55 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/broken_mutex_3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | unlocked = false 11 | locked = true 12 | ) 13 | 14 | type BrokenMutex struct { 15 | want [2]atomic.Bool 16 | owner int 17 | } 18 | 19 | // Здесь нет гарантии прогресса (liveness), так как могут несколько 20 | // горутин могут помешать друг другу и попасть в livelock 21 | 22 | func (m *BrokenMutex) Lock(index int) { 23 | otherIndex := 1 - index 24 | m.want[index].Store(locked) 25 | for m.want[otherIndex].Load() { 26 | runtime.Gosched() 27 | } 28 | 29 | m.owner = index 30 | } 31 | 32 | func (m *BrokenMutex) Unlock() { 33 | m.want[m.owner].Store(unlocked) 34 | } 35 | 36 | const goroutinesNumber = 2 37 | 38 | func main() { 39 | var mutex BrokenMutex 40 | wg := sync.WaitGroup{} 41 | wg.Add(goroutinesNumber) 42 | 43 | go func() { 44 | defer wg.Done() 45 | 46 | const goroutineIdx = 0 47 | mutex.Lock(goroutineIdx) 48 | mutex.Unlock() 49 | }() 50 | 51 | go func() { 52 | defer wg.Done() 53 | 54 | const goroutineIdx = 1 55 | mutex.Lock(goroutineIdx) 56 | mutex.Unlock() 57 | }() 58 | 59 | wg.Wait() 60 | } 61 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/correct_increment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | const goroutinesNumber = 1000 9 | 10 | func main() { 11 | mutex := sync.Mutex{} 12 | wg := sync.WaitGroup{} 13 | wg.Add(goroutinesNumber) 14 | 15 | value := 0 16 | for i := 0; i < goroutinesNumber; i++ { 17 | go func() { 18 | defer wg.Done() 19 | 20 | mutex.Lock() 21 | value++ 22 | mutex.Unlock() 23 | }() 24 | } 25 | 26 | wg.Wait() 27 | 28 | fmt.Println(value) 29 | } 30 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/incorrect_increment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | const goroutinesNumber = 1000 9 | 10 | func main() { 11 | wg := sync.WaitGroup{} 12 | wg.Add(goroutinesNumber) 13 | 14 | value := 0 15 | for i := 0; i < goroutinesNumber; i++ { 16 | go func() { 17 | defer wg.Done() 18 | value++ 19 | }() 20 | } 21 | 22 | wg.Wait() 23 | 24 | fmt.Println(value) 25 | } 26 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/mutex_without_owner/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func main() { 6 | mutex := sync.Mutex{} 7 | mutex.Lock() 8 | 9 | /* 10 | wg := sync.WaitGroup{} 11 | wg.Add(1) 12 | 13 | go func() { 14 | defer wg.Done() 15 | mutex.Unlock() 16 | }() 17 | 18 | wg.Wait() 19 | */ 20 | 21 | mutex.Lock() 22 | mutex.Unlock() 23 | } 24 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/peterson_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | unlocked = false 11 | locked = true 12 | ) 13 | 14 | type Mutex struct { 15 | want [2]atomic.Bool 16 | victim atomic.Int32 17 | owner int 18 | } 19 | 20 | func (m *Mutex) Lock(index int) { 21 | m.want[index].Store(locked) 22 | m.victim.Store(int32(index)) 23 | 24 | otherIndex := 1 - index 25 | for m.want[otherIndex].Load() && m.victim.Load() == int32(index) { 26 | runtime.Gosched() 27 | } 28 | 29 | m.owner = index 30 | } 31 | 32 | func (m *Mutex) Unlock() { 33 | m.want[m.owner].Store(unlocked) 34 | } 35 | 36 | const goroutinesNumber = 2 37 | 38 | func main() { 39 | var mutex Mutex 40 | wg := sync.WaitGroup{} 41 | wg.Add(goroutinesNumber) 42 | 43 | go func() { 44 | defer wg.Done() 45 | 46 | const goroutineIdx = 0 47 | mutex.Lock(goroutineIdx) 48 | mutex.Unlock() 49 | }() 50 | 51 | go func() { 52 | defer wg.Done() 53 | 54 | const goroutineIdx = 1 55 | mutex.Lock(goroutineIdx) 56 | mutex.Unlock() 57 | }() 58 | 59 | wg.Wait() 60 | } 61 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/rw_mutex_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type RWMutex struct { 6 | notifier *sync.Cond 7 | mutex *sync.Mutex 8 | readersNumber int 9 | hasWriter bool 10 | } 11 | 12 | func NewRWMutex() *RWMutex { 13 | var mutex sync.Mutex 14 | notifier := sync.NewCond(&mutex) 15 | 16 | return &RWMutex{ 17 | mutex: &mutex, 18 | notifier: notifier, 19 | } 20 | } 21 | 22 | func (m *RWMutex) Lock() { 23 | m.mutex.Lock() 24 | defer m.mutex.Unlock() 25 | 26 | for m.hasWriter { 27 | m.notifier.Wait() 28 | } 29 | 30 | m.hasWriter = true 31 | for m.readersNumber != 0 { 32 | m.notifier.Wait() 33 | } 34 | } 35 | 36 | func (m *RWMutex) Unlock() { 37 | m.mutex.Lock() 38 | defer m.mutex.Unlock() 39 | 40 | m.hasWriter = false 41 | m.notifier.Broadcast() 42 | } 43 | 44 | func (m *RWMutex) RLock() { 45 | m.mutex.Lock() 46 | defer m.mutex.Unlock() 47 | 48 | for m.hasWriter { 49 | m.notifier.Wait() 50 | } 51 | 52 | m.readersNumber++ 53 | } 54 | 55 | func (m *RWMutex) RUnlock() { 56 | m.mutex.Lock() 57 | defer m.mutex.Unlock() 58 | 59 | m.readersNumber-- 60 | if m.readersNumber == 0 { 61 | m.notifier.Broadcast() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/spinlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | const ( 10 | unlocked = false 11 | locked = true 12 | ) 13 | 14 | type Mutex struct { 15 | state atomic.Bool 16 | } 17 | 18 | func (m *Mutex) Lock() { 19 | for !m.state.CompareAndSwap(unlocked, locked) { 20 | // итерация за итерацией... 21 | } 22 | } 23 | 24 | func (m *Mutex) Unlock() { 25 | m.state.Store(unlocked) 26 | } 27 | 28 | const goroutinesNumber = 1000 29 | 30 | func main() { 31 | var mutex Mutex 32 | wg := sync.WaitGroup{} 33 | wg.Add(goroutinesNumber) 34 | 35 | value := 0 36 | for i := 0; i < goroutinesNumber; i++ { 37 | go func() { 38 | defer wg.Done() 39 | 40 | mutex.Lock() 41 | value++ 42 | mutex.Unlock() 43 | }() 44 | } 45 | 46 | wg.Wait() 47 | 48 | fmt.Println(value) 49 | } 50 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/spinlock_combined/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | const ( 11 | unlocked = false 12 | locked = true 13 | ) 14 | 15 | const retriesNumber = 3 16 | 17 | type Mutex struct { 18 | state atomic.Bool 19 | } 20 | 21 | func (m *Mutex) Lock() { 22 | retries := retriesNumber 23 | for !m.state.CompareAndSwap(unlocked, locked) { 24 | retries-- 25 | if retries == 0 { 26 | runtime.Gosched() 27 | retries = retriesNumber 28 | } 29 | } 30 | } 31 | 32 | func (m *Mutex) Unlock() { 33 | m.state.Store(unlocked) 34 | } 35 | 36 | const goroutinesNumber = 1000 37 | 38 | func main() { 39 | var mutex Mutex 40 | wg := sync.WaitGroup{} 41 | wg.Add(goroutinesNumber) 42 | 43 | value := 0 44 | for i := 0; i < goroutinesNumber; i++ { 45 | go func() { 46 | defer wg.Done() 47 | 48 | mutex.Lock() 49 | value++ 50 | mutex.Unlock() 51 | }() 52 | } 53 | 54 | wg.Wait() 55 | 56 | fmt.Println(value) 57 | } 58 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/spinlock_with_yield/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | const ( 11 | unlocked = false 12 | locked = true 13 | ) 14 | 15 | type Mutex struct { 16 | state atomic.Bool 17 | } 18 | 19 | func (m *Mutex) Lock() { 20 | for !m.state.CompareAndSwap(unlocked, locked) { 21 | runtime.Gosched() // но горутина не перейдет в состояние ожидания 22 | } 23 | } 24 | 25 | func (m *Mutex) Unlock() { 26 | m.state.Store(unlocked) 27 | } 28 | 29 | const goroutinesNumber = 1000 30 | 31 | func main() { 32 | var mutex Mutex 33 | wg := sync.WaitGroup{} 34 | wg.Add(goroutinesNumber) 35 | 36 | value := 0 37 | for i := 0; i < goroutinesNumber; i++ { 38 | go func() { 39 | defer wg.Done() 40 | 41 | mutex.Lock() 42 | value++ 43 | mutex.Unlock() 44 | }() 45 | } 46 | 47 | wg.Wait() 48 | 49 | fmt.Println(value) 50 | } 51 | -------------------------------------------------------------------------------- /additional/open_lessons/mutex_internals/ticket_lock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | "sync/atomic" 8 | ) 9 | 10 | type Mutex struct { 11 | ownerTicket atomic.Int64 12 | nextFreeTicket atomic.Int64 13 | } 14 | 15 | func (m *Mutex) Lock() { 16 | ticket := m.nextFreeTicket.Add(1) 17 | for m.ownerTicket.Load() != ticket-1 { 18 | runtime.Gosched() 19 | } 20 | } 21 | 22 | func (m *Mutex) Unlock() { 23 | m.ownerTicket.Add(1) 24 | } 25 | 26 | const goroutinesNumber = 1000 27 | 28 | func main() { 29 | var mutex Mutex 30 | wg := sync.WaitGroup{} 31 | wg.Add(goroutinesNumber) 32 | 33 | value := 0 34 | for i := 0; i < goroutinesNumber; i++ { 35 | go func() { 36 | defer wg.Done() 37 | 38 | mutex.Lock() 39 | value++ 40 | mutex.Unlock() 41 | }() 42 | } 43 | 44 | wg.Wait() 45 | 46 | fmt.Println(value) 47 | } 48 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | ./spider-cli 2 | ./spider-server 3 | ./spider.log 4 | 5 | ./local_test/* -------------------------------------------------------------------------------- /database/Makefile: -------------------------------------------------------------------------------- 1 | SERVER_APP_NAME=spider-server 2 | CLI_APP_NAME=spider-cli 3 | 4 | build-server: 5 | go build -o ${SERVER_APP_NAME} cmd/server/main.go 6 | 7 | build-cli: 8 | go build -o ${CLI_APP_NAME} cmd/cli/main.go 9 | 10 | run-server: build-server 11 | ./${SERVER_APP_NAME} 12 | 13 | run-server-with-config: build-server 14 | CONFIG_FILE_NAME=config.yml ./${SERVER_APP_NAME} 15 | 16 | run-cli: build-cli 17 | ./${CLI_APP_NAME} $(ARGS) 18 | 19 | run_unit_test: 20 | go test ./internal/... 21 | 22 | run_e2e_test: build-server 23 | go test ./test/... 24 | 25 | run_test_coverage: 26 | go test ./... -coverprofile=coverage.out -------------------------------------------------------------------------------- /database/README.md: -------------------------------------------------------------------------------- 1 | # spider 2 | 3 | ### Introduction 4 | Spider - is in-memory key-value database with support for asynchronous replication (physic) and WAL (Write-Ahead Logging). Implented only like reference of the final project for course Concurrency in Go. 5 | 6 | ### Server launch 7 | 8 | For local start of the server, you can use the following instructions: 9 | 10 | ```bash 11 | make run-server 12 | ``` 13 | 14 | ### Server configuration 15 | 16 | By default you don't need to use any parameters for start database, but implemented support of different configation paramaters with YAML format, for example: 17 | 18 | ```yaml 19 | engine: 20 | type: "in_memory" 21 | partitions_number: 8 22 | wal: 23 | flushing_batch_length: 100 24 | flushing_batch_timeout: "10ms" 25 | max_segment_size: "10MB" 26 | data_directory: "/data/spider/wal" 27 | replication: 28 | replica_type: "slave" 29 | master_address: "127.0.0.1:3232" 30 | sync_interval: "1s" 31 | network: 32 | address: "127.0.0.1:3223" 33 | max_connections: 100 34 | max_message_size: "4KB" 35 | idle_timeout: 5m 36 | logging: 37 | level: "info" 38 | output: "/log/output.log" 39 | ``` 40 | 41 | For launch with configuration you need to use the following instructions: 42 | 43 | ```bash 44 | make run-server CONFIG_FILE_NAME=some_address 45 | ``` 46 | 47 | ### CLI launch 48 | 49 | For local start of the CLI, you can use the following instructions: 50 | 51 | ```bash 52 | make run-cli 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /database/cmd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "spider/internal/configuration" 12 | "spider/internal/initialization" 13 | ) 14 | 15 | var ConfigFileName = os.Getenv("CONFIG_FILE_NAME") 16 | 17 | func main() { 18 | ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) 19 | defer stop() 20 | 21 | cfg := &configuration.Config{} 22 | if ConfigFileName != "" { 23 | data, err := os.ReadFile(ConfigFileName) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | reader := bytes.NewReader(data) 29 | cfg, err = configuration.Load(reader) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | } 34 | 35 | initializer, err := initialization.NewInitializer(cfg) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | if err = initializer.StartDatabase(ctx); err != nil { 41 | log.Fatal(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /database/config.yml: -------------------------------------------------------------------------------- 1 | engine: 2 | type: "in_memory" 3 | partitions_number: 8 4 | wal: 5 | flushing_batch_length: 100 6 | flushing_batch_timeout: "10ms" 7 | max_segment_size: "1KB" 8 | data_directory: "./wal" 9 | network: 10 | address: "127.0.0.1:3223" 11 | max_connections: 100 12 | max_message_size: "4KB" 13 | idle_timeout: 5m 14 | logging: 15 | level: "debug" 16 | output: "./spider.log" -------------------------------------------------------------------------------- /database/go.mod: -------------------------------------------------------------------------------- 1 | module spider 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/golang/mock v1.6.0 7 | github.com/stretchr/testify v1.8.4 8 | go.uber.org/zap v1.24.0 9 | golang.org/x/sync v0.6.0 10 | gopkg.in/yaml.v3 v3.0.1 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | go.uber.org/atomic v1.11.0 // indirect 17 | go.uber.org/multierr v1.11.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /database/internal/common/contexts.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "context" 4 | 5 | type TxID string 6 | 7 | func ContextWithTxID(parent context.Context, value int64) context.Context { 8 | return context.WithValue(parent, TxID("tx"), value) 9 | } 10 | 11 | func GetTxIDFromContext(ctx context.Context) int64 { 12 | return ctx.Value(TxID("tx")).(int64) 13 | } 14 | -------------------------------------------------------------------------------- /database/internal/common/size_parser.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "errors" 4 | 5 | func ParseSize(text string) (int, error) { 6 | if len(text) == 0 || text[0] < '0' || text[0] > '9' { 7 | return 0, errors.New("incorrect size") 8 | } 9 | 10 | idx := 0 11 | size := 0 12 | for idx < len(text) && text[idx] >= '0' && text[idx] <= '9' { 13 | number := int(text[idx] - '0') 14 | size = size*10 + number 15 | idx++ 16 | } 17 | 18 | parameter := text[idx:] 19 | switch parameter { 20 | case "GB", "Gb", "gb": 21 | return size << 30, nil 22 | case "MB", "Mb", "mb": 23 | return size << 20, nil 24 | case "KB", "Kb", "kb": 25 | return size << 10, nil 26 | case "B", "b", "": 27 | return size, nil 28 | default: 29 | return 0, errors.New("incorrect size") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/internal/concurrency/future.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | type FutureError = Future[error] 4 | 5 | type Future[T any] struct { 6 | result <-chan T 7 | } 8 | 9 | func NewFuture[T any](result <-chan T) Future[T] { 10 | return Future[T]{ 11 | result: result, 12 | } 13 | } 14 | 15 | func (f *Future[T]) Get() T { 16 | return <-f.result 17 | } 18 | -------------------------------------------------------------------------------- /database/internal/concurrency/lock.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import "sync" 4 | 5 | func WithLock(mutex sync.Locker, action func()) { 6 | if action == nil { 7 | return 8 | } 9 | 10 | mutex.Lock() 11 | defer mutex.Unlock() 12 | 13 | action() 14 | } 15 | -------------------------------------------------------------------------------- /database/internal/concurrency/promise.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | type PromiseError = Promise[error] 4 | 5 | type Promise[T any] struct { 6 | result chan T 7 | promised bool 8 | } 9 | 10 | func NewPromise[T any]() Promise[T] { 11 | return Promise[T]{ 12 | result: make(chan T, 1), 13 | } 14 | } 15 | 16 | // Set don't use with any goroutines 17 | func (p *Promise[T]) Set(value T) { 18 | if p.promised { 19 | return 20 | } 21 | 22 | p.promised = true 23 | p.result <- value 24 | close(p.result) 25 | } 26 | 27 | func (p *Promise[T]) GetFuture() Future[T] { 28 | return NewFuture(p.result) 29 | } 30 | -------------------------------------------------------------------------------- /database/internal/concurrency/semaphore.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | type Semaphore struct { 4 | tickets chan struct{} 5 | } 6 | 7 | func NewSemaphore(ticketsNumber int) Semaphore { 8 | return Semaphore{ 9 | tickets: make(chan struct{}, ticketsNumber), 10 | } 11 | } 12 | 13 | func (s *Semaphore) Acquire() { 14 | if s == nil || s.tickets == nil { 15 | return 16 | } 17 | 18 | s.tickets <- struct{}{} 19 | } 20 | 21 | func (s *Semaphore) Release() { 22 | if s == nil || s.tickets == nil { 23 | return 24 | } 25 | 26 | <-s.tickets 27 | } 28 | 29 | func (s *Semaphore) WithAcquire(action func()) { 30 | if s == nil || action == nil { 31 | return 32 | } 33 | 34 | s.Acquire() 35 | action() 36 | s.Release() 37 | } 38 | -------------------------------------------------------------------------------- /database/internal/database/compute/command.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | const ( 4 | UnknownCommandID = iota 5 | SetCommandID 6 | GetCommandID 7 | DelCommandID 8 | ) 9 | 10 | var ( 11 | UnknownCommand = "UNKNOWN" 12 | SetCommand = "SET" 13 | GetCommand = "GET" 14 | DelCommand = "DEL" 15 | ) 16 | 17 | var namesToId = map[string]int{ 18 | SetCommand: SetCommandID, 19 | GetCommand: GetCommandID, 20 | DelCommand: DelCommandID, 21 | } 22 | 23 | func commandNameToCommandID(command string) int { 24 | status, found := namesToId[command] 25 | if !found { 26 | return UnknownCommandID 27 | } 28 | 29 | return status 30 | } 31 | 32 | const ( 33 | setCommandArgumentsNumber = 2 34 | getCommandArgumentsNumber = 1 35 | delCommandArgumentsNumber = 1 36 | ) 37 | 38 | var argumentsNumber = map[int]int{ 39 | SetCommandID: setCommandArgumentsNumber, 40 | GetCommandID: getCommandArgumentsNumber, 41 | DelCommandID: delCommandArgumentsNumber, 42 | } 43 | 44 | func commandArgumentsNumber(commandID int) int { 45 | return argumentsNumber[commandID] 46 | } 47 | -------------------------------------------------------------------------------- /database/internal/database/compute/command_test.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCommandNameToCommandID(t *testing.T) { 10 | t.Parallel() 11 | 12 | require.Equal(t, SetCommandID, commandNameToCommandID("SET")) 13 | require.Equal(t, GetCommandID, commandNameToCommandID("GET")) 14 | require.Equal(t, DelCommandID, commandNameToCommandID("DEL")) 15 | require.Equal(t, UnknownCommandID, commandNameToCommandID("TRUNCATE")) 16 | } 17 | 18 | func TestCommandArgumentsNumber(t *testing.T) { 19 | t.Parallel() 20 | 21 | require.Equal(t, setCommandArgumentsNumber, commandArgumentsNumber(SetCommandID)) 22 | require.Equal(t, getCommandArgumentsNumber, commandArgumentsNumber(GetCommandID)) 23 | require.Equal(t, delCommandArgumentsNumber, commandArgumentsNumber(DelCommandID)) 24 | require.Equal(t, 0, commandNameToCommandID("TRUNCATE")) 25 | } 26 | -------------------------------------------------------------------------------- /database/internal/database/compute/compute.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "go.uber.org/zap" 8 | ) 9 | 10 | var ( 11 | errInvalidQuery = errors.New("empty query") 12 | errInvalidCommand = errors.New("invalid command") 13 | errInvalidArguments = errors.New("invalid arguments") 14 | ) 15 | 16 | type Compute struct { 17 | logger *zap.Logger 18 | } 19 | 20 | func NewCompute(logger *zap.Logger) (*Compute, error) { 21 | if logger == nil { 22 | return nil, errors.New("logger is invalid") 23 | } 24 | 25 | return &Compute{ 26 | logger: logger, 27 | }, nil 28 | } 29 | 30 | func (d *Compute) Parse(queryStr string) (Query, error) { 31 | tokens := strings.Fields(queryStr) 32 | if len(tokens) == 0 { 33 | d.logger.Debug("empty tokens", zap.String("query", queryStr)) 34 | return Query{}, errInvalidQuery 35 | } 36 | 37 | command := tokens[0] 38 | commandID := commandNameToCommandID(command) 39 | if commandID == UnknownCommandID { 40 | d.logger.Debug("invalid command", zap.String("query", queryStr)) 41 | return Query{}, errInvalidCommand 42 | } 43 | 44 | query := NewQuery(commandID, tokens[1:]) 45 | argumentsNumber := commandArgumentsNumber(commandID) 46 | if len(query.Arguments()) != argumentsNumber { 47 | d.logger.Debug("invalid arguments for query", zap.String("query", queryStr)) 48 | return Query{}, errInvalidArguments 49 | } 50 | 51 | return query, nil 52 | } 53 | -------------------------------------------------------------------------------- /database/internal/database/compute/query.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | type Query struct { 4 | commandID int 5 | arguments []string 6 | } 7 | 8 | func NewQuery(commandID int, arguments []string) Query { 9 | return Query{ 10 | commandID: commandID, 11 | arguments: arguments, 12 | } 13 | } 14 | 15 | func (c *Query) CommandID() int { 16 | return c.commandID 17 | } 18 | 19 | func (c *Query) Arguments() []string { 20 | return c.arguments 21 | } 22 | -------------------------------------------------------------------------------- /database/internal/database/compute/query_test.go: -------------------------------------------------------------------------------- 1 | package compute 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestQuery(t *testing.T) { 10 | query := NewQuery(GetCommandID, []string{"GET", "key"}) 11 | require.Equal(t, GetCommandID, query.CommandID()) 12 | require.True(t, reflect.DeepEqual([]string{"GET", "key"}, query.Arguments())) 13 | } 14 | -------------------------------------------------------------------------------- /database/internal/database/filesystem/segment.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "time" 7 | ) 8 | 9 | var now = time.Now 10 | 11 | type Segment struct { 12 | file *os.File 13 | directory string 14 | 15 | segmentSize int 16 | maxSegmentSize int 17 | } 18 | 19 | func NewSegment(directory string, maxSegmentSize int) *Segment { 20 | return &Segment{ 21 | directory: directory, 22 | maxSegmentSize: maxSegmentSize, 23 | } 24 | } 25 | 26 | func (s *Segment) Write(data []byte) error { 27 | if s.file == nil || s.segmentSize >= s.maxSegmentSize { 28 | if err := s.rotateSegment(); err != nil { 29 | return fmt.Errorf("failed to rotate segment file: %w", err) 30 | } 31 | } 32 | 33 | writtenBytes, err := WriteFile(s.file, data) 34 | if err != nil { 35 | return fmt.Errorf("failed to write data to segment file: %w", err) 36 | } 37 | 38 | s.segmentSize += writtenBytes 39 | return nil 40 | } 41 | 42 | func (s *Segment) rotateSegment() error { 43 | segmentName := fmt.Sprintf("%s/wal_%d.log", s.directory, now().UnixMilli()) 44 | file, err := CreateFile(segmentName) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | s.file = file 50 | s.segmentSize = 0 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /database/internal/database/filesystem/segment_test.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestSegmentWrite(t *testing.T) { 13 | t.Parallel() 14 | 15 | const testWALDirectory = "temp_test_data" 16 | err := os.Mkdir(testWALDirectory, os.ModePerm) 17 | require.NoError(t, err) 18 | 19 | defer func() { 20 | err := os.RemoveAll(testWALDirectory) 21 | require.NoError(t, err) 22 | }() 23 | 24 | const maxSegmentSize = 10 25 | segment := NewSegment(testWALDirectory, maxSegmentSize) 26 | 27 | now = func() time.Time { 28 | return time.Unix(1, 0) 29 | } 30 | 31 | err = segment.Write([]byte("aaaaa")) 32 | require.NoError(t, err) 33 | err = segment.Write([]byte("bbbbb")) 34 | require.NoError(t, err) 35 | 36 | now = func() time.Time { 37 | return time.Unix(2, 0) 38 | } 39 | 40 | err = segment.Write([]byte("ccccc")) 41 | require.NoError(t, err) 42 | 43 | stat, err := os.Stat(testWALDirectory + "/wal_1000.log") 44 | require.NoError(t, err) 45 | assert.Equal(t, int64(10), stat.Size()) 46 | 47 | stat, err = os.Stat(testWALDirectory + "/wal_2000.log") 48 | require.NoError(t, err) 49 | assert.Equal(t, int64(5), stat.Size()) 50 | } 51 | -------------------------------------------------------------------------------- /database/internal/database/filesystem/segments_directory.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | type SegmentsDirectory struct { 9 | directory string 10 | } 11 | 12 | func NewSegmentsDirectory(directory string) *SegmentsDirectory { 13 | return &SegmentsDirectory{ 14 | directory: directory, 15 | } 16 | } 17 | 18 | func (d *SegmentsDirectory) ForEach(action func([]byte) error) error { 19 | files, err := os.ReadDir(d.directory) 20 | if err != nil { 21 | // TODO: need to create a directory if it is missing 22 | return fmt.Errorf("failed to scan directory with segments: %w", err) 23 | } 24 | 25 | for _, file := range files { 26 | if file.IsDir() { 27 | continue 28 | } 29 | 30 | filename := fmt.Sprintf("%s/%s", d.directory, file.Name()) 31 | data, err := os.ReadFile(filename) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | if err := action(data); err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /database/internal/database/filesystem/segments_directory_test.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestSegmentsDirectoryForEach(t *testing.T) { 12 | t.Parallel() 13 | 14 | segmentsCount := 0 15 | expectedSegmentsCount := 3 16 | 17 | directory := NewSegmentsDirectory("test_data") 18 | err := directory.ForEach(func(data []byte) error { 19 | assert.True(t, len(data) != 0) 20 | segmentsCount++ 21 | return nil 22 | }) 23 | 24 | require.NoError(t, err) 25 | assert.Equal(t, expectedSegmentsCount, segmentsCount) 26 | } 27 | 28 | func TestSegmentsDirectoryForEachWithBreak(t *testing.T) { 29 | t.Parallel() 30 | 31 | directory := NewSegmentsDirectory("test_data") 32 | err := directory.ForEach(func([]byte) error { 33 | return errors.New("error") 34 | }) 35 | 36 | assert.Error(t, err, "error") 37 | } 38 | -------------------------------------------------------------------------------- /database/internal/database/filesystem/test_data/wal_1000.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/database/internal/database/filesystem/test_data/wal_1000.log -------------------------------------------------------------------------------- /database/internal/database/filesystem/test_data/wal_2000.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/database/internal/database/filesystem/test_data/wal_2000.log -------------------------------------------------------------------------------- /database/internal/database/filesystem/test_data/wal_3000.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/database/internal/database/filesystem/test_data/wal_3000.log -------------------------------------------------------------------------------- /database/internal/database/filesystem/utils_test.go: -------------------------------------------------------------------------------- 1 | package filesystem 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestSegmentUpperBound(t *testing.T) { 10 | t.Parallel() 11 | 12 | filename, err := SegmentNext("test_data", "wal_0.log") 13 | require.NoError(t, err) 14 | require.Equal(t, "wal_1000.log", filename) 15 | 16 | filename, err = SegmentNext("test_data", "wal_1000.log") 17 | require.NoError(t, err) 18 | require.Equal(t, "wal_2000.log", filename) 19 | 20 | filename, err = SegmentNext("test_data", "wal_2000.log") 21 | require.NoError(t, err) 22 | require.Equal(t, "", filename) 23 | 24 | filename, err = SegmentNext("test_data", "wal_3000.log") 25 | require.NoError(t, err) 26 | require.Equal(t, "", filename) 27 | } 28 | 29 | func TestSegmentLast(t *testing.T) { 30 | t.Parallel() 31 | 32 | filename, err := SegmentLast("test_data") 33 | require.NoError(t, err) 34 | require.Equal(t, "wal_3000.log", filename) 35 | } 36 | -------------------------------------------------------------------------------- /database/internal/database/storage/engine/in_memory/engine_options.go: -------------------------------------------------------------------------------- 1 | package in_memory 2 | 3 | type EngineOption func(*Engine) 4 | 5 | func WithPartitions(partitionsNumber uint) EngineOption { 6 | return func(engine *Engine) { 7 | engine.partitions = make([]*HashTable, partitionsNumber) 8 | for i := 0; i < int(partitionsNumber); i++ { 9 | engine.partitions[i] = NewHashTable() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /database/internal/database/storage/engine/in_memory/engine_options_test.go: -------------------------------------------------------------------------------- 1 | package in_memory 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWithPartitions(t *testing.T) { 10 | t.Parallel() 11 | 12 | partitionNumber := 10 13 | option := WithPartitions(uint(partitionNumber)) 14 | 15 | var engine Engine 16 | option(&engine) 17 | 18 | assert.Equal(t, partitionNumber, len(engine.partitions)) 19 | } 20 | -------------------------------------------------------------------------------- /database/internal/database/storage/engine/in_memory/hash_table.go: -------------------------------------------------------------------------------- 1 | package in_memory 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type HashTable struct { 8 | mutex sync.RWMutex 9 | data map[string]string 10 | } 11 | 12 | func NewHashTable() *HashTable { 13 | return &HashTable{ 14 | data: make(map[string]string), 15 | } 16 | } 17 | 18 | func (s *HashTable) Set(key, value string) { 19 | s.mutex.Lock() 20 | defer s.mutex.Unlock() 21 | 22 | s.data[key] = value 23 | } 24 | 25 | func (s *HashTable) Get(key string) (string, bool) { 26 | s.mutex.RLock() 27 | defer s.mutex.RUnlock() 28 | 29 | value, found := s.data[key] 30 | return value, found 31 | } 32 | 33 | func (s *HashTable) Del(key string) { 34 | s.mutex.Lock() 35 | defer s.mutex.Unlock() 36 | 37 | delete(s.data, key) 38 | } 39 | -------------------------------------------------------------------------------- /database/internal/database/storage/id_generator.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "math" 5 | "sync/atomic" 6 | ) 7 | 8 | type IDGenerator struct { 9 | counter atomic.Int64 10 | } 11 | 12 | func NewIDGenerator(previousID int64) *IDGenerator { 13 | generator := &IDGenerator{} 14 | generator.counter.Store(previousID) 15 | return generator 16 | } 17 | 18 | func (g *IDGenerator) Generate() int64 { 19 | g.counter.CompareAndSwap(math.MaxInt64, 0) 20 | return g.counter.Add(1) 21 | } 22 | -------------------------------------------------------------------------------- /database/internal/database/storage/id_generator_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "math" 5 | "sync" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestIDGeneratorCreatingWithoutPreviousID(t *testing.T) { 12 | t.Parallel() 13 | 14 | generator := NewIDGenerator(0) 15 | assert.Equal(t, int64(0), generator.counter.Load()) 16 | } 17 | 18 | func TestIDGeneratorCreatingWithPreviousID(t *testing.T) { 19 | t.Parallel() 20 | 21 | generator := NewIDGenerator(1000) 22 | assert.Equal(t, int64(1000), generator.counter.Load()) 23 | } 24 | 25 | func TestGenerateID(t *testing.T) { 26 | t.Parallel() 27 | 28 | generator := NewIDGenerator(0) 29 | 30 | goroutinesNumber := 999 31 | wg := sync.WaitGroup{} 32 | wg.Add(goroutinesNumber) 33 | 34 | for i := 0; i < goroutinesNumber; i++ { 35 | go func() { 36 | defer wg.Done() 37 | _ = generator.Generate() 38 | }() 39 | } 40 | 41 | wg.Wait() 42 | 43 | nextID := generator.Generate() 44 | expectedID := goroutinesNumber + 1 45 | assert.Equal(t, int64(expectedID), nextID) 46 | } 47 | 48 | func TestGenerateIDOverflow(t *testing.T) { 49 | t.Parallel() 50 | 51 | generator := NewIDGenerator(math.MaxInt64) 52 | 53 | nextID := generator.Generate() 54 | assert.Equal(t, int64(1), nextID) 55 | } 56 | -------------------------------------------------------------------------------- /database/internal/database/storage/replication/protocol.go: -------------------------------------------------------------------------------- 1 | package replication 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | ) 8 | 9 | type Request struct { 10 | LastSegmentName string 11 | } 12 | 13 | func NewRequest(lastSegmentName string) Request { 14 | return Request{ 15 | LastSegmentName: lastSegmentName, 16 | } 17 | } 18 | 19 | type Response struct { 20 | Succeed bool 21 | SegmentName string 22 | SegmentData []byte 23 | } 24 | 25 | func NewResponse(succeed bool, segmentName string, segmentData []byte) Response { 26 | return Response{ 27 | Succeed: succeed, 28 | SegmentName: segmentName, 29 | SegmentData: segmentData, 30 | } 31 | } 32 | 33 | func Encode[ProtocolObject Request | Response](object *ProtocolObject) ([]byte, error) { 34 | var buffer bytes.Buffer 35 | encoder := gob.NewEncoder(&buffer) 36 | if err := encoder.Encode(object); err != nil { 37 | return nil, fmt.Errorf("failed to encode object: %w", err) 38 | } 39 | 40 | return buffer.Bytes(), nil 41 | } 42 | 43 | func Decode[ProtocolObject Request | Response](object *ProtocolObject, data []byte) error { 44 | buffer := bytes.NewBuffer(data) 45 | decoder := gob.NewDecoder(buffer) 46 | if err := decoder.Decode(&object); err != nil { 47 | return fmt.Errorf("failed to decode object: %w", err) 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /database/internal/database/storage/replication/protocol_test.go: -------------------------------------------------------------------------------- 1 | package replication 2 | 3 | import ( 4 | "github.com/stretchr/testify/require" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestRequest(t *testing.T) { 10 | t.Parallel() 11 | 12 | lastSegmentName := "wal_1000.log" 13 | initialRequest := NewRequest(lastSegmentName) 14 | data, err := Encode(&initialRequest) 15 | require.NoError(t, err) 16 | require.NotNil(t, data) 17 | 18 | var decodedRequest Request 19 | err = Decode(&decodedRequest, data) 20 | require.NoError(t, err) 21 | 22 | require.Equal(t, initialRequest, decodedRequest) 23 | } 24 | 25 | func TestResponse(t *testing.T) { 26 | t.Parallel() 27 | 28 | succeed := true 29 | segmentName := "wal_1000.log" 30 | segmentData := []byte{'s', 'y', 'n', 'c'} 31 | initialResponse := NewResponse(succeed, segmentName, segmentData) 32 | data, err := Encode(&initialResponse) 33 | require.NoError(t, err) 34 | require.NotNil(t, data) 35 | 36 | var decodedResponse Response 37 | err = Decode(&decodedResponse, data) 38 | require.NoError(t, err) 39 | 40 | require.True(t, reflect.DeepEqual(initialResponse, decodedResponse)) 41 | } 42 | -------------------------------------------------------------------------------- /database/internal/database/storage/storage_options.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "spider/internal/database/storage/wal" 4 | 5 | type StorageOption func(*Storage) 6 | 7 | func WithReplicationStream(stream <-chan []wal.Log) StorageOption { 8 | return func(storage *Storage) { 9 | storage.stream = stream 10 | } 11 | } 12 | 13 | func WithWAL(wal WAL) StorageOption { 14 | return func(storage *Storage) { 15 | storage.wal = wal 16 | } 17 | } 18 | 19 | func WithReplication(replica Replica) StorageOption { 20 | return func(storage *Storage) { 21 | storage.replica = replica 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/internal/database/storage/storage_options_test.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/golang/mock/gomock" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "spider/internal/database/storage/wal" 10 | ) 11 | 12 | func TestWithReplicationStream(t *testing.T) { 13 | t.Parallel() 14 | 15 | stream := make(<-chan []wal.Log) 16 | option := WithReplicationStream(stream) 17 | 18 | var storage Storage 19 | option(&storage) 20 | 21 | assert.Equal(t, stream, storage.stream) 22 | } 23 | 24 | func TestWithWAL(t *testing.T) { 25 | t.Parallel() 26 | 27 | ctrl := gomock.NewController(t) 28 | wal := NewMockWAL(ctrl) 29 | option := WithWAL(wal) 30 | 31 | var storage Storage 32 | option(&storage) 33 | 34 | assert.Equal(t, wal, storage.wal) 35 | } 36 | 37 | func TestWithReplication(t *testing.T) { 38 | t.Parallel() 39 | 40 | ctrl := gomock.NewController(t) 41 | replica := NewMockReplica(ctrl) 42 | option := WithReplication(replica) 43 | 44 | var storage Storage 45 | option(&storage) 46 | 47 | assert.Equal(t, replica, storage.replica) 48 | } 49 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/log.go: -------------------------------------------------------------------------------- 1 | package wal 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | ) 7 | 8 | type Log struct { 9 | LSN int64 10 | CommandID int 11 | Arguments []string 12 | } 13 | 14 | func (l *Log) Encode(buffer *bytes.Buffer) error { 15 | encoder := gob.NewEncoder(buffer) 16 | return encoder.Encode(*l) 17 | } 18 | 19 | func (l *Log) Decode(buffer *bytes.Buffer) error { 20 | decoder := gob.NewDecoder(buffer) 21 | return decoder.Decode(l) 22 | } 23 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/logs_reader.go: -------------------------------------------------------------------------------- 1 | package wal 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "sort" 8 | ) 9 | 10 | type segmentsDirectory interface { 11 | ForEach(func([]byte) error) error 12 | } 13 | 14 | type LogsReader struct { 15 | segmentsDirectory segmentsDirectory 16 | } 17 | 18 | func NewLogsReader(segmentsDirectory segmentsDirectory) (*LogsReader, error) { 19 | if segmentsDirectory == nil { 20 | return nil, errors.New("segments directory is invalid") 21 | } 22 | 23 | return &LogsReader{ 24 | segmentsDirectory: segmentsDirectory, 25 | }, nil 26 | } 27 | 28 | func (r *LogsReader) Read() ([]Log, error) { 29 | var logs []Log 30 | err := r.segmentsDirectory.ForEach(func(data []byte) error { 31 | var err error 32 | logs, err = r.readSegment(logs, data) 33 | return err 34 | }) 35 | 36 | if err != nil { 37 | return nil, fmt.Errorf("failed to read segments: %w", err) 38 | } 39 | 40 | // TODO: need to chech invariant for sorting 41 | sort.Slice(logs, func(i, j int) bool { 42 | return logs[i].LSN < logs[j].LSN 43 | }) 44 | 45 | return logs, nil 46 | } 47 | 48 | func (r *LogsReader) readSegment(logs []Log, data []byte) ([]Log, error) { 49 | buffer := bytes.NewBuffer(data) 50 | for buffer.Len() > 0 { 51 | var log Log 52 | if err := log.Decode(buffer); err != nil { 53 | return nil, fmt.Errorf("failed to parse logs data: %w", err) 54 | } 55 | 56 | logs = append(logs, log) 57 | } 58 | 59 | return logs, nil 60 | } 61 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/logs_writer.go: -------------------------------------------------------------------------------- 1 | package wal 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "go.uber.org/zap" 8 | ) 9 | 10 | type segment interface { 11 | Write([]byte) error 12 | } 13 | 14 | type LogsWriter struct { 15 | segment segment 16 | logger *zap.Logger 17 | } 18 | 19 | func NewLogsWriter(segment segment, logger *zap.Logger) (*LogsWriter, error) { 20 | if segment == nil { 21 | return nil, errors.New("segment is invalid") 22 | } 23 | if logger == nil { 24 | return nil, errors.New("logger is invalid") 25 | } 26 | 27 | return &LogsWriter{ 28 | segment: segment, 29 | logger: logger, 30 | }, nil 31 | } 32 | 33 | func (w *LogsWriter) Write(requests []WriteRequest) { 34 | var buffer bytes.Buffer 35 | for idx := range requests { 36 | log := requests[idx].Log() 37 | if err := log.Encode(&buffer); err != nil { 38 | w.logger.Warn("failed to encode logs data", zap.Error(err)) 39 | w.acknowledgeWrite(requests, err) 40 | return 41 | } 42 | } 43 | 44 | err := w.segment.Write(buffer.Bytes()) 45 | if err != nil { 46 | w.logger.Warn("failed to write logs data", zap.Error(err)) 47 | } 48 | 49 | w.acknowledgeWrite(requests, err) 50 | } 51 | 52 | func (w *LogsWriter) acknowledgeWrite(requests []WriteRequest, err error) { 53 | for idx := range requests { 54 | requests[idx].SetResponse(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/logs_writer_mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: logs_writer.go 3 | 4 | // Package wal is a generated GoMock package. 5 | package wal 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockwalSegment is a mock of walSegment interface. 14 | type MockwalSegment struct { 15 | ctrl *gomock.Controller 16 | recorder *MockwalSegmentMockRecorder 17 | } 18 | 19 | // MockwalSegmentMockRecorder is the mock recorder for MockwalSegment. 20 | type MockwalSegmentMockRecorder struct { 21 | mock *MockwalSegment 22 | } 23 | 24 | // NewMockwalSegment creates a new mock instance. 25 | func NewMockwalSegment(ctrl *gomock.Controller) *MockwalSegment { 26 | mock := &MockwalSegment{ctrl: ctrl} 27 | mock.recorder = &MockwalSegmentMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockwalSegment) EXPECT() *MockwalSegmentMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Write mocks base method. 37 | func (m *MockwalSegment) Write(arg0 []byte) error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Write", arg0) 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // Write indicates an expected call of Write. 45 | func (mr *MockwalSegmentMockRecorder) Write(arg0 interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockwalSegment)(nil).Write), arg0) 48 | } 49 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/write_request.go: -------------------------------------------------------------------------------- 1 | package wal 2 | 3 | import "spider/internal/concurrency" 4 | 5 | type WriteRequest struct { 6 | log Log 7 | promise concurrency.PromiseError 8 | } 9 | 10 | func NewWriteRequest(lsn int64, commandID int, args []string) WriteRequest { 11 | return WriteRequest{ 12 | log: Log{ 13 | LSN: lsn, 14 | CommandID: commandID, 15 | Arguments: args, 16 | }, 17 | promise: concurrency.NewPromise[error](), 18 | } 19 | } 20 | 21 | func (l *WriteRequest) Log() Log { 22 | return l.log 23 | } 24 | 25 | func (l *WriteRequest) SetResponse(err error) { 26 | l.promise.Set(err) 27 | } 28 | 29 | func (l *WriteRequest) FutureResponse() concurrency.FutureError { 30 | return l.promise.GetFuture() 31 | } 32 | -------------------------------------------------------------------------------- /database/internal/database/storage/wal/write_request_test.go: -------------------------------------------------------------------------------- 1 | package wal 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | "spider/internal/database/compute" 11 | ) 12 | 13 | func TestNewWriteRequest(t *testing.T) { 14 | t.Parallel() 15 | 16 | lsn := int64(100) 17 | commandID := compute.GetCommandID 18 | argumnets := []string{"key"} 19 | 20 | request := NewWriteRequest(lsn, compute.GetCommandID, []string{"key"}) 21 | assert.Equal(t, lsn, request.log.LSN) 22 | assert.Equal(t, commandID, request.log.CommandID) 23 | assert.True(t, reflect.DeepEqual(argumnets, request.log.Arguments)) 24 | } 25 | 26 | func TestWriteRequestWithError(t *testing.T) { 27 | t.Parallel() 28 | 29 | request := NewWriteRequest(100, compute.GetCommandID, []string{"key"}) 30 | future := request.FutureResponse() 31 | 32 | go func() { 33 | request.SetResponse(errors.New("error")) 34 | }() 35 | 36 | err := future.Get() 37 | assert.Error(t, err, "error") 38 | } 39 | 40 | func TestWriteRequest(t *testing.T) { 41 | t.Parallel() 42 | 43 | request := NewWriteRequest(100, compute.GetCommandID, []string{"key"}) 44 | future := request.FutureResponse() 45 | 46 | go func() { 47 | request.SetResponse(nil) 48 | }() 49 | 50 | err := future.Get() 51 | assert.NoError(t, err) 52 | } 53 | -------------------------------------------------------------------------------- /database/internal/initialization/engine_initializer.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "errors" 5 | 6 | "go.uber.org/zap" 7 | 8 | "spider/internal/configuration" 9 | "spider/internal/database/storage/engine/in_memory" 10 | ) 11 | 12 | func CreateEngine(cfg *configuration.EngineConfig, logger *zap.Logger) (*in_memory.Engine, error) { 13 | if logger == nil { 14 | return nil, errors.New("logger is invalid") 15 | } 16 | 17 | if cfg == nil { 18 | return in_memory.NewEngine(logger) 19 | } 20 | 21 | if cfg.Type != "" { 22 | supportedTypes := map[string]struct{}{ 23 | "in_memory": {}, 24 | } 25 | 26 | if _, found := supportedTypes[cfg.Type]; !found { 27 | return nil, errors.New("engine type is incorrect") 28 | } 29 | } 30 | 31 | var options []in_memory.EngineOption 32 | if cfg.PartitionsNumber != 0 { 33 | options = append(options, in_memory.WithPartitions(cfg.PartitionsNumber)) 34 | } 35 | 36 | return in_memory.NewEngine(logger, options...) 37 | } 38 | -------------------------------------------------------------------------------- /database/internal/initialization/initializer_test.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "spider/internal/configuration" 11 | ) 12 | 13 | func TestInitializer(t *testing.T) { 14 | t.Parallel() 15 | 16 | initializer, err := NewInitializer(&configuration.Config{ 17 | Network: &configuration.NetworkConfig{ 18 | Address: "localhost:30003", 19 | }, 20 | }) 21 | require.NoError(t, err) 22 | 23 | ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) 24 | defer cancel() 25 | 26 | err = initializer.StartDatabase(ctx) 27 | require.NoError(t, err) 28 | } 29 | -------------------------------------------------------------------------------- /database/internal/initialization/logger_initializer.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "errors" 5 | 6 | "go.uber.org/zap" 7 | "go.uber.org/zap/zapcore" 8 | 9 | "spider/internal/configuration" 10 | ) 11 | 12 | const ( 13 | debugLevel = "debug" 14 | infoLevel = "info" 15 | warnLevel = "warn" 16 | errorLevel = "error" 17 | ) 18 | 19 | const ( 20 | defaultEncoding = "json" 21 | defaultLevel = zapcore.InfoLevel 22 | defaultOutputPath = "spider.log" 23 | ) 24 | 25 | func CreateLogger(cfg *configuration.LoggingConfig) (*zap.Logger, error) { 26 | level := defaultLevel 27 | output := defaultOutputPath 28 | 29 | if cfg != nil { 30 | if cfg.Level != "" { 31 | supportedLoggingLevels := map[string]zapcore.Level{ 32 | debugLevel: zapcore.DebugLevel, 33 | infoLevel: zapcore.InfoLevel, 34 | warnLevel: zapcore.WarnLevel, 35 | errorLevel: zapcore.ErrorLevel, 36 | } 37 | 38 | var found bool 39 | if level, found = supportedLoggingLevels[cfg.Level]; !found { 40 | return nil, errors.New("logging level is incorrect") 41 | } 42 | } 43 | 44 | if cfg.Output != "" { 45 | // TODO: need to create a 46 | // directory if it is missing 47 | output = cfg.Output 48 | } 49 | } 50 | 51 | loggerCfg := zap.Config{ 52 | Encoding: defaultEncoding, 53 | Level: zap.NewAtomicLevelAt(level), 54 | OutputPaths: []string{output}, 55 | } 56 | 57 | return loggerCfg.Build() 58 | } 59 | -------------------------------------------------------------------------------- /database/internal/initialization/logger_initializer_test.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | 9 | "spider/internal/configuration" 10 | ) 11 | 12 | func TestCreateLogger(t *testing.T) { 13 | t.Parallel() 14 | 15 | tests := map[string]struct { 16 | cfg *configuration.LoggingConfig 17 | expectedErr error 18 | expectedNilObj bool 19 | }{ 20 | "create logger without config": { 21 | expectedErr: nil, 22 | }, 23 | "create logger with empty config fields": { 24 | cfg: &configuration.LoggingConfig{}, 25 | expectedErr: nil, 26 | }, 27 | "create logger with config fields": { 28 | cfg: &configuration.LoggingConfig{ 29 | Level: debugLevel, 30 | Output: "test.log", 31 | }, 32 | expectedErr: nil, 33 | }, 34 | "create logger with incorrect level": { 35 | cfg: &configuration.LoggingConfig{Level: "incorrect"}, 36 | expectedErr: errors.New("logging level is incorrect"), 37 | expectedNilObj: true, 38 | }, 39 | } 40 | 41 | for name, test := range tests { 42 | t.Run(name, func(t *testing.T) { 43 | t.Parallel() 44 | 45 | logger, err := CreateLogger(test.cfg) 46 | assert.Equal(t, test.expectedErr, err) 47 | if test.expectedNilObj { 48 | assert.Nil(t, logger) 49 | } else { 50 | assert.NotNil(t, logger) 51 | } 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /database/internal/initialization/network_initializer.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "errors" 5 | 6 | "go.uber.org/zap" 7 | 8 | "spider/internal/common" 9 | "spider/internal/configuration" 10 | "spider/internal/network" 11 | ) 12 | 13 | const defaultServerAddress = ":3223" 14 | 15 | func CreateNetwork(cfg *configuration.NetworkConfig, logger *zap.Logger) (*network.TCPServer, error) { 16 | if logger == nil { 17 | return nil, errors.New("logger is invalid") 18 | } 19 | 20 | address := defaultServerAddress 21 | var options []network.TCPServerOption 22 | 23 | if cfg != nil { 24 | if cfg.Address != "" { 25 | address = cfg.Address 26 | } 27 | 28 | if cfg.MaxConnections != 0 { 29 | options = append(options, network.WithServerMaxConnectionsNumber(uint(cfg.MaxConnections))) 30 | } 31 | 32 | if cfg.MaxMessageSize != "" { 33 | size, err := common.ParseSize(cfg.MaxMessageSize) 34 | if err != nil { 35 | return nil, errors.New("incorrect max message size") 36 | } 37 | 38 | options = append(options, network.WithServerBufferSize(uint(size))) 39 | } 40 | 41 | if cfg.IdleTimeout != 0 { 42 | options = append(options, network.WithServerIdleTimeout(cfg.IdleTimeout)) 43 | } 44 | } 45 | 46 | return network.NewTCPServer(address, logger, options...) 47 | } 48 | -------------------------------------------------------------------------------- /database/internal/network/network_options.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import "time" 4 | 5 | const defaultBufferSize = 4 << 10 6 | 7 | type TCPClientOption func(*TCPClient) 8 | 9 | func WithClientIdleTimeout(timeout time.Duration) TCPClientOption { 10 | return func(client *TCPClient) { 11 | client.idleTimeout = timeout 12 | } 13 | } 14 | 15 | func WithClientBufferSize(size uint) TCPClientOption { 16 | return func(client *TCPClient) { 17 | client.bufferSize = int(size) 18 | } 19 | } 20 | 21 | type TCPServerOption func(*TCPServer) 22 | 23 | func WithServerIdleTimeout(timeout time.Duration) TCPServerOption { 24 | return func(server *TCPServer) { 25 | server.idleTimeout = timeout 26 | } 27 | } 28 | 29 | func WithServerBufferSize(size uint) TCPServerOption { 30 | return func(server *TCPServer) { 31 | server.bufferSize = int(size) 32 | } 33 | } 34 | 35 | func WithServerMaxConnectionsNumber(count uint) TCPServerOption { 36 | return func(server *TCPServer) { 37 | server.maxConnections = int(count) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/internal/network/network_options_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWithClientIdleTimeout(t *testing.T) { 11 | t.Parallel() 12 | 13 | idleTimeout := time.Second 14 | option := WithClientIdleTimeout(time.Second) 15 | 16 | var client TCPClient 17 | option(&client) 18 | 19 | assert.Equal(t, idleTimeout, client.idleTimeout) 20 | } 21 | 22 | func TestWithClientBufferSize(t *testing.T) { 23 | t.Parallel() 24 | 25 | var bufferSize uint = 10 << 10 26 | option := WithClientBufferSize(bufferSize) 27 | 28 | var client TCPClient 29 | option(&client) 30 | 31 | assert.Equal(t, bufferSize, uint(client.bufferSize)) 32 | } 33 | 34 | func TestWithServerIdleTimeout(t *testing.T) { 35 | t.Parallel() 36 | 37 | idleTimeout := time.Second 38 | option := WithServerIdleTimeout(time.Second) 39 | 40 | var server TCPServer 41 | option(&server) 42 | 43 | assert.Equal(t, idleTimeout, server.idleTimeout) 44 | } 45 | 46 | func TestWithServerBufferSize(t *testing.T) { 47 | t.Parallel() 48 | 49 | var bufferSize uint = 10 << 10 50 | option := WithServerBufferSize(bufferSize) 51 | 52 | var server TCPServer 53 | option(&server) 54 | 55 | assert.Equal(t, bufferSize, uint(server.bufferSize)) 56 | } 57 | 58 | func TestWithServerMaxConnectionsNumber(t *testing.T) { 59 | t.Parallel() 60 | 61 | var maxConnections uint = 100 62 | option := WithServerMaxConnectionsNumber(maxConnections) 63 | 64 | var server TCPServer 65 | option(&server) 66 | 67 | assert.Equal(t, maxConnections, uint(server.maxConnections)) 68 | } 69 | -------------------------------------------------------------------------------- /database/internal/network/tcp_client.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "net" 8 | "time" 9 | ) 10 | 11 | type TCPClient struct { 12 | connection net.Conn 13 | idleTimeout time.Duration 14 | bufferSize int 15 | } 16 | 17 | func NewTCPClient(address string, options ...TCPClientOption) (*TCPClient, error) { 18 | connection, err := net.Dial("tcp", address) 19 | if err != nil { 20 | return nil, fmt.Errorf("failed to dial: %w", err) 21 | } 22 | 23 | client := &TCPClient{ 24 | connection: connection, 25 | bufferSize: defaultBufferSize, 26 | } 27 | 28 | for _, option := range options { 29 | option(client) 30 | } 31 | 32 | if client.idleTimeout != 0 { 33 | if err := connection.SetDeadline(time.Now().Add(client.idleTimeout)); err != nil { 34 | return nil, fmt.Errorf("failed to set deadline for connection: %w", err) 35 | } 36 | } 37 | 38 | return client, nil 39 | } 40 | 41 | func (c *TCPClient) Send(request []byte) ([]byte, error) { 42 | if _, err := c.connection.Write(request); err != nil { 43 | return nil, err 44 | } 45 | 46 | response := make([]byte, c.bufferSize) 47 | count, err := c.connection.Read(response) 48 | if err != nil && err != io.EOF { 49 | return nil, err 50 | } else if count == c.bufferSize { 51 | return nil, errors.New("small buffer size") 52 | } 53 | 54 | return response[:count], nil 55 | } 56 | 57 | func (c *TCPClient) Close() { 58 | if c.connection != nil { 59 | _ = c.connection.Close() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /database/test/wal/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/database/test/wal/.gitkeep -------------------------------------------------------------------------------- /lessons/10_lesson_live_coding_practise/scheduler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Scheduler struct { 10 | mutex sync.Mutex 11 | closed bool 12 | actions map[int]*time.Timer 13 | } 14 | 15 | func NewScheduler() *Scheduler { 16 | return &Scheduler{ 17 | actions: make(map[int]*time.Timer), 18 | } 19 | } 20 | 21 | // SetTimeout run some function after some timeout 22 | func (s *Scheduler) SetTimeout(key int, delay time.Duration, action func()) error { 23 | if delay < 0 { 24 | return errors.New("invalid delay") 25 | } 26 | 27 | if action == nil { 28 | return errors.New("invalid action") 29 | } 30 | 31 | s.mutex.Lock() 32 | defer s.mutex.Unlock() 33 | 34 | if s.closed { 35 | return errors.New("scheduler is closed") 36 | } 37 | 38 | if timer, found := s.actions[key]; found { 39 | timer.Stop() 40 | } 41 | 42 | s.actions[key] = time.AfterFunc(delay, action) 43 | return nil 44 | } 45 | 46 | // CancelTimeout cancel running of some function 47 | func (s *Scheduler) CancelTimeout(key int) { 48 | s.mutex.Lock() 49 | defer s.mutex.Unlock() 50 | 51 | if timer, found := s.actions[key]; found { 52 | timer.Stop() 53 | delete(s.actions, key) 54 | } 55 | } 56 | 57 | // Close cancel all functions 58 | func (s *Scheduler) Close() { 59 | s.mutex.Lock() 60 | defer s.mutex.Unlock() 61 | 62 | s.closed = true 63 | for key, timer := range s.actions { 64 | timer.Stop() 65 | delete(s.actions, key) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lessons/10_lesson_live_coding_practise/worker_pool/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | type WorkerPool struct { 9 | tasksCh chan func() 10 | mutex sync.RWMutex 11 | 12 | closed bool 13 | closeDoneCh chan struct{} 14 | } 15 | 16 | func NewWorkerPool(workersNumber int) (*WorkerPool, error) { 17 | if workersNumber <= 0 { 18 | return nil, errors.New("incorrect workers number") 19 | } 20 | 21 | wp := &WorkerPool{ 22 | closeDoneCh: make(chan struct{}), 23 | tasksCh: make(chan func(), workersNumber), 24 | } 25 | 26 | go wp.processTasks(workersNumber) 27 | return wp, nil 28 | } 29 | 30 | func (wp *WorkerPool) processTasks(workersNumber int) { 31 | var wg sync.WaitGroup 32 | wg.Add(workersNumber) 33 | 34 | for i := 0; i < workersNumber; i++ { 35 | go func() { 36 | defer wg.Done() 37 | for task := range wp.tasksCh { 38 | task() 39 | } 40 | }() 41 | } 42 | 43 | wg.Wait() 44 | close(wp.closeDoneCh) 45 | } 46 | 47 | // AddTask add task to pool 48 | func (wp *WorkerPool) AddTask(task func()) error { 49 | if task == nil { 50 | return errors.New("incorrect task") 51 | } 52 | 53 | wp.mutex.RLock() 54 | defer wp.mutex.RUnlock() 55 | 56 | if wp.closed { 57 | return errors.New("pool is closed") 58 | } 59 | 60 | select { 61 | case wp.tasksCh <- task: 62 | return nil 63 | default: 64 | return errors.New("pool is full") 65 | } 66 | } 67 | 68 | // Close close pool and wait all the tasks 69 | func (wp *WorkerPool) Close() { 70 | if wp.closed { 71 | return 72 | } 73 | 74 | wp.mutex.Lock() 75 | wp.closed = true 76 | wp.mutex.Unlock() 77 | 78 | close(wp.tasksCh) 79 | <-wp.closeDoneCh 80 | } 81 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/async_preemptible/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | func infiniteLoop(str string) { 9 | for { 10 | log.Println(str) 11 | } 12 | } 13 | 14 | func loop(str string) { 15 | for i := 0; i < 5; i++ { 16 | runtime.Gosched() 17 | log.Println(str) 18 | } 19 | } 20 | 21 | func main() { 22 | runtime.GOMAXPROCS(1) 23 | go infiniteLoop("infinite_loop") 24 | loop("loop") 25 | } 26 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/endless_loop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | func main() { 9 | runtime.GOMAXPROCS(1) 10 | 11 | var i int 12 | go func() { 13 | for { 14 | i++ 15 | } 16 | }() 17 | 18 | fmt.Println(i) 19 | } 20 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/gmp_model/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) 10 | fmt.Println("CPU:", runtime.NumCPU()) 11 | 12 | runtime.GOMAXPROCS(16) 13 | 14 | fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) 15 | fmt.Println("CPU:", runtime.NumCPU()) 16 | } 17 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/goroutine_index/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | for i := 0; i < 5; i++ { 10 | go func() { 11 | fmt.Print(i) 12 | }() 13 | } 14 | 15 | time.Sleep(2 * time.Second) 16 | } 17 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/goroutines_number/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | func main() { 9 | fmt.Printf("Goroutines: %d\n", runtime.NumGoroutine()) 10 | } 11 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/never_exit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func task() { 9 | for { 10 | time.Sleep(time.Millisecond * 200) 11 | panic("unexpected situation") 12 | } 13 | } 14 | 15 | func NeverExit(name string, action func()) { 16 | defer func() { 17 | if v := recover(); v != nil { 18 | log.Println(name, "is crashed - restarting...") 19 | go NeverExit(name, action) 20 | } 21 | }() 22 | 23 | if action != nil { 24 | action() 25 | } 26 | } 27 | 28 | func main() { 29 | go NeverExit("first_goroutine", task) 30 | go NeverExit("second_goroutine", task) 31 | 32 | time.Sleep(time.Second) 33 | } 34 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/panic_from_other_goroutine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func process() { 9 | defer func() { 10 | v := recover() 11 | fmt.Println("recovered:", v) 12 | }() 13 | 14 | go func() { 15 | panic("error") 16 | }() 17 | 18 | time.Sleep(time.Second) 19 | } 20 | 21 | func main() { 22 | process() 23 | } 24 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/runtime_exit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | go func() { 11 | fmt.Println("first") 12 | runtime.Goexit() 13 | fmt.Println("second") 14 | }() 15 | 16 | time.Sleep(3 * time.Second) 17 | } 18 | 19 | /* 20 | func main() { 21 | go func() { 22 | for { 23 | time.Sleep(time.Second) 24 | fmt.Println("tick") 25 | } 26 | }() 27 | 28 | time.Sleep(3 * time.Second) 29 | runtime.Goexit() 30 | } 31 | */ 32 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/tcp_server_with_panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "net" 7 | ) 8 | 9 | // nc 127.0.0.1 12345 10 | 11 | func main() { 12 | listener, err := net.Listen("tcp", ":12345") 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | for { 18 | conn, err := listener.Accept() 19 | if err != nil { 20 | log.Println(err) 21 | } 22 | 23 | go ClientHandler(conn) 24 | } 25 | } 26 | 27 | func ClientHandler(c net.Conn) { 28 | panic(errors.New("internal error")) 29 | } 30 | -------------------------------------------------------------------------------- /lessons/2_lesson_gorutines_and_scheduler/tcp_server_without_panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net" 6 | ) 7 | 8 | // nc 127.0.0.1 12345 9 | 10 | func main() { 11 | listener, err := net.Listen("tcp", ":12345") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | for { 17 | conn, err := listener.Accept() 18 | if err != nil { 19 | log.Println(err) 20 | } 21 | 22 | go ClientHandler(conn) 23 | } 24 | } 25 | 26 | func ClientHandler(c net.Conn) { 27 | defer func() { 28 | if v := recover(); v != nil { 29 | log.Println("captured panic:", v) 30 | } 31 | c.Close() 32 | }() 33 | 34 | panic("internal error") 35 | } 36 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/lessons/3_lesson_sync_primitives_1/.DS_Store -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/correct_increment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | mutex := sync.Mutex{} 10 | wg := sync.WaitGroup{} 11 | wg.Add(1000) 12 | 13 | value := 0 14 | for i := 0; i < 1000; i++ { 15 | go func() { 16 | defer wg.Done() 17 | 18 | mutex.Lock() 19 | value++ 20 | mutex.Unlock() 21 | }() 22 | } 23 | 24 | wg.Wait() 25 | 26 | fmt.Println(value) 27 | } 28 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/data_race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | text := "" 10 | 11 | wg := sync.WaitGroup{} 12 | wg.Add(2) 13 | 14 | go func() { 15 | defer wg.Done() 16 | text = "hello world" 17 | }() 18 | 19 | go func() { 20 | defer wg.Done() 21 | fmt.Println(text) 22 | }() 23 | 24 | wg.Wait() 25 | } 26 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // Need to show solution 6 | 7 | var resource1 int 8 | var resource2 int 9 | 10 | func normalizeResources(lhs, rhs *sync.Mutex) { 11 | lhs.Lock() 12 | rhs.Lock() 13 | 14 | // normalization 15 | 16 | rhs.Unlock() 17 | lhs.Unlock() 18 | } 19 | 20 | func main() { 21 | var mutex1 sync.Mutex 22 | var mutex2 sync.Mutex 23 | 24 | wg := sync.WaitGroup{} 25 | wg.Add(1000) 26 | 27 | for i := 0; i < 500; i++ { 28 | go func() { 29 | defer wg.Done() 30 | normalizeResources(&mutex1, &mutex2) 31 | }() 32 | } 33 | 34 | for i := 0; i < 500; i++ { 35 | go func() { 36 | defer wg.Done() 37 | normalizeResources(&mutex2, &mutex1) 38 | }() 39 | } 40 | 41 | wg.Wait() 42 | } 43 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/deadlock_with_work/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Need to show solution 10 | 11 | var resource1 int 12 | var resource2 int 13 | 14 | func normalizeResources(lhs, rhs *sync.Mutex) { 15 | lhs.Lock() 16 | rhs.Lock() 17 | 18 | // normalization 19 | 20 | rhs.Unlock() 21 | lhs.Unlock() 22 | } 23 | 24 | func main() { 25 | var mutex1 sync.Mutex 26 | var mutex2 sync.Mutex 27 | 28 | wg := sync.WaitGroup{} 29 | wg.Add(1000) 30 | 31 | for i := 0; i < 500; i++ { 32 | go func() { 33 | defer wg.Done() 34 | normalizeResources(&mutex1, &mutex2) 35 | }() 36 | } 37 | 38 | for i := 0; i < 500; i++ { 39 | go func() { 40 | defer wg.Done() 41 | normalizeResources(&mutex2, &mutex1) 42 | }() 43 | } 44 | 45 | time.Sleep(time.Millisecond * 100) 46 | 47 | go func() { 48 | for { 49 | time.Sleep(time.Second) 50 | log.Println("tick") 51 | } 52 | }() 53 | 54 | wg.Wait() 55 | } 56 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/defer_performance/perf_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // go test -bench=. perf_test.go 9 | 10 | func BenchmarkMutex(b *testing.B) { 11 | var counter int64 12 | var mutex sync.Mutex 13 | for j := 0; j < b.N; j++ { 14 | func() { 15 | mutex.Lock() 16 | counter++ 17 | mutex.Unlock() 18 | }() 19 | } 20 | } 21 | 22 | func BenchmarkMutexWithDefer(b *testing.B) { 23 | var counter int64 24 | var mutex sync.Mutex 25 | for j := 0; j < b.N; j++ { 26 | func() { 27 | mutex.Lock() 28 | defer mutex.Unlock() 29 | counter++ 30 | }() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/defer_with_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // Need to show solution 6 | 7 | var mutex sync.Mutex 8 | 9 | func operation() {} 10 | 11 | func doSomething() { 12 | mutex.Lock() 13 | operation() 14 | mutex.Unlock() 15 | 16 | // some long operation 17 | 18 | mutex.Lock() 19 | operation() 20 | mutex.Unlock() 21 | } 22 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/defer_with_panic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | ) 7 | 8 | var mutex sync.Mutex 9 | 10 | func functionWithPanic() { 11 | panic("error") 12 | } 13 | 14 | func handle1() { 15 | defer func() { 16 | if err := recover(); err != nil { 17 | log.Println("recovered") 18 | } 19 | }() 20 | 21 | mutex.Lock() 22 | defer mutex.Unlock() 23 | 24 | functionWithPanic() 25 | } 26 | 27 | func handle2() { 28 | defer func() { 29 | if err := recover(); err != nil { 30 | log.Println("recovered") 31 | } 32 | }() 33 | 34 | mutex.Lock() 35 | functionWithPanic() 36 | mutex.Unlock() 37 | } 38 | 39 | func main() { 40 | handle1() 41 | handle2() 42 | } 43 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/incorrect_buffer_design/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Buffer struct { 6 | mtx sync.Mutex 7 | data []int 8 | } 9 | 10 | func NewBuffer() *Buffer { 11 | return &Buffer{} 12 | } 13 | 14 | func (b *Buffer) Add(value int) { 15 | b.mtx.Lock() 16 | defer b.mtx.Unlock() 17 | 18 | b.data = append(b.data, value) 19 | } 20 | 21 | func (b *Buffer) Data() []int { 22 | b.mtx.Lock() 23 | defer b.mtx.Unlock() 24 | 25 | return b.data 26 | } 27 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/incorrect_defer_with_loop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Need to show solution 8 | 9 | var mutex sync.Mutex 10 | var values []int 11 | 12 | func doSomething() { 13 | for number := 0; number < 10; number++ { 14 | mutex.Lock() 15 | defer mutex.Unlock() 16 | 17 | values = append(values, number) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/incorrect_defer_with_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | var mutex sync.Mutex 11 | var cache map[string]string 12 | 13 | func doSomething() { 14 | var value string 15 | 16 | { 17 | mutex.Lock() 18 | defer mutex.Unlock() 19 | value = cache["key"] 20 | } 21 | 22 | fmt.Println(value) 23 | 24 | { 25 | mutex.Lock() 26 | defer mutex.Unlock() 27 | cache["key"] = "value" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/incorrect_increment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | wg := sync.WaitGroup{} 10 | wg.Add(1000) 11 | 12 | value := 0 13 | for i := 0; i < 1000; i++ { 14 | go func() { 15 | defer wg.Done() 16 | value++ 17 | }() 18 | } 19 | 20 | wg.Wait() 21 | 22 | fmt.Println(value) 23 | } 24 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/incorrect_struct_design/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Data struct { 6 | sync.Mutex 7 | values []int 8 | } 9 | 10 | func (d *Data) Add(value int) { 11 | d.Lock() 12 | defer d.Unlock() 13 | 14 | d.values = append(d.values, value) 15 | } 16 | 17 | func main() { 18 | data := Data{} 19 | data.Add(100) 20 | 21 | data.Unlock() // Possible problem! 22 | } 23 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/lazy_initialization/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Map struct { 6 | once sync.Once 7 | values map[interface{}]interface{} 8 | } 9 | 10 | func NewMap() *Map { 11 | return &Map{} 12 | } 13 | 14 | func (m *Map) Add(key, value interface{}) { 15 | m.init() 16 | m.values[key] = value 17 | } 18 | 19 | func (m *Map) init() { 20 | m.once.Do(func() { 21 | m.values = make(map[interface{}]interface{}) 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/livelock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | // Need to show solution 10 | 11 | var mutex1 sync.Mutex 12 | var mutex2 sync.Mutex 13 | 14 | func goroutine1() { 15 | mutex1.Lock() 16 | 17 | runtime.Gosched() 18 | for !mutex2.TryLock() { 19 | // active waiting 20 | } 21 | 22 | mutex2.Unlock() 23 | mutex1.Unlock() 24 | 25 | fmt.Println("goroutine1 finished") 26 | } 27 | 28 | func goroutine2() { 29 | mutex2.Lock() 30 | 31 | runtime.Gosched() 32 | for !mutex1.TryLock() { 33 | // active waiting 34 | } 35 | 36 | mutex1.Unlock() 37 | mutex2.Unlock() 38 | 39 | fmt.Println("goroutine2 finished") 40 | } 41 | 42 | func main() { 43 | wg := sync.WaitGroup{} 44 | wg.Add(2) 45 | 46 | go func() { 47 | defer wg.Done() 48 | goroutine1() 49 | }() 50 | 51 | go func() { 52 | defer wg.Done() 53 | goroutine2() 54 | }() 55 | 56 | wg.Wait() 57 | } 58 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/local_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | var value int 11 | 12 | func inc() { 13 | mutex := sync.Mutex{} 14 | 15 | mutex.Lock() 16 | value++ 17 | mutex.Unlock() 18 | } 19 | 20 | func main() { 21 | wg := sync.WaitGroup{} 22 | wg.Add(1000) 23 | 24 | for i := 0; i < 1000; i++ { 25 | go func() { 26 | defer wg.Done() 27 | inc() 28 | }() 29 | } 30 | 31 | wg.Wait() 32 | 33 | fmt.Println(value) 34 | } 35 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/lock_granularity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | var mutex sync.Mutex 9 | var cache map[string]string 10 | 11 | func doSomething() { 12 | mutex.Lock() 13 | item := cache["key"] 14 | fmt.Println(item) 15 | mutex.Unlock() 16 | } 17 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/lockable_struct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Lockable[T any] struct { 6 | sync.Mutex 7 | Data T 8 | } 9 | 10 | func main() { 11 | var l1 Lockable[int32] 12 | l1.Lock() 13 | l1.Data = 100 14 | l1.Unlock() 15 | 16 | var l2 Lockable[string] 17 | l2.Lock() 18 | l2.Data = "test" 19 | l2.Unlock() 20 | } 21 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/mutex_different_operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | var mutex sync.Mutex 9 | var value string 10 | 11 | func set(v string) { 12 | mutex.Lock() 13 | value = v 14 | mutex.Unlock() 15 | } 16 | 17 | func print() { 18 | mutex.Lock() 19 | fmt.Println(value) 20 | mutex.Unlock() 21 | } 22 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/mutex_for_order/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var mutex sync.Mutex 11 | mutex.Lock() 12 | 13 | go func() { 14 | time.Sleep(time.Second) 15 | log.Println("Hi") 16 | mutex.Unlock() 17 | }() 18 | 19 | mutex.Lock() 20 | log.Println("Bye") 21 | } 22 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/mutex_operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func lockAnyTimes() { 6 | mutex := sync.Mutex{} 7 | mutex.Lock() 8 | mutex.Lock() 9 | } 10 | 11 | func unlockWithoutLock() { 12 | mutex := sync.Mutex{} 13 | mutex.Unlock() 14 | } 15 | 16 | func unlockFromAnotherGoroutine() { 17 | mutex := sync.Mutex{} 18 | mutex.Lock() 19 | 20 | wg := sync.WaitGroup{} 21 | wg.Add(1) 22 | 23 | go func() { 24 | defer wg.Done() 25 | mutex.Unlock() 26 | }() 27 | 28 | wg.Wait() 29 | 30 | mutex.Lock() 31 | mutex.Unlock() 32 | } 33 | 34 | func main() { 35 | } 36 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/mutex_with_common_values/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // go run -race main.go 6 | 7 | type Data struct { 8 | X int 9 | Y int 10 | } 11 | 12 | func main() { 13 | var data Data 14 | values := make([]int, 2) 15 | 16 | wg := sync.WaitGroup{} 17 | wg.Add(2) 18 | 19 | go func() { 20 | defer wg.Done() 21 | 22 | data.X = 5 23 | values[0] = 5 24 | }() 25 | 26 | go func() { 27 | defer wg.Done() 28 | 29 | data.Y = 10 30 | values[1] = 10 31 | }() 32 | 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/mutex_with_local_values/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func main() { 11 | mutex := sync.Mutex{} 12 | wg := sync.WaitGroup{} 13 | wg.Add(1000) 14 | 15 | for i := 0; i < 10; i++ { 16 | go func() { 17 | defer wg.Done() 18 | 19 | value := 0 20 | for j := 0; j < 10; j++ { 21 | mutex.Lock() 22 | value++ 23 | mutex.Unlock() 24 | } 25 | 26 | log.Println(value) 27 | }() 28 | } 29 | 30 | wg.Wait() 31 | } 32 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/once/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | var once sync.Once 10 | onceBody := func() { 11 | fmt.Println("Only once") 12 | } 13 | 14 | var wg sync.WaitGroup 15 | wg.Add(10) 16 | 17 | for i := 0; i < 10; i++ { 18 | go func() { 19 | defer wg.Done() 20 | once.Do(onceBody) 21 | }() 22 | } 23 | 24 | wg.Wait() 25 | } 26 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/print_foo_bar_alternately/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type FooBar struct { 6 | number int 7 | fooMutex sync.Mutex 8 | barMutex sync.Mutex 9 | } 10 | 11 | func NewFooBar(number int) *FooBar { 12 | fb := &FooBar{number: number} 13 | fb.barMutex.Lock() 14 | return fb 15 | } 16 | 17 | func (fb *FooBar) Foo(printFoo func()) { 18 | for i := 0; i < fb.number; i++ { 19 | fb.fooMutex.Lock() 20 | printFoo() 21 | fb.barMutex.Unlock() 22 | } 23 | } 24 | 25 | func (fb *FooBar) Bar(printBar func()) { 26 | for i := 0; i < fb.number; i++ { 27 | fb.barMutex.Lock() 28 | printBar() 29 | fb.fooMutex.Unlock() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/recursive_lock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Need to show solution 8 | 9 | type Cache struct { 10 | mutex sync.Mutex 11 | data map[string]string 12 | } 13 | 14 | func NewCache() *Cache { 15 | return &Cache{ 16 | data: make(map[string]string), 17 | } 18 | } 19 | 20 | func (c *Cache) Set(key, value string) { 21 | c.mutex.Lock() 22 | defer c.mutex.Unlock() 23 | 24 | c.data[key] = value 25 | } 26 | 27 | func (c *Cache) Get(key string) string { 28 | c.mutex.Lock() 29 | defer c.mutex.Unlock() 30 | 31 | if c.Size() > 0 { 32 | return c.data[key] 33 | } 34 | 35 | return "" 36 | } 37 | 38 | func (c *Cache) Size() int { 39 | c.mutex.Lock() 40 | defer c.mutex.Unlock() 41 | 42 | return len(c.data) 43 | } 44 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/starvation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Starvation may be with processor, memory, file descriptors, 10 | // connections with database and so on... 11 | 12 | func main() { 13 | var wg sync.WaitGroup 14 | var mutex sync.Mutex 15 | const runtime = 1 * time.Second 16 | 17 | greedyWorker := func() { 18 | defer wg.Done() 19 | 20 | var count int 21 | for begin := time.Now(); time.Since(begin) <= runtime; { 22 | mutex.Lock() 23 | time.Sleep(3 * time.Nanosecond) 24 | mutex.Unlock() 25 | count++ 26 | } 27 | 28 | fmt.Printf("Greedy worker was able to execute %v work loops\n", count) 29 | } 30 | 31 | politeWorker := func() { 32 | defer wg.Done() 33 | 34 | var count int 35 | for begin := time.Now(); time.Since(begin) <= runtime; { 36 | mutex.Lock() 37 | time.Sleep(1 * time.Nanosecond) 38 | mutex.Unlock() 39 | 40 | mutex.Lock() 41 | time.Sleep(1 * time.Nanosecond) 42 | mutex.Unlock() 43 | 44 | mutex.Lock() 45 | time.Sleep(1 * time.Nanosecond) 46 | mutex.Unlock() 47 | 48 | count++ 49 | } 50 | 51 | fmt.Printf("Polite worker was able to execute %v work loops.\n", count) 52 | } 53 | 54 | wg.Add(2) 55 | go greedyWorker() 56 | go politeWorker() 57 | 58 | wg.Wait() 59 | } 60 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/sync_singleton/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Need to show solution 4 | 5 | type Singleton struct{} 6 | 7 | var instance *Singleton 8 | 9 | func GetInstance() *Singleton { 10 | if instance == nil { 11 | instance = &Singleton{} 12 | } 13 | 14 | return instance 15 | } 16 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/sync_stack/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // Need to show solution 8 | 9 | type Stack struct { 10 | mutex sync.Mutex 11 | data []string 12 | } 13 | 14 | func NewStack() Stack { 15 | return Stack{} 16 | } 17 | 18 | func (b Stack) Push(value string) { 19 | b.mutex.Lock() 20 | defer b.mutex.Unlock() 21 | 22 | b.data = append(b.data, value) 23 | } 24 | 25 | func (b Stack) Pop() { 26 | if len(b.data) < 0 { 27 | panic("pop: stack is empty") 28 | } 29 | 30 | b.mutex.Lock() 31 | defer b.mutex.Unlock() 32 | 33 | b.data = b.data[:len(b.data)-1] 34 | } 35 | 36 | func (b Stack) Top() string { 37 | if len(b.data) < 0 { 38 | panic("top: stack is empty") 39 | } 40 | 41 | b.mutex.Lock() 42 | defer b.mutex.Unlock() 43 | 44 | return b.data[len(b.data)-1] 45 | } 46 | 47 | var stack Stack 48 | 49 | func producer() { 50 | for i := 0; i < 1000; i++ { 51 | stack.Push("message") 52 | } 53 | } 54 | 55 | func consumer() { 56 | for i := 0; i < 10; i++ { 57 | _ = stack.Top() 58 | stack.Pop() 59 | } 60 | } 61 | 62 | func main() { 63 | producer() 64 | 65 | wg := sync.WaitGroup{} 66 | wg.Add(100) 67 | 68 | for i := 0; i < 100; i++ { 69 | go func() { 70 | defer wg.Done() 71 | consumer() 72 | }() 73 | } 74 | 75 | wg.Wait() 76 | } 77 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/wait_group_copying/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func done(wg sync.WaitGroup) { 6 | wg.Done() 7 | } 8 | 9 | func main() { 10 | wg := sync.WaitGroup{} 11 | wg.Add(1) 12 | done(wg) 13 | wg.Wait() 14 | } 15 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/wait_group_operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func makeNegativeCounter() { 6 | wg := sync.WaitGroup{} 7 | wg.Add(-10) 8 | } 9 | 10 | func waitZeroCounter() { 11 | wg := sync.WaitGroup{} 12 | wg.Wait() 13 | } 14 | 15 | func main() { 16 | } 17 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/wait_group_order/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | wg := sync.WaitGroup{} 10 | for i := 0; i < 5; i++ { 11 | wg.Add(1) 12 | go func() { 13 | defer wg.Done() 14 | log.Println("test") 15 | }() 16 | 17 | wg.Wait() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/3_lesson_sync_primitives_1/waiting_goroutines/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "log" 4 | 5 | // Need to show solution 6 | 7 | func main() { 8 | for i := 0; i < 5; i++ { 9 | go func() { 10 | log.Println("test") 11 | }() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Balun-courses/concurrency_go/47dfb8919653eb9528bd6fa5b4fadc2d38a56598/lessons/4_lesson_sync_primitives_2/.DS_Store -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/action_during_actions/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync/atomic" 4 | 5 | // Need to show solution 6 | 7 | type Data struct { 8 | count atomic.Int32 9 | } 10 | 11 | func (d *Data) Process() { 12 | d.count.Add(1) 13 | if d.count.CompareAndSwap(100, 0) { 14 | // do something... 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/atomic_performance/perf_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "testing" 7 | ) 8 | 9 | // go test -bench=. perf_test.go 10 | 11 | func BenchmarkMutexAdd(b *testing.B) { 12 | var number int32 13 | var mutex sync.Mutex 14 | for i := 0; i < b.N; i++ { 15 | mutex.Lock() 16 | number++ 17 | mutex.Unlock() 18 | } 19 | } 20 | 21 | func BenchmarkAtomicAdd(b *testing.B) { 22 | var number atomic.Int32 23 | for i := 0; i < b.N; i++ { 24 | number.Add(1) 25 | } 26 | } 27 | 28 | func BenchmarkAdd(b *testing.B) { 29 | var number int32 30 | for i := 0; i < b.N; i++ { 31 | number++ 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/atomic_pointer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | ) 7 | 8 | func main() { 9 | { 10 | var value1 int32 = 100 11 | var value2 int32 = 100 12 | pointer := unsafe.Pointer(&value1) 13 | atomic.StorePointer(&pointer, unsafe.Pointer(&value2)) 14 | } 15 | { 16 | var value1 int32 = 100 17 | var value2 int32 = 100 18 | var pointer atomic.Pointer[int32] 19 | pointer.Store(&value1) 20 | pointer.Store(&value2) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/atomic_value/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | func loadConfig() map[string]string { 9 | return make(map[string]string) 10 | } 11 | 12 | func requests() chan int { 13 | return make(chan int) 14 | } 15 | 16 | func main() { 17 | var config atomic.Value 18 | config.Store(loadConfig()) 19 | 20 | go func() { 21 | for { 22 | time.Sleep(10 * time.Second) 23 | config.Store(loadConfig()) 24 | } 25 | }() 26 | 27 | for r := range requests() { 28 | c := config.Load().(map[string]string) 29 | _, _ = r, c 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/broken_mutex_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | unlocked = false 5 | locked = true 6 | ) 7 | 8 | type BrokenMutex struct { 9 | state bool 10 | } 11 | 12 | // Здесь есть data race и нет гарантии взаимного исключения (safety), 13 | // так как несколько горутин могут попасть совместно в критическую секцию 14 | 15 | func (m *BrokenMutex) Lock() { 16 | for m.state { 17 | // итерация за итерацией... 18 | } 19 | 20 | m.state = locked 21 | } 22 | 23 | func (m *BrokenMutex) Unlock() { 24 | m.state = unlocked 25 | } 26 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/broken_mutex_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync/atomic" 4 | 5 | const ( 6 | unlocked = false 7 | locked = true 8 | ) 9 | 10 | type BrokenMutex struct { 11 | state atomic.Bool 12 | } 13 | 14 | // Здесь нет гарантии взаимного исключения (safety), так как несколько 15 | // горутин могут попасть совместно в критическую секцию 16 | 17 | func (m *BrokenMutex) Lock() { 18 | for m.state.Load() { 19 | // итерация за итерацией... 20 | } 21 | 22 | m.state.Store(locked) 23 | } 24 | 25 | func (m *BrokenMutex) Unlock() { 26 | m.state.Store(unlocked) 27 | } 28 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/broken_mutex_3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | const ( 9 | unlocked = false 10 | locked = true 11 | ) 12 | 13 | type BrokenMutex struct { 14 | want [2]atomic.Bool 15 | owner int 16 | } 17 | 18 | // Здесь нет гарантии прогресса (liveness), так как могут несколько 19 | // горутин могут помешать друг другу и попасть в livelock 20 | 21 | func (m *BrokenMutex) Lock(index int) { 22 | m.want[index].Store(locked) 23 | for m.want[1-index].Load() { 24 | runtime.Gosched() 25 | } 26 | 27 | m.owner = index 28 | } 29 | 30 | func (m *BrokenMutex) Unlock(index int) { 31 | m.want[m.owner].Store(unlocked) 32 | } 33 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/cas_loop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | func IncrementAndGet(pointer *int32) int32 { 8 | for { 9 | currentValue := atomic.LoadInt32(pointer) 10 | nextValue := currentValue + 1 11 | if atomic.CompareAndSwapInt32(pointer, currentValue, nextValue) { 12 | return nextValue 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/compare_and_swap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | // Need to show solution 10 | 11 | var data map[string]string 12 | var initialized atomic.Bool 13 | 14 | func initialize() { 15 | if !initialized.Load() { 16 | initialized.Store(true) 17 | data = make(map[string]string) 18 | fmt.Println("initialized") 19 | } 20 | } 21 | 22 | func main() { 23 | wg := sync.WaitGroup{} 24 | wg.Add(1000) 25 | 26 | for i := 0; i < 1000; i++ { 27 | go func() { 28 | defer wg.Done() 29 | initialize() 30 | }() 31 | } 32 | 33 | wg.Wait() 34 | } 35 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/concurrent_map_writes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func setter(data map[string]string) { 6 | data["test"] = "test" 7 | } 8 | 9 | func main() { 10 | data := make(map[string]string) 11 | 12 | wg := sync.WaitGroup{} 13 | wg.Add(1000) 14 | for i := 0; i < 1000; i++ { 15 | go func() { 16 | defer wg.Done() 17 | setter(data) 18 | }() 19 | } 20 | 21 | wg.Wait() 22 | } 23 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/cond/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func subscribe(name string, data map[string]string, c *sync.Cond) { 10 | c.L.Lock() 11 | 12 | for len(data) == 0 { 13 | c.Wait() 14 | } 15 | 16 | log.Printf("[%s] %s\n", name, data["key"]) 17 | 18 | c.L.Unlock() 19 | } 20 | 21 | func publish(name string, data map[string]string, c *sync.Cond) { 22 | time.Sleep(time.Second) 23 | 24 | c.L.Lock() 25 | data["key"] = "value" 26 | c.L.Unlock() 27 | 28 | log.Printf("[%s] data publisher\n", name) 29 | c.Broadcast() 30 | } 31 | 32 | func main() { 33 | data := map[string]string{} 34 | cond := sync.NewCond(&sync.Mutex{}) 35 | 36 | wg := sync.WaitGroup{} 37 | wg.Add(3) 38 | 39 | go func() { 40 | defer wg.Done() 41 | subscribe("subscriber_1", data, cond) 42 | }() 43 | 44 | go func() { 45 | defer wg.Done() 46 | subscribe("subscriber_2", data, cond) 47 | }() 48 | 49 | go func() { 50 | defer wg.Done() 51 | publish("publisher", data, cond) 52 | }() 53 | 54 | wg.Wait() 55 | } 56 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/cond_operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func waitWithoutLock() { 6 | cond := sync.NewCond(&sync.Mutex{}) 7 | cond.Wait() 8 | } 9 | 10 | func waitAfterSignal() { 11 | cond := sync.NewCond(&sync.Mutex{}) 12 | 13 | cond.Signal() 14 | 15 | cond.L.Lock() 16 | cond.Wait() 17 | cond.L.Unlock() 18 | } 19 | 20 | func main() { 21 | } 22 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/false_sharing/perf_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "sync/atomic" 7 | "testing" 8 | ) 9 | 10 | // go test -bench=. perf_test.go 11 | 12 | type MutexCounter struct { 13 | value int32 14 | mutex sync.Mutex 15 | } 16 | 17 | func (c *MutexCounter) Increment(int) { 18 | c.mutex.Lock() 19 | defer c.mutex.Unlock() 20 | c.value++ 21 | } 22 | 23 | func (c *MutexCounter) Get() int32 { 24 | c.mutex.Lock() 25 | defer c.mutex.Unlock() 26 | return c.value 27 | } 28 | 29 | type AtomicCounter struct { 30 | value atomic.Int32 31 | } 32 | 33 | func (c *AtomicCounter) Increment(int) { 34 | c.value.Add(1) 35 | } 36 | 37 | func (c *AtomicCounter) Get() int32 { 38 | return c.value.Load() 39 | } 40 | 41 | type ShardedAtomicCounter struct { 42 | shards [10]AtomicCounter 43 | } 44 | 45 | func (c *ShardedAtomicCounter) Increment(idx int) { 46 | c.shards[idx].value.Add(1) 47 | } 48 | 49 | func (c *ShardedAtomicCounter) Get() int32 { 50 | var value int32 51 | for idx := 0; idx < 10; idx++ { 52 | value += c.shards[idx].Get() 53 | } 54 | 55 | return value 56 | } 57 | 58 | func BenchmarkAtomicCounter(b *testing.B) { 59 | wg := sync.WaitGroup{} 60 | wg.Add(runtime.NumCPU()) 61 | 62 | counter := MutexCounter{} 63 | for i := 0; i < runtime.NumCPU(); i++ { 64 | go func(idx int) { 65 | defer wg.Done() 66 | for j := 0; j < b.N; j++ { 67 | counter.Increment(idx) 68 | } 69 | }(i) 70 | } 71 | 72 | wg.Wait() 73 | } 74 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/incorrect_increment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func main() { 11 | wg := sync.WaitGroup{} 12 | wg.Add(1000) 13 | 14 | var value int 15 | for i := 0; i < 1000; i++ { 16 | go func() { 17 | defer wg.Done() 18 | value++ 19 | }() 20 | } 21 | 22 | wg.Wait() 23 | 24 | fmt.Println(value) 25 | } 26 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/once_implementation/once.go: -------------------------------------------------------------------------------- 1 | package once_implementation 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | ) 7 | 8 | type Once struct { 9 | mutex sync.Mutex 10 | state uintptr 11 | } 12 | 13 | func NewOnce() *Once { 14 | return &Once{} 15 | } 16 | 17 | func (o *Once) Do(action func()) { 18 | if action == nil { 19 | return 20 | } 21 | 22 | if atomic.LoadUintptr(&o.state) == 0 { 23 | o.doOnce(action) 24 | } 25 | } 26 | 27 | func (o *Once) doOnce(action func()) { 28 | o.mutex.Lock() 29 | defer o.mutex.Unlock() 30 | 31 | if o.state == 0 { 32 | action() 33 | atomic.StoreUintptr(&o.state, 1) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/peterson_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | const ( 9 | unlocked = false 10 | locked = true 11 | ) 12 | 13 | type BrokenMutex struct { 14 | want [2]atomic.Bool 15 | victim atomic.Int32 16 | owner int 17 | } 18 | 19 | func (m *BrokenMutex) Lock(index int) { 20 | m.want[index].Store(locked) 21 | m.victim.Store(int32(index)) 22 | 23 | for m.want[1-index].Load() && m.victim.Load() == int32(index) { 24 | runtime.Gosched() 25 | } 26 | 27 | m.owner = index 28 | } 29 | 30 | func (m *BrokenMutex) Unlock(index int) { 31 | m.want[m.owner].Store(unlocked) 32 | } 33 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/pool/pool_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // go test -bench=. pool_test.go -benchmem 4 | 5 | import ( 6 | "sync" 7 | "testing" 8 | ) 9 | 10 | type Person struct { 11 | name string 12 | } 13 | 14 | type PersonsPool struct { 15 | pool sync.Pool 16 | } 17 | 18 | func NewPersonsPool() *PersonsPool { 19 | return &PersonsPool{ 20 | pool: sync.Pool{ 21 | New: func() interface{} { return new(Person) }, 22 | }, 23 | } 24 | } 25 | func (p *PersonsPool) Get() *Person { 26 | return p.pool.Get().(*Person) 27 | } 28 | 29 | func (p *PersonsPool) Put(person *Person) { 30 | p.pool.Put(person) 31 | } 32 | 33 | func BenchmarkWithPool(b *testing.B) { 34 | pool := NewPersonsPool() 35 | for i := 0; i < b.N; i++ { 36 | person := pool.Get() 37 | person.name = "Ivan" // Need to initialize 38 | pool.Put(person) 39 | } 40 | } 41 | 42 | var gPerson *Person 43 | 44 | func BenchmarkWithoutPool(b *testing.B) { 45 | for i := 0; i < b.N; i++ { 46 | person := &Person{name: "Ivan"} 47 | gPerson = person 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/pool_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Node struct { 6 | Value any 7 | next *Node 8 | } 9 | 10 | type freeList struct { 11 | head *Node 12 | } 13 | 14 | func newLinkedList() freeList { 15 | return freeList{} 16 | } 17 | 18 | func (l *freeList) push(node *Node) { 19 | node.next = l.head 20 | l.head = node 21 | } 22 | 23 | func (l *freeList) pop() *Node { 24 | if l.head != nil { 25 | node := l.head 26 | l.head = l.head.next 27 | return node 28 | } 29 | 30 | return nil 31 | } 32 | 33 | type Pool struct { 34 | ctr func() any 35 | 36 | mtx sync.Mutex 37 | list freeList 38 | } 39 | 40 | func NewPool(ctr func() any) *Pool { 41 | return &Pool{ 42 | ctr: ctr, 43 | } 44 | } 45 | 46 | func (l *Pool) Get() *Node { 47 | l.mtx.Lock() 48 | defer l.mtx.Unlock() 49 | 50 | node := l.list.pop() 51 | if node == nil { 52 | node = &Node{ 53 | Value: l.ctr(), 54 | } 55 | } 56 | 57 | return node 58 | } 59 | 60 | func (l *Pool) Put(node *Node) { 61 | l.mtx.Lock() 62 | defer l.mtx.Unlock() 63 | 64 | l.list.push(node) 65 | } 66 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/print_in_order/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync/atomic" 4 | 5 | type Foo struct { 6 | firstJobDone atomic.Bool 7 | secondJobDone atomic.Bool 8 | } 9 | 10 | func (f *Foo) first(printFirst func()) { 11 | printFirst() 12 | f.firstJobDone.Store(true) 13 | } 14 | 15 | func (f *Foo) second(printSecond func()) { 16 | for !f.firstJobDone.Load() { 17 | // active waiting... 18 | } 19 | 20 | printSecond() 21 | f.secondJobDone.Store(true) 22 | } 23 | 24 | func (f *Foo) third(printThird func()) { 25 | for !f.secondJobDone.Load() { 26 | // active waiting... 27 | } 28 | 29 | printThird() 30 | } 31 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/recursive_mutex_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "runtime" 7 | "strconv" 8 | "sync" 9 | "sync/atomic" 10 | ) 11 | 12 | // This is terrible, slow, and should never be used. 13 | func goid() (int, error) { 14 | buf := make([]byte, 32) 15 | n := runtime.Stack(buf, false) 16 | buf = buf[:n] 17 | // goroutine 1 [running]: ... 18 | 19 | buf, ok := bytes.CutPrefix(buf, []byte("goroutine ")) 20 | if !ok { 21 | return 0, errors.New("bad stack") 22 | } 23 | 24 | i := bytes.IndexByte(buf, ' ') 25 | if i < 0 { 26 | return 0, errors.New("bad stack") 27 | } 28 | 29 | return strconv.Atoi(string(buf[:i])) 30 | } 31 | 32 | type RecursiveMutex struct { 33 | mutex sync.Mutex 34 | count atomic.Int32 35 | owner atomic.Int32 36 | } 37 | 38 | func NewRecursiveMutex() *RecursiveMutex { 39 | return &RecursiveMutex{} 40 | } 41 | 42 | func (m *RecursiveMutex) Lock() { 43 | id, err := goid() 44 | if err != nil { 45 | panic("recursive_mutex: " + err.Error()) 46 | } 47 | 48 | if m.owner.Load() == int32(id) { 49 | m.count.Add(1) 50 | } else { 51 | m.mutex.Lock() 52 | m.owner.Store(int32(id)) 53 | m.count.Store(1) 54 | } 55 | } 56 | 57 | func (m *RecursiveMutex) Unlock() { 58 | id, err := goid() 59 | if err != nil || m.owner.Load() != int32(id) { 60 | panic("recursive_mutex: " + err.Error()) 61 | } 62 | 63 | m.count.Add(-1) 64 | if m.count.Load() == 0 { 65 | m.owner.Store(0) 66 | m.mutex.Unlock() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/rlocker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func withLock(mutex sync.Locker, action func()) { 6 | if action == nil { 7 | return 8 | } 9 | 10 | mutex.Lock() 11 | defer mutex.Unlock() 12 | 13 | action() 14 | } 15 | 16 | func main() { 17 | mutex := sync.RWMutex{} 18 | withLock(&mutex, func() { 19 | // write lock 20 | }) 21 | 22 | withLock(mutex.RLocker(), func() { 23 | // read lock 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/rw_mutex_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type RWMutex struct { 6 | notifier *sync.Cond 7 | mutex *sync.Mutex 8 | readersNumber int 9 | hasWriter bool 10 | } 11 | 12 | func NewRWMutex() *RWMutex { 13 | var mutex sync.Mutex 14 | notifier := sync.NewCond(&mutex) 15 | 16 | return &RWMutex{ 17 | mutex: &mutex, 18 | notifier: notifier, 19 | } 20 | } 21 | 22 | func (m *RWMutex) Lock() { 23 | m.mutex.Lock() 24 | defer m.mutex.Unlock() 25 | 26 | for m.hasWriter { 27 | m.notifier.Wait() 28 | } 29 | 30 | m.hasWriter = true 31 | for m.readersNumber != 0 { 32 | m.notifier.Wait() 33 | } 34 | } 35 | 36 | func (m *RWMutex) Unlock() { 37 | m.mutex.Lock() 38 | defer m.mutex.Unlock() 39 | 40 | m.hasWriter = false 41 | m.notifier.Broadcast() 42 | } 43 | 44 | func (m *RWMutex) RLock() { 45 | m.mutex.Lock() 46 | defer m.mutex.Unlock() 47 | 48 | for m.hasWriter { 49 | m.notifier.Wait() 50 | } 51 | 52 | m.readersNumber++ 53 | } 54 | 55 | func (m *RWMutex) RUnlock() { 56 | m.mutex.Lock() 57 | defer m.mutex.Unlock() 58 | 59 | m.readersNumber-- 60 | if m.readersNumber == 0 { 61 | m.notifier.Broadcast() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/rw_mutex_operations/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | func RUnlockLockedMutex() { 6 | m := sync.RWMutex{} 7 | m.Lock() 8 | m.RUnlock() 9 | } 10 | 11 | func UnlockRLockedMutex() { 12 | m := sync.RWMutex{} 13 | m.RLock() 14 | m.Unlock() 15 | } 16 | 17 | func LockRLockedMutex() { 18 | m := sync.RWMutex{} 19 | m.Lock() 20 | m.RLock() 21 | } 22 | 23 | func main() { 24 | } 25 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/rw_mutex_performance/perf_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | // go test -bench=. perf_test.go 9 | 10 | func BenchmarkMutexAdd(b *testing.B) { 11 | var number int32 12 | var mutex sync.Mutex 13 | for i := 0; i < b.N; i++ { 14 | mutex.Lock() 15 | number++ 16 | mutex.Unlock() 17 | } 18 | } 19 | 20 | func BenchmarkRWMutexAdd(b *testing.B) { 21 | var number int32 22 | var mutex sync.RWMutex 23 | for i := 0; i < b.N; i++ { 24 | mutex.Lock() 25 | number++ 26 | mutex.Unlock() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/rw_mutex_with_map/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Counters struct { 6 | mu sync.RWMutex 7 | m map[string]int 8 | } 9 | 10 | func (c *Counters) Load(key string) (int, bool) { 11 | c.mu.RLock() 12 | defer c.mu.RUnlock() 13 | 14 | value, found := c.m[key] 15 | return value, found 16 | } 17 | 18 | func (c *Counters) Store(key string, value int) { 19 | c.mu.Lock() 20 | defer c.mu.Unlock() 21 | 22 | c.m[key] = value 23 | } 24 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/semaphore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Semaphore struct { 8 | count int 9 | max int 10 | condition *sync.Cond 11 | } 12 | 13 | func NewSemaphore(limit int) *Semaphore { 14 | mutex := &sync.Mutex{} 15 | return &Semaphore{ 16 | max: limit, 17 | condition: sync.NewCond(mutex), 18 | } 19 | } 20 | 21 | func (s *Semaphore) Acquire() { 22 | s.condition.L.Lock() 23 | defer s.condition.L.Unlock() 24 | 25 | for s.count >= s.max { 26 | s.condition.Wait() 27 | } 28 | 29 | s.count++ 30 | } 31 | 32 | func (s *Semaphore) Release() { 33 | s.condition.L.Lock() 34 | defer s.condition.L.Unlock() 35 | 36 | s.count-- 37 | s.condition.Signal() 38 | } 39 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/spinlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync/atomic" 5 | ) 6 | 7 | const ( 8 | unlocked = false 9 | locked = true 10 | ) 11 | 12 | type SpinLock struct { 13 | state atomic.Bool 14 | } 15 | 16 | func NewSpinLock() *SpinLock { 17 | return &SpinLock{} 18 | } 19 | 20 | func (s *SpinLock) Lock() { 21 | for !s.state.CompareAndSwap(unlocked, locked) { 22 | // итерация за итерацией... 23 | } 24 | } 25 | 26 | func (s *SpinLock) Unlock() { 27 | s.state.Store(unlocked) 28 | } 29 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/spinlock_combined/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | const ( 9 | unlocked = false 10 | locked = true 11 | ) 12 | 13 | const retriesNumber = 3 14 | 15 | type SpinLock struct { 16 | state atomic.Bool 17 | } 18 | 19 | func NewSpinLock() *SpinLock { 20 | return &SpinLock{} 21 | } 22 | 23 | func (s *SpinLock) Lock() { 24 | retries := retriesNumber 25 | for !s.state.CompareAndSwap(unlocked, locked) { 26 | retries-- 27 | if retries == 0 { 28 | runtime.Gosched() 29 | retries = retriesNumber 30 | } 31 | } 32 | } 33 | 34 | func (s *SpinLock) Unlock() { 35 | s.state.Store(unlocked) 36 | } 37 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/spinlock_with_yield/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | const ( 9 | unlocked = false 10 | locked = true 11 | ) 12 | 13 | type SpinLock struct { 14 | state atomic.Bool 15 | } 16 | 17 | func NewSpinLock() *SpinLock { 18 | return &SpinLock{} 19 | } 20 | 21 | func (s *SpinLock) Lock() { 22 | for !s.state.CompareAndSwap(unlocked, locked) { 23 | runtime.Gosched() // но горутина не перейдет в состояние ожидания 24 | } 25 | } 26 | 27 | func (s *SpinLock) Unlock() { 28 | s.state.Store(unlocked) 29 | } 30 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/sync_map/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type Counters struct { 6 | m sync.Map 7 | } 8 | 9 | func (c *Counters) Load(key string) (int, bool) { 10 | value, found := c.m.Load(key) 11 | return value.(int), found 12 | } 13 | 14 | func (c *Counters) Store(key string, value int) { 15 | c.m.Store(key, value) 16 | } 17 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/ticket_lock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "sync/atomic" 6 | ) 7 | 8 | type TicketLock struct { 9 | ownerTicket atomic.Int64 10 | nextFreeTicket atomic.Int64 11 | } 12 | 13 | func NewTicketLock() *TicketLock { 14 | return &TicketLock{} 15 | } 16 | 17 | func (t *TicketLock) Lock() { 18 | ticket := t.nextFreeTicket.Add(1) 19 | for t.ownerTicket.Load() != ticket-1 { 20 | runtime.Gosched() 21 | } 22 | } 23 | 24 | func (t *TicketLock) Unlock() { 25 | t.ownerTicket.Add(1) 26 | } 27 | -------------------------------------------------------------------------------- /lessons/4_lesson_sync_primitives_2/timed_mutex_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type TimedMutex struct { 9 | mutex sync.Mutex 10 | } 11 | 12 | func NewTimedMutex() *TimedMutex { 13 | return &TimedMutex{} 14 | } 15 | 16 | func (m *TimedMutex) Lock() { 17 | m.mutex.Lock() 18 | } 19 | 20 | func (m *TimedMutex) TryLock() bool { 21 | return m.mutex.TryLock() 22 | } 23 | 24 | func (m *TimedMutex) TryLockFor(duration time.Duration) bool { 25 | const periodsNumber = 10 26 | period := duration / periodsNumber 27 | for duration > 0 { 28 | if m.TryLock() { 29 | return true 30 | } 31 | 32 | time.Sleep(period) 33 | duration -= period 34 | } 35 | 36 | return false 37 | } 38 | 39 | func (m *TimedMutex) Unlock() { 40 | m.mutex.Unlock() 41 | } 42 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/broadcast/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func notifier(signals chan int) { 9 | close(signals) 10 | } 11 | 12 | func subscriber(signals chan int) { 13 | <-signals 14 | fmt.Println("signaled") 15 | } 16 | 17 | func main() { 18 | signals := make(chan int) 19 | wg := sync.WaitGroup{} 20 | wg.Add(3) 21 | 22 | go func() { 23 | defer wg.Done() 24 | notifier(signals) 25 | }() 26 | 27 | go func() { 28 | defer wg.Done() 29 | subscriber(signals) 30 | }() 31 | 32 | go func() { 33 | defer wg.Done() 34 | subscriber(signals) 35 | }() 36 | 37 | wg.Wait() 38 | } 39 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/buffered_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | ch := make(chan int, 2) 5 | ch <- 100 6 | ch <- 100 7 | } 8 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/channel_data_race/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | // go run -race main.go 6 | 7 | var buffer chan int 8 | 9 | func main() { 10 | wg := sync.WaitGroup{} 11 | wg.Add(100) 12 | 13 | for i := 0; i < 100; i++ { 14 | go func() { 15 | defer wg.Done() 16 | buffer = make(chan int) 17 | }() 18 | } 19 | 20 | wg.Wait() 21 | } 22 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/channel_interaction/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Describe blocking 9 | 10 | func async(ch chan string) { 11 | time.Sleep(2 * time.Second) 12 | ch <- "async result" 13 | } 14 | 15 | func main() { 16 | ch := make(chan string) 17 | go async(ch) 18 | // ... 19 | result := <-ch 20 | fmt.Println(result) 21 | } 22 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/copy_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | source := make(chan int) 7 | clone := source 8 | 9 | go func() { 10 | source <- 1 11 | }() 12 | 13 | fmt.Println(<-clone) 14 | } 15 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/deadlock/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var actions int 8 | var mutex sync.Mutex 9 | var buffer chan struct{} 10 | 11 | func consumer() { 12 | for i := 0; i < 1000; i++ { 13 | mutex.Lock() 14 | actions++ 15 | <-buffer 16 | mutex.Unlock() 17 | } 18 | } 19 | 20 | func producer() { 21 | for i := 0; i < 1000; i++ { 22 | buffer <- struct{}{} 23 | mutex.Lock() 24 | actions++ 25 | mutex.Unlock() 26 | } 27 | } 28 | 29 | func main() { 30 | wg := sync.WaitGroup{} 31 | wg.Add(2) 32 | 33 | buffer = make(chan struct{}, 1) 34 | 35 | go func() { 36 | defer wg.Done() 37 | consumer() 38 | }() 39 | 40 | go func() { 41 | defer wg.Done() 42 | producer() 43 | }() 44 | 45 | wg.Wait() 46 | } 47 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/goroutine_leak_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | doWork := func(strings <-chan string) { 11 | go func() { 12 | for str := range strings { 13 | fmt.Println(str) 14 | } 15 | 16 | log.Println("doWork exited") 17 | }() 18 | } 19 | 20 | strings := make(chan string) 21 | doWork(strings) 22 | strings <- "Test" 23 | 24 | time.Sleep(time.Second) 25 | fmt.Println("Done") 26 | } 27 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/goroutine_leak_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Need to show solution and describe close 4 | 5 | // First-response-wins strategy 6 | func request() int { 7 | ch := make(chan int) 8 | for i := 0; i < 5; i++ { 9 | go func() { 10 | ch <- i // 4 goroutines will be blocked 11 | }() 12 | } 13 | 14 | return <-ch 15 | } 16 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/increment_with_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | ch := make(chan int) 9 | 10 | go func() { 11 | ch <- 1 12 | }() 13 | go func() { 14 | ch <- 1 15 | }() 16 | 17 | value := 0 18 | value += <-ch 19 | value += <-ch 20 | 21 | fmt.Println(value) 22 | } 23 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/increment_with_mutex/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func main() { 9 | mutex := sync.Mutex{} 10 | wg := sync.WaitGroup{} 11 | wg.Add(2) 12 | 13 | value := 0 14 | for i := 0; i < 2; i++ { 15 | go func() { 16 | defer wg.Done() 17 | 18 | mutex.Lock() 19 | value++ 20 | mutex.Unlock() 21 | }() 22 | } 23 | 24 | wg.Wait() 25 | 26 | fmt.Println(value) 27 | } 28 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/is_closed_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func IsClosed(ch chan int) bool { 6 | select { 7 | case <-ch: 8 | return true 9 | default: 10 | return false 11 | } 12 | } 13 | 14 | func main() { 15 | ch := make(chan int) 16 | fmt.Println(IsClosed(ch)) 17 | close(ch) 18 | fmt.Println(IsClosed(ch)) 19 | } 20 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/is_closed_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // Need to show solution 6 | 7 | func IsClosed(ch chan int) bool { 8 | select { 9 | case <-ch: 10 | return true 11 | default: 12 | return false 13 | } 14 | } 15 | 16 | func main() { 17 | ch := make(chan int, 1) 18 | ch <- 1 19 | fmt.Println(IsClosed(ch)) 20 | close(ch) 21 | fmt.Println(IsClosed(ch)) 22 | } 23 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/nil_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func WaitToClose(lhs, rhs chan struct{}) { 11 | lhsClosed, rhsClosed := false, false 12 | for !lhsClosed || !rhsClosed { 13 | select { 14 | case _, ok := <-lhs: 15 | fmt.Println("lhs", ok) 16 | if !ok { 17 | lhsClosed = true 18 | } 19 | case _, ok := <-rhs: 20 | fmt.Println("rhs", ok) 21 | if !ok { 22 | rhsClosed = true 23 | } 24 | } 25 | } 26 | } 27 | 28 | func main() { 29 | lhs := make(chan struct{}, 1) 30 | rhs := make(chan struct{}, 1) 31 | 32 | wg := sync.WaitGroup{} 33 | wg.Add(1) 34 | 35 | go func() { 36 | defer wg.Done() 37 | WaitToClose(lhs, rhs) 38 | }() 39 | 40 | lhs <- struct{}{} 41 | rhs <- struct{}{} 42 | 43 | close(lhs) 44 | close(rhs) 45 | 46 | wg.Wait() 47 | } 48 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/nil_channel_task/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch := make(chan int, 1) 7 | for done := false; !done; { 8 | select { 9 | default: 10 | fmt.Println(3) 11 | done = true 12 | case <-ch: 13 | fmt.Println(2) 14 | ch = nil 15 | case ch <- 1: 16 | fmt.Println(1) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/non_blocking_channels_correct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func tryToReadFromChannel(ch chan string) (string, bool) { 4 | select { 5 | case value := <-ch: 6 | return value, true 7 | default: 8 | return "", false 9 | } 10 | } 11 | 12 | func tryToWriteToChannel(ch chan string, value string) bool { 13 | select { 14 | case ch <- value: 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | 21 | func tryToReadOrWrite(ch1 chan string, ch2 chan string) { 22 | select { 23 | case <-ch1: 24 | case ch2 <- "test": 25 | default: 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/non_blocking_channels_incorrect/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func tryToReadFromChannel(ch chan string) (string, bool) { 4 | if len(ch) != 0 { 5 | value := <-ch 6 | return value, true 7 | } else { 8 | return "", false 9 | } 10 | } 11 | 12 | func tryToWriteToChannel(ch chan string, value string) bool { 13 | if len(ch) < cap(ch) { 14 | ch <- value 15 | return true 16 | } else { 17 | return false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/operations_with_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func writeToNilChannel() { 8 | var ch chan int 9 | ch <- 1 10 | } 11 | 12 | func writeToClosedChannel() { 13 | ch := make(chan int, 2) 14 | close(ch) 15 | ch <- 20 16 | } 17 | 18 | // Descibe read after close 19 | 20 | func readFromChannel() { 21 | ch := make(chan int, 2) 22 | ch <- 10 23 | ch <- 20 24 | 25 | val, ok := <-ch 26 | fmt.Println(val, ok) 27 | 28 | close(ch) 29 | val, ok = <-ch 30 | fmt.Println(val, ok) 31 | 32 | val, ok = <-ch 33 | fmt.Println(val, ok) 34 | } 35 | 36 | func readAnyChannels() { 37 | ch1 := make(chan int) 38 | ch2 := make(chan int) 39 | 40 | go func() { 41 | ch1 <- 100 42 | }() 43 | 44 | go func() { 45 | ch2 <- 200 46 | }() 47 | 48 | select { 49 | case val1 := <-ch1: 50 | fmt.Println(val1) 51 | case val2 := <-ch2: 52 | fmt.Println(val2) 53 | } 54 | } 55 | 56 | func readFromNilChannel() { 57 | var ch chan int 58 | <-ch 59 | } 60 | 61 | func rangeNilChannel() { 62 | var ch chan int 63 | for range ch { 64 | 65 | } 66 | } 67 | 68 | func closeNilChannel() { 69 | var ch chan int 70 | close(ch) 71 | } 72 | 73 | func closeChannelAnyTimes() { 74 | ch := make(chan int) 75 | close(ch) 76 | close(ch) 77 | } 78 | 79 | func compareChannels() { 80 | ch1 := make(chan int) 81 | ch2 := make(chan int) 82 | 83 | equal1 := ch1 == ch2 84 | equal2 := ch1 == ch1 85 | 86 | fmt.Println(equal1) 87 | fmt.Println(equal2) 88 | } 89 | 90 | func main() { 91 | } 92 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/prioritization/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func producer(ch chan<- int) { 11 | for { 12 | ch <- 1 13 | time.Sleep(time.Second) 14 | } 15 | } 16 | 17 | func main() { 18 | ch1 := make(chan int) // more prioritized 19 | ch2 := make(chan int) 20 | 21 | go producer(ch1) 22 | go producer(ch2) 23 | 24 | for { 25 | select { 26 | case value := <-ch1: 27 | fmt.Println(value) 28 | return 29 | case value := <-ch2: 30 | fmt.Println(value) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/prioritization_weight/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | ch1 := make(chan struct{}, 1) 7 | ch2 := make(chan struct{}, 1) 8 | 9 | close(ch1) 10 | close(ch2) 11 | 12 | ch1Value := 0.0 13 | ch2Value := 0.0 14 | 15 | for i := 0; i < 100000; i++ { 16 | select { 17 | case <-ch1: 18 | ch1Value++ 19 | case <-ch1: 20 | ch1Value++ 21 | case <-ch2: 22 | ch2Value++ 23 | } 24 | } 25 | 26 | fmt.Println(ch1Value / ch2Value) 27 | } 28 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/producer_consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func producer(ch chan int) { 9 | defer close(ch) 10 | for i := 0; i < 5; i++ { 11 | ch <- i 12 | } 13 | } 14 | 15 | func consumer(ch chan int) { 16 | 17 | /* 18 | for { 19 | select { 20 | case value, opened := <-ch: 21 | if !opened { 22 | return 23 | } 24 | 25 | fmt.Println(value) 26 | } 27 | } 28 | */ 29 | 30 | for value := range ch { // syntax sugar 31 | fmt.Println(value) 32 | } 33 | } 34 | 35 | func main() { 36 | ch := make(chan int) 37 | wg := sync.WaitGroup{} 38 | wg.Add(2) 39 | 40 | go func() { 41 | defer wg.Done() 42 | producer(ch) 43 | }() 44 | 45 | go func() { 46 | defer wg.Done() 47 | consumer(ch) 48 | }() 49 | 50 | wg.Wait() 51 | } 52 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/select/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func async1() chan string { 9 | ch := make(chan string) 10 | go func() { 11 | time.Sleep(1 * time.Second) 12 | ch <- "async1 result" 13 | }() 14 | return ch 15 | } 16 | 17 | func async2() chan string { 18 | ch := make(chan string) 19 | go func() { 20 | time.Sleep(1 * time.Second) 21 | ch <- "async2 result" 22 | }() 23 | return ch 24 | } 25 | 26 | func main() { 27 | ch1 := async1() 28 | ch2 := async2() 29 | 30 | select { 31 | case result := <-ch1: 32 | fmt.Println(result) 33 | case result := <-ch2: 34 | fmt.Println(result) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/select_forever/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func way1() { 4 | make(chan struct{}) <- struct{}{} 5 | // or 6 | make(chan<- struct{}) <- struct{}{} 7 | } 8 | 9 | func way2() { 10 | <-make(chan struct{}) 11 | // or 12 | <-make(<-chan struct{}) 13 | // or 14 | for range make(<-chan struct{}) { 15 | } 16 | } 17 | 18 | func way3() { 19 | chan struct{}(nil) <- struct{}{} 20 | // or 21 | <-chan struct{}(nil) 22 | // or 23 | for range chan struct{}(nil) { 24 | } 25 | } 26 | 27 | func way4() { 28 | select {} 29 | } 30 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/select_with_break_and_continue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | data := make(chan int) 7 | go func() { 8 | for i := 1; i <= 4; i++ { 9 | data <- i 10 | } 11 | close(data) 12 | }() 13 | 14 | for { 15 | value := 0 16 | opened := true 17 | 18 | select { 19 | case value, opened = <-data: 20 | if value == 2 { 21 | continue 22 | } else if value == 3 { 23 | break 24 | } 25 | 26 | if !opened { 27 | return 28 | } 29 | } 30 | 31 | fmt.Println(value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/select_with_main_goroutine/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "runtime" 4 | 5 | func doSomething() { 6 | for { 7 | runtime.Gosched() 8 | } 9 | } 10 | 11 | func main() { 12 | go doSomething() 13 | go doSomething() 14 | select {} 15 | } 16 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/sequential_execution/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func FetchData1() chan int { 11 | ch := make(chan int) 12 | go func() { 13 | time.Sleep(time.Second * 2) 14 | ch <- 10 15 | }() 16 | 17 | return ch 18 | } 19 | 20 | func FetchData2() chan int { 21 | ch := make(chan int) 22 | go func() { 23 | time.Sleep(time.Second * 2) 24 | ch <- 20 25 | }() 26 | 27 | return ch 28 | } 29 | 30 | func Process(value1, value2 int) { 31 | // Processing... 32 | } 33 | 34 | func main() { 35 | start := time.Now() 36 | Process(<-FetchData1(), <-FetchData2()) 37 | fmt.Println(time.Now().Sub(start)) 38 | } 39 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/signals/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func notifier(signals chan struct{}) { 9 | signals <- struct{}{} 10 | } 11 | 12 | func subscriber(signals chan struct{}) { 13 | <-signals 14 | fmt.Println("signaled") 15 | } 16 | 17 | func main() { 18 | signals := make(chan struct{}) 19 | wg := sync.WaitGroup{} 20 | wg.Add(2) 21 | 22 | go func() { 23 | defer wg.Done() 24 | notifier(signals) 25 | }() 26 | 27 | go func() { 28 | defer wg.Done() 29 | subscriber(signals) 30 | }() 31 | 32 | wg.Wait() 33 | } 34 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/unidirectional_channels/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func in(in chan<- int) { 6 | in <- 100 7 | close(in) 8 | } 9 | 10 | func out(out <-chan int) { 11 | fmt.Println(<-out) 12 | } 13 | 14 | func main() { 15 | var ch = make(chan int, 1) 16 | 17 | in(ch) 18 | out(ch) 19 | } 20 | -------------------------------------------------------------------------------- /lessons/5_lesson_channels/write_after_close/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | func main() { 8 | ch := make(chan int) 9 | go func() { 10 | ch <- 1 11 | }() 12 | 13 | time.Sleep(500 * time.Millisecond) 14 | 15 | close(ch) 16 | <-ch 17 | 18 | time.Sleep(100 * time.Millisecond) 19 | } 20 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/after_and_tick/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // Need to show solution 9 | 10 | func main() { 11 | for { 12 | select { 13 | case <-time.After(5 * time.Second): 14 | fmt.Println("timeout") 15 | return 16 | case <-time.Tick(time.Second): 17 | fmt.Println("tick") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/barrier/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Barrier struct { 9 | count int 10 | size int 11 | mutex sync.Mutex 12 | beforeCh chan int 13 | afterCh chan int 14 | } 15 | 16 | func NewBarrier(size int) *Barrier { 17 | return &Barrier{ 18 | size: size, 19 | beforeCh: make(chan int, size), 20 | afterCh: make(chan int, size), 21 | } 22 | } 23 | 24 | func (b *Barrier) Before() { 25 | b.mutex.Lock() 26 | 27 | b.count++ 28 | if b.count == b.size { 29 | for i := 0; i < b.size; i++ { 30 | b.beforeCh <- 1 31 | } 32 | } 33 | 34 | b.mutex.Unlock() 35 | <-b.beforeCh 36 | } 37 | 38 | func (b *Barrier) After() { 39 | b.mutex.Lock() 40 | 41 | b.count-- 42 | if b.count == 0 { 43 | for i := 0; i < b.size; i++ { 44 | b.afterCh <- 1 45 | } 46 | } 47 | 48 | b.mutex.Unlock() 49 | <-b.afterCh 50 | } 51 | 52 | func main() { 53 | wg := sync.WaitGroup{} 54 | wg.Add(3) 55 | 56 | bootstrap := func() { 57 | fmt.Println("bootstrap") 58 | } 59 | 60 | work := func() { 61 | fmt.Println("work") 62 | } 63 | 64 | barrier := NewBarrier(3) 65 | for i := 0; i < 3; i++ { 66 | go func() { 67 | defer wg.Done() 68 | for j := 0; j < 3; j++ { 69 | // wait for all workers to finish previous loop 70 | barrier.Before() 71 | bootstrap() 72 | // wait for other workers to bootstrap 73 | barrier.After() 74 | work() 75 | } 76 | }() 77 | } 78 | 79 | wg.Wait() 80 | } 81 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/bridge/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Bridge(in chan chan string) chan string { 8 | out := make(chan string) 9 | go func() { 10 | defer close(out) 11 | for ch := range in { 12 | for value := range ch { 13 | out <- value 14 | } 15 | } 16 | }() 17 | 18 | return out 19 | } 20 | 21 | func main() { 22 | in := make(chan chan string) 23 | go func() { 24 | innerCh1 := make(chan string, 3) 25 | for i := 0; i < 3; i++ { 26 | innerCh1 <- "inner-ch-1" 27 | } 28 | 29 | close(innerCh1) 30 | 31 | innerCh2 := make(chan string, 3) 32 | for i := 0; i < 3; i++ { 33 | innerCh2 <- "inner-ch-2" 34 | } 35 | 36 | close(innerCh2) 37 | 38 | in <- innerCh1 39 | in <- innerCh2 40 | close(in) 41 | }() 42 | 43 | for value := range Bridge(in) { 44 | fmt.Println(value) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/done_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func doWork(closeCh chan struct{}) <-chan struct{} { 8 | closeDoneCh := make(chan struct{}) 9 | 10 | go func() { 11 | defer close(closeDoneCh) 12 | 13 | for { 14 | select { 15 | case <-closeCh: 16 | return 17 | default: 18 | // ... do some work 19 | } 20 | } 21 | }() 22 | 23 | return closeDoneCh 24 | } 25 | 26 | func main() { 27 | closeCh := make(chan struct{}) 28 | closeDoneCh := doWork(closeCh) 29 | 30 | close(closeCh) 31 | <-closeDoneCh 32 | 33 | fmt.Println("terminated") 34 | } 35 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/done_channel_with_struct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Worker struct { 9 | closeCh chan struct{} 10 | closeDoneCh chan struct{} 11 | } 12 | 13 | func NewWorker() Worker { 14 | worker := Worker{ 15 | closeCh: make(chan struct{}), 16 | closeDoneCh: make(chan struct{}), 17 | } 18 | 19 | go func() { 20 | ticker := time.NewTicker(time.Second) 21 | defer func() { 22 | ticker.Stop() 23 | close(worker.closeDoneCh) 24 | }() 25 | 26 | for { 27 | select { 28 | case <-worker.closeCh: 29 | fmt.Println("Worker was stopped") 30 | 31 | return 32 | default: 33 | } 34 | 35 | select { 36 | case <-worker.closeCh: 37 | fmt.Println("Worker was stopped") 38 | return 39 | case <-ticker.C: 40 | fmt.Println("Do something") 41 | } 42 | } 43 | }() 44 | 45 | return worker 46 | } 47 | 48 | func (w *Worker) Shutdown() { 49 | close(w.closeCh) 50 | <-w.closeDoneCh 51 | } 52 | 53 | func main() { 54 | worker := NewWorker() 55 | time.Sleep(5 * time.Second) 56 | worker.Shutdown() 57 | } 58 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/dynamic_select_with_reflection/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int, 1) 10 | vch := reflect.ValueOf(ch) 11 | 12 | succeed := vch.TrySend(reflect.ValueOf(100)) 13 | fmt.Println(succeed, vch.Len(), vch.Cap()) 14 | 15 | branches := []reflect.SelectCase{ 16 | {Dir: reflect.SelectDefault}, 17 | {Dir: reflect.SelectRecv, Chan: vch}, 18 | } 19 | 20 | index, vRecv, recvOk := reflect.Select(branches) 21 | fmt.Println(index, vRecv, recvOk) 22 | 23 | vch.Close() 24 | } 25 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/errgroup_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type ErrGroup struct { 11 | err error 12 | wg sync.WaitGroup 13 | once sync.Once 14 | doneCh chan struct{} 15 | } 16 | 17 | func NewErrGroup() *ErrGroup { 18 | return &ErrGroup{ 19 | doneCh: make(chan struct{}), 20 | } 21 | } 22 | 23 | func (eg *ErrGroup) Go(task func() error) { 24 | select { 25 | case <-eg.doneCh: 26 | return 27 | default: 28 | } 29 | 30 | eg.wg.Add(1) 31 | go func() { 32 | defer eg.wg.Done() 33 | 34 | select { 35 | case <-eg.doneCh: 36 | return 37 | default: 38 | if err := task(); err != nil { 39 | eg.once.Do(func() { 40 | eg.err = err 41 | close(eg.doneCh) 42 | }) 43 | } 44 | } 45 | }() 46 | } 47 | 48 | func (eg *ErrGroup) Wait() error { 49 | eg.wg.Wait() 50 | return eg.err 51 | } 52 | 53 | func main() { 54 | group := NewErrGroup() 55 | group.Go(func() error { 56 | fmt.Println("started") 57 | return errors.New("error") 58 | }) 59 | 60 | time.Sleep(time.Second) 61 | for i := 0; i < 5; i++ { 62 | group.Go(func() error { 63 | fmt.Println("started after timeout") 64 | return nil 65 | }) 66 | } 67 | 68 | if err := group.Wait(); err != nil { 69 | fmt.Println(err.Error()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/fan_in/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func MergeChannels(channels ...<-chan int) <-chan int { 10 | wg := sync.WaitGroup{} 11 | wg.Add(len(channels)) 12 | 13 | result := make(chan int) 14 | for _, channel := range channels { 15 | go func(ch <-chan int) { 16 | defer wg.Done() 17 | for value := range ch { 18 | result <- value 19 | } 20 | }(channel) 21 | } 22 | 23 | go func() { 24 | wg.Wait() 25 | close(result) 26 | }() 27 | 28 | return result 29 | } 30 | 31 | func main() { 32 | ch1 := make(chan int) 33 | ch2 := make(chan int) 34 | ch3 := make(chan int) 35 | 36 | go func() { 37 | defer func() { 38 | close(ch1) 39 | close(ch2) 40 | close(ch3) 41 | }() 42 | 43 | for i := 0; i < 100; i += 3 { 44 | ch1 <- i 45 | ch2 <- i + 1 46 | ch3 <- i + 2 47 | time.Sleep(100 * time.Millisecond) 48 | } 49 | }() 50 | 51 | for value := range MergeChannels(ch1, ch2, ch3) { 52 | fmt.Println(value) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/fan_out/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func SplitChannel(inputCh <-chan int, n int) []chan int { 10 | if n <= 0 { 11 | n = 1 12 | } 13 | 14 | outputCh := make([]chan int, n) 15 | for i := 0; i < n; i++ { 16 | outputCh[i] = make(chan int) 17 | } 18 | 19 | go func() { 20 | idx := 0 21 | for value := range inputCh { 22 | outputCh[idx] <- value 23 | idx = (idx + 1) % n 24 | } 25 | 26 | for _, ch := range outputCh { 27 | close(ch) 28 | } 29 | }() 30 | 31 | return outputCh 32 | } 33 | 34 | func main() { 35 | ch := make(chan int) 36 | 37 | go func() { 38 | defer close(ch) 39 | for i := 0; i < 10; i++ { 40 | ch <- i 41 | time.Sleep(100 * time.Millisecond) 42 | } 43 | }() 44 | 45 | channels := SplitChannel(ch, 2) 46 | 47 | wg := sync.WaitGroup{} 48 | wg.Add(2) 49 | 50 | go func() { 51 | defer wg.Done() 52 | for value := range channels[0] { 53 | fmt.Println("ch1: ", value) 54 | } 55 | }() 56 | 57 | go func() { 58 | defer wg.Done() 59 | for value := range channels[1] { 60 | fmt.Println("ch2: ", value) 61 | } 62 | }() 63 | 64 | wg.Wait() 65 | } 66 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/filter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func Filter(input <-chan int) <-chan int { 9 | output := make(chan int) 10 | 11 | go func() { 12 | for number := range input { 13 | if number%2 != 0 { 14 | output <- number 15 | } 16 | } 17 | 18 | close(output) 19 | }() 20 | 21 | return output 22 | } 23 | 24 | func main() { 25 | in := make(chan int) 26 | 27 | go func() { 28 | defer close(in) 29 | for i := 0; i < 10; i++ { 30 | in <- i 31 | time.Sleep(100 * time.Millisecond) 32 | } 33 | }() 34 | 35 | for value := range Filter(in) { 36 | fmt.Println(value) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/future/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Future struct { 9 | result chan interface{} 10 | } 11 | 12 | func NewFuture(task func() interface{}) *Future { 13 | future := &Future{ 14 | result: make(chan interface{}), 15 | } 16 | 17 | go func() { 18 | defer close(future.result) 19 | future.result <- task() 20 | }() 21 | 22 | return future 23 | } 24 | 25 | func (f *Future) Get() interface{} { 26 | return <-f.result 27 | } 28 | 29 | func main() { 30 | callback := func() interface{} { 31 | time.Sleep(time.Second) 32 | return "success" 33 | } 34 | 35 | future := NewFuture(callback) 36 | result := future.Get() 37 | fmt.Println(result) 38 | } 39 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/future_with_promise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Future struct { 9 | result <-chan interface{} 10 | } 11 | 12 | func NewFuture(result <-chan interface{}) Future { 13 | return Future{ 14 | result: result, 15 | } 16 | } 17 | 18 | func (f *Future) Get() interface{} { 19 | return <-f.result 20 | } 21 | 22 | type Promise struct { 23 | result chan interface{} 24 | promised bool 25 | } 26 | 27 | func NewPromise() Promise { 28 | return Promise{ 29 | result: make(chan interface{}, 1), 30 | } 31 | } 32 | 33 | // Set don't use with any goroutines 34 | func (p *Promise) Set(value interface{}) { 35 | if p.promised { 36 | return 37 | } 38 | 39 | p.promised = true 40 | p.result <- value 41 | close(p.result) 42 | } 43 | 44 | func (p *Promise) GetFuture() Future { 45 | return NewFuture(p.result) 46 | } 47 | 48 | func main() { 49 | promise := NewPromise() 50 | go func() { 51 | time.Sleep(time.Second) 52 | promise.Set("Test") 53 | }() 54 | 55 | future := promise.GetFuture() 56 | value := future.Get() 57 | fmt.Println(value) 58 | } 59 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/generator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func GenerateWithClosure(number int) func() int { 6 | return func() int { 7 | r := number 8 | number++ 9 | return r 10 | } 11 | } 12 | 13 | func GenerateWithChannel(start, end int) chan int { 14 | ch := make(chan int) 15 | go func() { 16 | defer close(ch) 17 | for number := start; number <= end; number++ { 18 | ch <- number 19 | } 20 | }() 21 | 22 | return ch 23 | } 24 | 25 | func main() { 26 | generator := GenerateWithClosure(100) 27 | for i := 0; i <= 200; i++ { 28 | fmt.Println(generator()) 29 | } 30 | 31 | /*for number := range GenerateWithChannel(100, 200) { 32 | fmt.Println(number) 33 | }*/ 34 | } 35 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/graceful_shutdown/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "sync" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | interruptCh := make(chan os.Signal, 1) 14 | signal.Notify(interruptCh, syscall.SIGINT, syscall.SIGTERM) 15 | 16 | wg := sync.WaitGroup{} 17 | wg.Add(1) 18 | 19 | go func() { 20 | ticker := time.NewTicker(time.Second) 21 | defer func() { 22 | ticker.Stop() 23 | wg.Done() 24 | }() 25 | 26 | for { 27 | select { 28 | case <-interruptCh: 29 | log.Print("Worker was stopped") 30 | return 31 | default: 32 | } 33 | 34 | select { 35 | case <-interruptCh: 36 | log.Print("Worker was stopped") 37 | return 38 | case <-ticker.C: 39 | log.Print("Do something") 40 | } 41 | } 42 | }() 43 | 44 | wg.Wait() 45 | log.Print("Application was stopped") 46 | } 47 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/moving_later/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type DistributedDatabase struct{} 10 | 11 | func (d *DistributedDatabase) Query(address string, key string) string { 12 | time.Sleep(time.Second * time.Duration(rand.Intn(3))) 13 | return fmt.Sprintf("[%s]: value", address) 14 | } 15 | 16 | var database DistributedDatabase 17 | 18 | func DistributedQuery(addresses []string, query string) string { 19 | responseCh := make(chan string, 1) // buffered necessary 20 | for _, address := range addresses { 21 | go func(address string) { 22 | select { 23 | case responseCh <- database.Query(address, query): 24 | default: 25 | return 26 | } 27 | }(address) 28 | } 29 | 30 | return <-responseCh 31 | } 32 | 33 | func main() { 34 | addresses := []string{ 35 | "127.0.0.1", 36 | "127.0.0.2", 37 | "127.0.0.3", 38 | } 39 | 40 | value := DistributedQuery(addresses, "GET key_1") 41 | fmt.Println(value) 42 | } 43 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/or_channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func or(channels ...<-chan struct{}) <-chan struct{} { 9 | switch len(channels) { 10 | case 0: 11 | return nil 12 | case 1: 13 | return channels[0] 14 | } 15 | 16 | orDone := make(chan struct{}) 17 | 18 | go func() { 19 | defer close(orDone) 20 | switch len(channels) { 21 | case 2: 22 | select { 23 | case <-channels[0]: 24 | case <-channels[1]: 25 | } 26 | default: 27 | select { 28 | case <-orDone: 29 | case <-channels[0]: 30 | case <-channels[1]: 31 | case <-channels[2]: 32 | case <-or(channels[3:]...): 33 | } 34 | } 35 | }() 36 | 37 | return orDone 38 | } 39 | 40 | func main() { 41 | after := func(after time.Duration) <-chan struct{} { 42 | c := make(chan struct{}) 43 | go func() { 44 | defer close(c) 45 | time.Sleep(after) 46 | }() 47 | return c 48 | } 49 | 50 | start := time.Now() 51 | 52 | <-or( 53 | after(2*time.Hour), 54 | after(5*time.Minute), 55 | after(1*time.Second), 56 | after(1*time.Hour), 57 | after(10*time.Second), 58 | ) 59 | 60 | fmt.Printf("Called after: %s", time.Since(start)) 61 | } 62 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/or_done/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func OrDone(done chan struct{}, in chan string) chan string { 9 | out := make(chan string) 10 | 11 | go func() { 12 | defer close(out) 13 | 14 | for { 15 | select { 16 | case <-done: 17 | return 18 | default: 19 | } 20 | 21 | select { 22 | case value, ok := <-in: 23 | if !ok { 24 | return 25 | } 26 | 27 | out <- value 28 | case <-done: 29 | return 30 | } 31 | } 32 | }() 33 | 34 | return out 35 | } 36 | 37 | func main() { 38 | in := make(chan string) 39 | go func() { 40 | for i := 0; i < 5; i++ { 41 | time.Sleep(500 * time.Millisecond) 42 | in <- "test" 43 | } 44 | }() 45 | 46 | done := make(chan struct{}) 47 | go func() { 48 | time.Sleep(time.Second) 49 | close(done) 50 | }() 51 | 52 | for value := range OrDone(done, in) { 53 | fmt.Println(value) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/parallel_pipeline/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func parse(in chan string) <-chan string { 10 | out := make(chan string) 11 | go func() { 12 | defer close(out) 13 | for data := range in { 14 | time.Sleep(50 * time.Millisecond) 15 | out <- fmt.Sprintf("parsed - %s", data) 16 | } 17 | }() 18 | 19 | return out 20 | } 21 | 22 | func send(in <-chan string) <-chan string { 23 | out := make(chan string) 24 | wg := sync.WaitGroup{} 25 | wg.Add(2) 26 | 27 | go func() { 28 | defer wg.Done() 29 | for data := range in { 30 | time.Sleep(100 * time.Millisecond) 31 | out <- fmt.Sprintf("sent - %s", data) 32 | } 33 | }() 34 | 35 | go func() { 36 | defer wg.Done() 37 | for data := range in { 38 | time.Sleep(100 * time.Millisecond) 39 | out <- fmt.Sprintf("sent - %s", data) 40 | } 41 | }() 42 | 43 | go func() { 44 | wg.Wait() 45 | close(out) 46 | }() 47 | 48 | return out 49 | } 50 | 51 | func main() { 52 | ch := make(chan string) 53 | go func() { 54 | defer close(ch) 55 | for i := 0; i < 5; i++ { 56 | ch <- "value" 57 | } 58 | }() 59 | 60 | out := send(parse(ch)) 61 | for value := range out { 62 | fmt.Println(value) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/pipeline/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func gen(numbers ...int) <-chan int { 6 | out := make(chan int) 7 | go func() { 8 | defer close(out) 9 | for _, number := range numbers { 10 | out <- number 11 | } 12 | }() 13 | 14 | return out 15 | } 16 | 17 | func mul(in <-chan int) <-chan int { 18 | out := make(chan int) 19 | go func() { 20 | defer close(out) 21 | for number := range in { 22 | out <- number * number 23 | } 24 | }() 25 | 26 | return out 27 | } 28 | 29 | func main() { 30 | for value := range mul(gen(1, 2, 3, 4, 5)) { 31 | fmt.Println(value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/promise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type Promise struct { 9 | waitCh chan struct{} 10 | value interface{} 11 | err error 12 | } 13 | 14 | func NewPromise(task func() (interface{}, error)) *Promise { 15 | if task == nil { 16 | return nil 17 | } 18 | 19 | promise := &Promise{ 20 | waitCh: make(chan struct{}), 21 | } 22 | 23 | go func() { 24 | defer close(promise.waitCh) 25 | promise.value, promise.err = task() 26 | }() 27 | 28 | return promise 29 | } 30 | 31 | func (p *Promise) Then(successCb func(interface{}), errCb func(error)) { 32 | <-p.waitCh // can be non-blocking 33 | if p.err == nil { 34 | successCb(p.value) 35 | } else { 36 | errCb(p.err) 37 | } 38 | } 39 | 40 | func main() { 41 | callback := func() (interface{}, error) { 42 | time.Sleep(time.Second) 43 | return "ok", nil 44 | } 45 | 46 | promise := NewPromise(callback) 47 | promise.Then( 48 | func(value interface{}) { 49 | fmt.Println("success", value) 50 | }, 51 | func(err error) { 52 | fmt.Println("error", err.Error()) 53 | }, 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/rate_limiter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type LeakyBucketLimiter struct { 8 | leakyBucketCh chan struct{} 9 | } 10 | 11 | func NewLeakyBucketLimiter(limit int, period time.Duration) *LeakyBucketLimiter { 12 | limiter := &LeakyBucketLimiter{ 13 | leakyBucketCh: make(chan struct{}, limit), 14 | } 15 | 16 | leakInterval := period.Nanoseconds() / int64(limit) 17 | go limiter.startPeriodicLeak(time.Duration(leakInterval)) 18 | return limiter 19 | } 20 | 21 | func (l *LeakyBucketLimiter) startPeriodicLeak(interval time.Duration) { 22 | timer := time.NewTicker(interval) 23 | defer timer.Stop() 24 | 25 | for { 26 | select { 27 | case <-timer.C: 28 | select { 29 | case <-l.leakyBucketCh: 30 | default: 31 | } 32 | } 33 | } 34 | } 35 | 36 | func (l *LeakyBucketLimiter) Allow() bool { 37 | select { 38 | case l.leakyBucketCh <- struct{}{}: 39 | return true 40 | default: 41 | return false 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/request_with_timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | func request(chan string) 9 | 10 | func requestWithTimeout(timeout time.Duration) (string, error) { 11 | result := make(chan string) 12 | go request(result) 13 | 14 | select { 15 | case data := <-result: 16 | return data, nil 17 | case <-time.After(timeout): 18 | return "", errors.New("timeout") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/semaphore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Semaphore struct { 10 | tickets chan struct{} 11 | } 12 | 13 | func NewSemaphore(ticketsNumber int) Semaphore { 14 | return Semaphore{ 15 | tickets: make(chan struct{}, ticketsNumber), 16 | } 17 | } 18 | 19 | func (s *Semaphore) Acquire() { 20 | s.tickets <- struct{}{} 21 | } 22 | 23 | func (s *Semaphore) Release() { 24 | <-s.tickets 25 | } 26 | 27 | func (s *Semaphore) WithSemaphore(action func()) { 28 | if action == nil { 29 | return 30 | } 31 | 32 | s.Acquire() 33 | action() 34 | s.Release() 35 | } 36 | 37 | func main() { 38 | wg := sync.WaitGroup{} 39 | wg.Add(6) 40 | 41 | semaphore := NewSemaphore(5) 42 | for i := 0; i < 6; i++ { 43 | semaphore.Acquire() 44 | go func() { 45 | defer func() { 46 | wg.Done() 47 | semaphore.Release() 48 | }() 49 | 50 | fmt.Println("working...") 51 | time.Sleep(time.Second * 2) 52 | fmt.Println("exiting...") 53 | }() 54 | } 55 | 56 | wg.Wait() 57 | } 58 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/tee/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | func Tee(in chan int) (chan int, chan int) { 9 | out1 := make(chan int) 10 | out2 := make(chan int) 11 | 12 | go func() { 13 | defer close(out1) 14 | defer close(out2) 15 | 16 | for value := range in { 17 | out1 <- value 18 | out2 <- value 19 | } 20 | }() 21 | 22 | return out1, out2 23 | } 24 | 25 | func main() { 26 | ch := make(chan int) 27 | go func() { 28 | defer close(ch) 29 | for i := 0; i < 5; i++ { 30 | ch <- i 31 | } 32 | }() 33 | 34 | wg := sync.WaitGroup{} 35 | wg.Add(2) 36 | 37 | ch1, ch2 := Tee(ch) 38 | 39 | go func() { 40 | defer wg.Done() 41 | for value := range ch1 { 42 | fmt.Println("ch1: ", value) 43 | } 44 | }() 45 | 46 | go func() { 47 | defer wg.Done() 48 | for value := range ch2 { 49 | fmt.Println("ch2: ", value) 50 | } 51 | }() 52 | 53 | wg.Wait() 54 | } 55 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/ticker/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func producer(ch chan<- struct{}) { 9 | time.Sleep(5 * time.Second) 10 | ch <- struct{}{} 11 | } 12 | 13 | func main() { 14 | ch := make(chan struct{}) 15 | go producer(ch) 16 | 17 | ticker := time.NewTicker(time.Second) 18 | defer ticker.Stop() 19 | 20 | for { 21 | select { 22 | case <-ch: 23 | return 24 | case <-ticker.C: 25 | fmt.Println("tick") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/ticker_after_tick/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ticker := time.NewTicker(time.Second * 1) 10 | defer ticker.Stop() 11 | 12 | time.Sleep(time.Second * 2) 13 | fmt.Println("after sleep") 14 | 15 | /* 16 | Note, the if code block is used to discard/drain the potential 17 | ticker notification which is sent in the small period when executing 18 | the second branch code block (since Go 1.23, this has become needless). 19 | Note: since Go 1.23, the Ticker.Reset method will automatically 20 | discard/drain the potential stale ticker notification. 21 | 22 | https://go.dev/play/p/JAC8Ln1kwMz?v=goprev 23 | */ 24 | 25 | ticker.Reset(time.Second * 3) 26 | 27 | <-ticker.C 28 | fmt.Println("first") 29 | <-ticker.C 30 | fmt.Println("second") 31 | } 32 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/ticker_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type Ticker struct { 10 | C chan struct{} 11 | interval int64 12 | closed atomic.Bool 13 | } 14 | 15 | func NewTicker(interval time.Duration) *Ticker { 16 | ticker := &Ticker{ 17 | C: make(chan struct{}, 1), 18 | interval: int64(interval), 19 | } 20 | 21 | go func() { 22 | for !ticker.closed.Load() { 23 | duration := atomic.LoadInt64(&ticker.interval) 24 | time.Sleep(time.Duration(duration)) 25 | ticker.C <- struct{}{} 26 | } 27 | }() 28 | 29 | return ticker 30 | } 31 | 32 | func (t *Ticker) Stop() { 33 | t.closed.Store(true) 34 | } 35 | 36 | func (t *Ticker) Reset(interval time.Duration) { 37 | // can discard previous tick 38 | atomic.StoreInt64(&t.interval, int64(interval)) 39 | } 40 | 41 | func main() { 42 | ticker := time.NewTicker(time.Second) 43 | defer ticker.Stop() 44 | 45 | for range ticker.C { 46 | fmt.Println("tick") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/timer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func producer(ch chan<- struct{}) { 9 | time.Sleep(5 * time.Second) 10 | ch <- struct{}{} 11 | } 12 | 13 | func main() { 14 | ch := make(chan struct{}) 15 | go producer(ch) 16 | 17 | timer := time.NewTimer(time.Second) 18 | defer timer.Stop() 19 | 20 | for { 21 | select { 22 | case <-ch: 23 | return 24 | case <-timer.C: 25 | fmt.Println("tick") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lessons/6_lesson_channel_patterns/transformer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func Transform(in <-chan int) <-chan int { 6 | result := make(chan int) 7 | go func() { 8 | defer close(result) 9 | for number := range in { 10 | result <- number * number 11 | } 12 | }() 13 | 14 | return result 15 | } 16 | 17 | func main() { 18 | ch := make(chan int) 19 | go func() { 20 | defer close(ch) 21 | for i := 0; i < 5; i++ { 22 | ch <- i 23 | } 24 | }() 25 | 26 | for number := range Transform(ch) { 27 | fmt.Println(number) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/after_done/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // context.AfterFunc 10 | 11 | func WithCtxAfterFunc(ctx context.Context, action func()) { 12 | if action != nil { 13 | go func() { 14 | <-ctx.Done() 15 | action() 16 | }() 17 | } 18 | } 19 | 20 | func main() { 21 | ctx, cancel := context.WithCancel(context.Background()) 22 | WithCtxAfterFunc(ctx, func() { 23 | fmt.Println("after") 24 | }) 25 | 26 | cancel() 27 | 28 | time.Sleep(100 * time.Millisecond) 29 | } 30 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/chech_cancel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "context" 4 | 5 | func incorrectCheck(ctx context.Context, stream <-chan string) { 6 | data := <-stream 7 | _ = data 8 | } 9 | 10 | func correctCheck(ctx context.Context, stream <-chan string) { 11 | select { 12 | case data := <-stream: 13 | _ = data 14 | case <-ctx.Done(): 15 | return 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_handling/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | func Query(string) string 9 | 10 | func DoQeury(qyeryStr string) (string, error) { 11 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) 12 | defer cancel() 13 | 14 | var resultCh chan string 15 | go func() { 16 | result := Query(qyeryStr) 17 | resultCh <- result 18 | }() 19 | 20 | select { 21 | case <-ctx.Done(): 22 | return "", ctx.Err() 23 | case result := <-resultCh: 24 | return result, nil 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_inheritance_1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 11 | defer cancel() 12 | 13 | makeRequest(ctx) 14 | } 15 | 16 | func makeRequest(ctx context.Context) { 17 | timer := time.NewTimer(5 * time.Second) 18 | defer timer.Stop() 19 | 20 | newCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 21 | defer cancel() 22 | 23 | select { 24 | case <-newCtx.Done(): 25 | fmt.Println("canceled") 26 | case <-timer.C: 27 | fmt.Println("timer") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_inheritance_2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 11 | defer cancel() 12 | 13 | _, cancel = context.WithCancel(ctx) 14 | cancel() 15 | 16 | if ctx.Err() != nil { 17 | fmt.Println("canceled") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_cancel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | func receiveWeather(ctx context.Context, result chan struct{}, idx int) { 12 | randomTime := time.Duration(rand.Intn(5000)) * time.Millisecond 13 | 14 | timer := time.NewTimer(randomTime) 15 | defer timer.Stop() 16 | 17 | select { 18 | case <-timer.C: 19 | fmt.Printf("finished: %d\n", idx) 20 | result <- struct{}{} 21 | case <-ctx.Done(): 22 | fmt.Printf("canceled: %d\n", idx) 23 | } 24 | } 25 | 26 | func main() { 27 | wg := sync.WaitGroup{} 28 | wg.Add(10) 29 | 30 | ctx, cancel := context.WithCancel(context.Background()) 31 | 32 | result := make(chan struct{}, 10) 33 | for i := 0; i < 10; i++ { 34 | go func(idx int) { 35 | defer wg.Done() 36 | receiveWeather(ctx, result, idx) 37 | }(i) 38 | } 39 | 40 | <-result 41 | cancel() 42 | 43 | wg.Wait() 44 | } 45 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_cancel_cause/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithCancelCause(context.Background()) 11 | cancel(errors.New("error")) 12 | 13 | fmt.Println(ctx.Err()) 14 | fmt.Println(context.Cause(ctx)) 15 | } 16 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_http_client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) 12 | defer cancel() 13 | 14 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil) 15 | if err != nil { 16 | fmt.Println(err.Error()) 17 | } 18 | 19 | if _, err = http.DefaultClient.Do(req); err != nil { 20 | fmt.Println(err.Error()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_http_server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | helloWorldHandler := http.HandlerFunc(handle) 11 | http.Handle("/welcome", injectTraceID(helloWorldHandler)) 12 | _ = http.ListenAndServe(":8080", nil) 13 | } 14 | 15 | func handle(_ http.ResponseWriter, r *http.Request) { 16 | value, ok := r.Context().Value("trace_id").(string) 17 | if ok { 18 | fmt.Println(value) 19 | } 20 | 21 | makeRequest(r.Context()) 22 | } 23 | 24 | func makeRequest(_ context.Context) { 25 | // requesting to database with context 26 | } 27 | 28 | func injectTraceID(next http.Handler) http.Handler { 29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 | ctx := context.WithValue(r.Context(), "trace_id", "12-21-33") 31 | req := r.WithContext(ctx) 32 | next.ServeHTTP(w, req) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func makeRequest(ctx context.Context) { 10 | timer := time.NewTimer(5 * time.Second) 11 | defer timer.Stop() 12 | 13 | select { 14 | case <-timer.C: 15 | fmt.Println("finished") 16 | case <-ctx.Done(): 17 | fmt.Println("canceled") 18 | } 19 | } 20 | 21 | func main() { 22 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 23 | defer cancel() 24 | 25 | makeRequest(ctx) 26 | } 27 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_timeout_cause/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | ctx, cancel := context.WithTimeoutCause(context.Background(), time.Second, errors.New("timeout")) 12 | defer cancel() // show difference 13 | 14 | <-ctx.Done() 15 | 16 | fmt.Println(ctx.Err()) 17 | fmt.Println(context.Cause(ctx)) 18 | } 19 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_timeout_implementation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | type Context struct { 11 | done chan struct{} 12 | closed int32 13 | } 14 | 15 | func WithTimeout(parent Context, duration time.Duration) (*Context, func()) { 16 | if atomic.LoadInt32(&parent.closed) == 1 { 17 | return nil, nil // don't use nil 18 | } 19 | 20 | ctx := &Context{ 21 | done: make(chan struct{}), 22 | } 23 | 24 | cancel := func() { 25 | if atomic.CompareAndSwapInt32(&ctx.closed, 0, 1) { 26 | close(ctx.done) 27 | } 28 | } 29 | 30 | go func() { 31 | timer := time.NewTimer(duration) 32 | defer timer.Stop() 33 | 34 | select { 35 | case <-parent.Done(): 36 | case <-timer.C: 37 | } 38 | 39 | cancel() 40 | }() 41 | 42 | return ctx, cancel 43 | } 44 | 45 | func (c *Context) Done() <-chan struct{} { 46 | return c.done 47 | } 48 | 49 | func (c *Context) Err() error { 50 | select { 51 | case <-c.done: 52 | return errors.New("context deadline exceeded") 53 | default: 54 | return nil 55 | } 56 | } 57 | 58 | func (c *Context) Deadline() (time.Time, bool) { 59 | // not implemented 60 | return time.Time{}, false 61 | } 62 | 63 | func (c *Context) Value(any) any { 64 | // not implemented 65 | return nil 66 | } 67 | 68 | func main() { 69 | ctx, cancel := WithTimeout(Context{}, time.Second) 70 | defer cancel() 71 | 72 | timer := time.NewTimer(5 * time.Second) 73 | defer timer.Stop() 74 | 75 | select { 76 | case <-timer.C: 77 | fmt.Println("finished") 78 | case <-ctx.Done(): 79 | fmt.Println("canceled") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_value/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | traceCtx := context.WithValue(context.Background(), "trace_id", "12-21-33") 10 | makeRequest(traceCtx) 11 | 12 | oldValue, ok := traceCtx.Value("trace_id").(string) 13 | if ok { 14 | fmt.Println("mainValue", oldValue) 15 | } 16 | } 17 | 18 | func makeRequest(ctx context.Context) { 19 | oldValue, ok := ctx.Value("trace_id").(string) 20 | if ok { 21 | fmt.Println("oldValue", oldValue) 22 | } 23 | 24 | newCtx := context.WithValue(ctx, "trace_id", "22-22-22") 25 | newValue, ok := newCtx.Value("trace_id").(string) 26 | if ok { 27 | fmt.Println("newValue", newValue) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_value_inheritance/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | traceCtx := context.WithValue(context.Background(), "trace_id", "12-21-33") 10 | makeRequest(traceCtx) 11 | } 12 | 13 | func makeRequest(ctx context.Context) { 14 | oldValue, ok := ctx.Value("trace_id").(string) 15 | if ok { 16 | fmt.Println(oldValue) 17 | } 18 | 19 | newCtx, cancel := context.WithCancel(ctx) 20 | defer cancel() 21 | 22 | newValue, ok := newCtx.Value("trace_id").(string) 23 | if ok { 24 | fmt.Println(newValue) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_with_value_type/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | { 10 | ctx := context.WithValue(context.Background(), "key", "value1") 11 | ctx = context.WithValue(ctx, "key", "value2") 12 | 13 | fmt.Println("string =", ctx.Value("key").(string)) 14 | } 15 | { 16 | type key1 string // type definition, not type alias 17 | type key2 string // type definition, not type alias 18 | const k1 key1 = "key" 19 | const k2 key2 = "key" 20 | 21 | ctx := context.WithValue(context.Background(), k1, "value1") 22 | ctx = context.WithValue(ctx, k2, "value2") 23 | 24 | fmt.Println("key1 =", ctx.Value(k1).(string)) 25 | fmt.Println("key2 =", ctx.Value(k2).(string)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/context_without_cancel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 11 | innerCtx := context.WithoutCancel(ctx) 12 | cancel() 13 | 14 | if innerCtx.Err() != nil { 15 | fmt.Println("canceled") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/correct_memory_model_usage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | "sync/atomic" 7 | ) 8 | 9 | var a string 10 | var done atomic.Bool 11 | 12 | func setup() { 13 | a = "hello, world" 14 | done.Store(true) 15 | if done.Load() { 16 | log.Println(len(a)) // always 12 once printed 17 | } 18 | } 19 | 20 | func main() { 21 | go setup() 22 | 23 | for !done.Load() { 24 | runtime.Gosched() 25 | } 26 | 27 | log.Println(a) // hello, world 28 | } 29 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/errgroup_with_ctx/go.mod: -------------------------------------------------------------------------------- 1 | module errgroup 2 | 3 | go 1.20 4 | 5 | require golang.org/x/sync v0.6.0 6 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/errgroup_with_ctx/go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= 2 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 3 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/errgroup_with_ctx/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | 10 | "golang.org/x/sync/errgroup" 11 | "math/rand" 12 | ) 13 | 14 | func main() { 15 | ctx, cancel := context.WithCancel(context.Background()) 16 | defer cancel() 17 | 18 | group, groupCtx := errgroup.WithContext(ctx) 19 | for i := 0; i < 10; i++ { 20 | group.Go(func() error { 21 | timeout := time.Second * time.Duration(rand.Intn(10)) 22 | 23 | timer := time.NewTimer(timeout) 24 | defer timer.Stop() 25 | 26 | select { 27 | case <-timer.C: 28 | fmt.Println("timeout") 29 | return errors.New("error") 30 | case <-groupCtx.Done(): 31 | fmt.Println("canceled") 32 | return nil 33 | } 34 | }) 35 | } 36 | 37 | if err := group.Wait(); err != nil { 38 | fmt.Println(err.Error()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/graceful_shutdown/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) 16 | defer stop() 17 | 18 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 19 | _, _ = io.WriteString(w, "hello world\n") 20 | }) 21 | 22 | server := &http.Server{ 23 | Addr: ":8888", 24 | } 25 | 26 | go func() { 27 | err := server.ListenAndServe() 28 | if err != nil && err != http.ErrServerClosed { 29 | log.Print(err.Error()) // exit 30 | } 31 | }() 32 | 33 | <-ctx.Done() 34 | 35 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 36 | cancel() 37 | 38 | if err := server.Shutdown(ctx); err != nil { 39 | log.Print(err.Error()) 40 | } 41 | 42 | fmt.Println("canceled") 43 | } 44 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/incorrect_memory_model_usage/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | ) 7 | 8 | var a string 9 | var done bool 10 | 11 | func setup() { 12 | a = "hello, world" 13 | done = true 14 | if done { 15 | log.Println(len(a)) // always 12 once printed 16 | } 17 | } 18 | 19 | func main() { 20 | go setup() 21 | 22 | for !done { 23 | runtime.Gosched() 24 | } 25 | 26 | log.Println(a) // expected to print: hello, world 27 | } 28 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/memory_reordering/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "runtime" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | var a []int // nil 10 | var b bool // false 11 | 12 | go func() { 13 | a = make([]int, 3) 14 | b = true 15 | }() 16 | 17 | for !b { 18 | time.Sleep(time.Second) 19 | runtime.Gosched() 20 | } 21 | 22 | a[0], a[1], a[2] = 0, 1, 2 // might panic 23 | } 24 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/nil_context/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | func process(ctx context.Context) { 8 | if ctx.Err() != nil { 9 | // handling... 10 | } 11 | } 12 | 13 | func main() { 14 | process(nil) 15 | } 16 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/with_barriers_sync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "sync/atomic" 7 | ) 8 | 9 | var x int 10 | var y int 11 | 12 | var local_x int 13 | var local_y int 14 | 15 | var barrier atomic.Bool 16 | 17 | func main() { 18 | index := 0 19 | for { 20 | wg := sync.WaitGroup{} 21 | wg.Add(2) 22 | 23 | go func() { 24 | defer wg.Done() 25 | 26 | x = 1 27 | barrier.Store(barrier.Load()) 28 | local_x = y 29 | }() 30 | 31 | go func() { 32 | defer wg.Done() 33 | 34 | y = 1 35 | barrier.Store(barrier.Load()) 36 | local_y = x 37 | }() 38 | 39 | wg.Wait() 40 | 41 | if local_x == 0 && local_y == 0 { 42 | fmt.Println("broken CPU, iteration =", index) 43 | return 44 | } else { 45 | fmt.Println("iteration =", index) 46 | } 47 | 48 | index++ 49 | x, y = 0, 0 50 | local_x, local_y = 0, 0 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/with_ctx_check/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "context" 4 | 5 | func WithContexCheck(ctx context.Context, action func()) { 6 | if action == nil || ctx.Err() != nil { 7 | return 8 | } 9 | 10 | action() 11 | } 12 | 13 | func main() { 14 | ctx := context.Background() 15 | WithContexCheck(ctx, func() { 16 | // do something 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /lessons/7_lesson_contexts_and_memory_barriers/without_barriers_sync/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | var x int 9 | var y int 10 | 11 | var local_x int 12 | var local_y int 13 | 14 | func main() { 15 | index := 0 16 | for { 17 | wg := sync.WaitGroup{} 18 | wg.Add(2) 19 | 20 | go func() { 21 | defer wg.Done() 22 | 23 | x = 1 24 | local_x = y 25 | }() 26 | 27 | go func() { 28 | defer wg.Done() 29 | 30 | y = 1 31 | local_y = x 32 | }() 33 | 34 | wg.Wait() 35 | 36 | if local_x == 0 && local_y == 0 { 37 | fmt.Println("broken CPU, iteration =", index) 38 | return 39 | } else { 40 | fmt.Println("iteration =", index) 41 | } 42 | 43 | index++ 44 | x, y = 0, 0 45 | local_x, local_y = 0, 0 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/lock_free/queue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type item struct { 6 | value int 7 | next *item 8 | } 9 | 10 | type Queue struct { 11 | head *item 12 | tail *item 13 | } 14 | 15 | func NewQueue() Queue { 16 | dummy := &item{} 17 | return Queue{ 18 | head: dummy, 19 | tail: dummy, 20 | } 21 | } 22 | 23 | func (q *Queue) Push(value int) { 24 | q.tail.next = &item{value: value} 25 | q.tail = q.tail.next 26 | } 27 | 28 | func (q *Queue) Pop() int { 29 | if q.head == q.tail { 30 | return -1 31 | } 32 | 33 | value := q.head.next.value 34 | q.head = q.head.next 35 | return value 36 | } 37 | 38 | func main() { 39 | queue := NewQueue() 40 | 41 | queue.Push(10) 42 | queue.Push(20) 43 | queue.Push(30) 44 | 45 | fmt.Println(queue.Pop()) 46 | fmt.Println(queue.Pop()) 47 | fmt.Println(queue.Pop()) 48 | } 49 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/lock_free/stack/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type item struct { 6 | value int 7 | next *item 8 | } 9 | 10 | type Stack struct { 11 | head *item 12 | } 13 | 14 | func NewStack() Stack { 15 | return Stack{} 16 | } 17 | 18 | func (s *Stack) Push(value int) { 19 | s.head = &item{value: value, next: s.head} 20 | } 21 | 22 | func (s *Stack) Pop() int { 23 | if s.head == nil { 24 | return -1 25 | } 26 | 27 | value := s.head.value 28 | s.head = s.head.next 29 | return value 30 | } 31 | 32 | func main() { 33 | stack := NewStack() 34 | 35 | stack.Push(10) 36 | stack.Push(20) 37 | stack.Push(30) 38 | 39 | fmt.Println(stack.Pop()) 40 | fmt.Println(stack.Pop()) 41 | fmt.Println(stack.Pop()) 42 | } 43 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/lock_free/treiber_stack/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | type item struct { 11 | value int 12 | next unsafe.Pointer 13 | } 14 | 15 | type Stack struct { 16 | head unsafe.Pointer 17 | } 18 | 19 | func NewStack() Stack { 20 | return Stack{} 21 | } 22 | 23 | func (s *Stack) Push(value int) { 24 | node := &item{value: value} 25 | 26 | for { 27 | head := atomic.LoadPointer(&s.head) 28 | node.next = head 29 | 30 | if atomic.CompareAndSwapPointer(&s.head, head, unsafe.Pointer(node)) { 31 | return 32 | } 33 | } 34 | } 35 | 36 | func (s *Stack) Pop() int { 37 | for { 38 | head := atomic.LoadPointer(&s.head) 39 | if head == nil { 40 | return -1 41 | } 42 | 43 | next := atomic.LoadPointer(&(*item)(head).next) 44 | if atomic.CompareAndSwapPointer(&s.head, head, next) { 45 | return (*item)(head).value 46 | } 47 | } 48 | } 49 | 50 | func main() { 51 | stack := NewStack() 52 | 53 | wg := sync.WaitGroup{} 54 | wg.Add(100) 55 | 56 | for i := 0; i < 50; i++ { 57 | go func(value int) { 58 | defer wg.Done() 59 | stack.Push(value) 60 | stack.Push(value) 61 | stack.Push(value) 62 | }(i) 63 | } 64 | 65 | time.Sleep(100 * time.Millisecond) 66 | 67 | for i := 0; i < 50; i++ { 68 | go func() { 69 | defer wg.Done() 70 | stack.Pop() 71 | stack.Pop() 72 | stack.Pop() 73 | }() 74 | } 75 | 76 | wg.Wait() 77 | } 78 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/rcu/cache_with_atomic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "sync/atomic" 6 | "time" 7 | "unsafe" 8 | ) 9 | 10 | type CacheRCU struct { 11 | data unsafe.Pointer 12 | } 13 | 14 | func NewCacheRCU(ctx context.Context) *CacheRCU { 15 | data := make(map[string]string) 16 | cache := &CacheRCU{ 17 | data: unsafe.Pointer(&data), 18 | } 19 | 20 | go cache.synchronize(ctx) 21 | return cache 22 | } 23 | 24 | func (c *CacheRCU) synchronize(ctx context.Context) { 25 | ticker := time.NewTicker(time.Minute) 26 | defer ticker.Stop() 27 | 28 | for { 29 | select { 30 | case <-ctx.Done(): 31 | return 32 | default: 33 | } 34 | 35 | select { 36 | case <-ctx.Done(): 37 | return 38 | case <-ticker.C: 39 | var data map[string]string 40 | // data = ... - get from remote storage 41 | 42 | pointer := unsafe.Pointer(&data) 43 | atomic.StorePointer(&c.data, pointer) 44 | } 45 | } 46 | } 47 | 48 | func (c *CacheRCU) Get(key string) (string, bool) { 49 | pointer := atomic.LoadPointer(&c.data) 50 | data := *(*map[string]string)(pointer) 51 | 52 | value, found := data[key] 53 | return value, found 54 | } 55 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/rcu/cache_with_mutex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type Cache struct { 10 | mutex sync.RWMutex 11 | data map[string]string 12 | } 13 | 14 | func NewCache(ctx context.Context) *Cache { 15 | cache := &Cache{ 16 | data: make(map[string]string), 17 | } 18 | 19 | go cache.synchronize(ctx) 20 | return cache 21 | } 22 | 23 | func (c *Cache) synchronize(ctx context.Context) { 24 | ticker := time.NewTicker(time.Minute) 25 | defer ticker.Stop() 26 | 27 | for { 28 | select { 29 | case <-ctx.Done(): 30 | return 31 | default: 32 | } 33 | 34 | select { 35 | case <-ctx.Done(): 36 | return 37 | case <-ticker.C: 38 | var data map[string]string 39 | // data = ... - get from remote storage 40 | 41 | c.mutex.Lock() 42 | c.data = data 43 | c.mutex.Unlock() 44 | } 45 | } 46 | } 47 | 48 | func (c *Cache) Get(key string) (string, bool) { 49 | c.mutex.RLock() 50 | defer c.mutex.RUnlock() 51 | 52 | value, found := c.data[key] 53 | return value, found 54 | } 55 | -------------------------------------------------------------------------------- /lessons/8_lesson_sync_algorithms_and_lock_free/sharded_map/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "hash/fnv" 5 | "sync" 6 | ) 7 | 8 | type Map struct { 9 | mutex sync.Mutex 10 | data map[string]string 11 | } 12 | 13 | func NewMap() *Map { 14 | return &Map{ 15 | data: make(map[string]string), 16 | } 17 | } 18 | 19 | func (m *Map) Get(key string) (string, bool) { 20 | m.mutex.Lock() 21 | defer m.mutex.Unlock() 22 | 23 | value, found := m.data[key] 24 | return value, found 25 | } 26 | 27 | func (m *Map) Set(key, value string) { 28 | m.mutex.Lock() 29 | defer m.mutex.Unlock() 30 | 31 | m.data[key] = value 32 | } 33 | 34 | func (m *Map) Delete(key string) { 35 | m.mutex.Lock() 36 | defer m.mutex.Unlock() 37 | 38 | delete(m.data, key) 39 | } 40 | 41 | type ShardedMap struct { 42 | shards []Map 43 | shardsNumber int 44 | } 45 | 46 | func NewShardedMap(shardsNumber int) *ShardedMap { 47 | return &ShardedMap{ 48 | shards: make([]Map, shardsNumber), 49 | shardsNumber: shardsNumber, 50 | } 51 | } 52 | 53 | func (sm *ShardedMap) Get(key string) (string, bool) { 54 | idx := sm.shardIdx(key) 55 | return sm.shards[idx].Get(key) 56 | } 57 | 58 | func (sm *ShardedMap) Set(key, value string) { 59 | idx := sm.shardIdx(key) 60 | sm.shards[idx].Set(key, value) 61 | } 62 | 63 | func (sm *ShardedMap) Delete(key string) { 64 | idx := sm.shardIdx(key) 65 | sm.shards[idx].Delete(key) 66 | } 67 | 68 | func (sm *ShardedMap) shardIdx(key string) int { 69 | h := fnv.New32a() 70 | _, _ = h.Write([]byte(key)) 71 | 72 | hash := int(h.Sum32()) 73 | return hash % sm.shardsNumber 74 | } 75 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/2pl/go.mod: -------------------------------------------------------------------------------- 1 | module 2pl 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/2pl/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 11 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/2pl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func withLock(mutex sync.Locker, action func()) { 9 | if mutex == nil || action == nil { 10 | return 11 | } 12 | 13 | mutex.Lock() 14 | action() 15 | mutex.Unlock() 16 | } 17 | 18 | func main() { 19 | inMemory := NewInMemoryStorage() 20 | s := NewScheduler(inMemory) 21 | 22 | wg := sync.WaitGroup{} 23 | wg.Add(1) 24 | 25 | parallelTx2 := func() { 26 | defer wg.Done() 27 | tx2 := s.StartTransaction() 28 | tx2.Set("key_2", "value_2") 29 | time.Sleep(time.Millisecond * 200) 30 | tx2.Set("key_1", "value_1") 31 | tx2.Commit() 32 | } 33 | 34 | tx1 := s.StartTransaction() 35 | tx1.Get("key_1") 36 | go parallelTx2() 37 | time.Sleep(time.Millisecond * 100) 38 | tx1.Get("key_2") 39 | tx1.Commit() 40 | 41 | wg.Wait() 42 | } 43 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/2pl/storage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type InMemoryStorage struct { 6 | mutex sync.RWMutex 7 | data map[string]string 8 | } 9 | 10 | func NewInMemoryStorage() *InMemoryStorage { 11 | return &InMemoryStorage{ 12 | data: make(map[string]string), 13 | } 14 | } 15 | 16 | func (s *InMemoryStorage) Set(key string, value string) { 17 | s.mutex.Lock() 18 | defer s.mutex.Unlock() 19 | s.data[key] = value 20 | } 21 | 22 | func (s *InMemoryStorage) Get(key string) string { 23 | s.mutex.RLock() 24 | defer s.mutex.RUnlock() 25 | return s.data[key] 26 | } 27 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/2pl/transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | shared = iota + 1 5 | exclusive 6 | ) 7 | 8 | type scheduler interface { 9 | set(int32, string, string) txOperation 10 | get(int32, string) (string, txOperation) 11 | 12 | commit([]txOperation) 13 | rollback([]txOperation) 14 | } 15 | 16 | type txOperation struct { 17 | lock *txLock 18 | key string 19 | value *string 20 | } 21 | 22 | type Transaction struct { 23 | scheduler scheduler 24 | operations []txOperation 25 | identifier int32 26 | finished bool 27 | } 28 | 29 | func newTransaction(scheduler scheduler, id int32) Transaction { 30 | return Transaction{ 31 | scheduler: scheduler, 32 | identifier: id, 33 | } 34 | } 35 | 36 | func (t *Transaction) Set(key, value string) { 37 | if t.finished || key == "" || value == "" { 38 | return 39 | } 40 | 41 | operation := t.scheduler.set(t.identifier, key, value) 42 | t.operations = append(t.operations, operation) 43 | } 44 | 45 | func (t *Transaction) Get(key string) string { 46 | if t.finished { 47 | return "" 48 | } 49 | 50 | value, operation := t.scheduler.get(t.identifier, key) 51 | t.operations = append(t.operations, operation) 52 | return value 53 | } 54 | 55 | func (t *Transaction) Commit() { 56 | if t.finished { 57 | return 58 | } 59 | 60 | t.scheduler.commit(t.operations) 61 | t.finished = true 62 | } 63 | 64 | func (t *Transaction) Rollback() { 65 | if t.finished { 66 | return 67 | } 68 | 69 | t.scheduler.rollback(t.operations) 70 | t.finished = true 71 | } 72 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/actors/actor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const inboxSize = 10 4 | 5 | type Executor interface { 6 | Execute(Message) 7 | } 8 | 9 | type Message struct { 10 | To string 11 | From string 12 | Body string 13 | } 14 | 15 | type actor struct { 16 | inbox chan Message 17 | executor Executor 18 | } 19 | 20 | func newActor(executor Executor) *actor { 21 | obj := &actor{ 22 | inbox: make(chan Message, inboxSize), 23 | executor: executor, 24 | } 25 | 26 | go obj.loop() 27 | return obj 28 | } 29 | 30 | func (a *actor) loop() { 31 | for message := range a.inbox { 32 | a.executor.Execute(message) 33 | } 34 | } 35 | 36 | func (a *actor) receive(message Message) { 37 | a.inbox <- message 38 | } 39 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/actors/actor_manager.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | var once sync.Once 9 | var instance *ActorManager 10 | 11 | type ActorManager struct { 12 | mutex sync.RWMutex 13 | actors map[string]*actor 14 | } 15 | 16 | func GetActorManager() *ActorManager { 17 | once.Do(func() { 18 | instance = &ActorManager{ 19 | actors: make(map[string]*actor), 20 | } 21 | }) 22 | 23 | return instance 24 | } 25 | 26 | func (am *ActorManager) CreateActor(address string, executor Executor) error { 27 | am.mutex.Lock() 28 | defer am.mutex.Unlock() 29 | 30 | if _, found := am.actors[address]; found { 31 | return errors.New("already exists") 32 | } 33 | 34 | am.actors[address] = newActor(executor) 35 | return nil 36 | } 37 | 38 | func (am *ActorManager) SendMessage(message Message) error { 39 | am.mutex.RLock() 40 | defer am.mutex.RUnlock() 41 | 42 | obj, found := am.actors[message.To] 43 | if !found { 44 | return errors.New("not found") 45 | } 46 | 47 | // may be without lock 48 | obj.receive(message) 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/actors/go.mod: -------------------------------------------------------------------------------- 1 | module actors 2 | 3 | go 1.22.3 4 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/actors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | const ( 9 | NetworkActor = "network" 10 | ProcessorActor = "processor" 11 | SenderActor = "sender" 12 | ) 13 | 14 | type MessageProcessor struct{} 15 | 16 | func (p *MessageProcessor) Execute(income Message) { 17 | fmt.Printf("received message from [%s:%s]: %s\n", income.From, income.To, income.Body) 18 | 19 | outcome := Message{ 20 | From: income.To, 21 | To: SenderActor, 22 | Body: "processed_message", 23 | } 24 | 25 | if err := GetActorManager().SendMessage(outcome); err != nil { 26 | fmt.Printf("failed to send message: %s", err.Error()) 27 | } 28 | } 29 | 30 | type MessageSender struct{} 31 | 32 | func (s *MessageSender) Execute(income Message) { 33 | fmt.Printf("received message [%s:%s]: %s\n", income.From, income.To, income.Body) 34 | fmt.Printf("message successfully sent") 35 | } 36 | 37 | func main() { 38 | manager := GetActorManager() 39 | if err := manager.CreateActor(ProcessorActor, &MessageProcessor{}); err != nil { 40 | panic("failed to create actor") 41 | } 42 | 43 | if err := manager.CreateActor(SenderActor, &MessageSender{}); err != nil { 44 | panic("failed to create actor") 45 | } 46 | 47 | message := Message{ 48 | From: NetworkActor, 49 | To: ProcessorActor, 50 | Body: "received_message", 51 | } 52 | 53 | if err := manager.SendMessage(message); err != nil { 54 | fmt.Printf("failed to send message: %s", err.Error()) 55 | } 56 | 57 | time.Sleep(time.Second) 58 | } 59 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/amdala/perf_test.go: -------------------------------------------------------------------------------- 1 | package amdala 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | // go test -bench=. perf_test.go 10 | // a = 1 / ((1 - P) + (P / S)) 11 | 12 | func calculate(parallelFactor int) { 13 | value := 0 14 | for i := 0; i < 1000000; i++ { 15 | value += i 16 | } 17 | 18 | wg := sync.WaitGroup{} 19 | wg.Add(parallelFactor) 20 | for i := 0; i < parallelFactor; i++ { 21 | go func() { 22 | defer wg.Done() 23 | 24 | localValue := 0 25 | for j := 0; j < 1000000/parallelFactor; j++ { 26 | localValue += j 27 | } 28 | }() 29 | } 30 | 31 | wg.Wait() 32 | } 33 | 34 | func BenchmarkCalculation(b *testing.B) { 35 | parallelFactor := 1 36 | runtime.GOMAXPROCS(parallelFactor) 37 | for i := 0; i < b.N; i++ { 38 | calculate(parallelFactor) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/mvcc/go.mod: -------------------------------------------------------------------------------- 1 | module mvcc 2 | 3 | go 1.20 4 | 5 | require github.com/stretchr/testify v1.9.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/igrmk/treemap/v2 v2.0.1 // indirect 10 | github.com/pmezard/go-difflib v1.0.0 // indirect 11 | golang.org/x/exp v0.0.0-20220317015231-48e79f11773a // indirect 12 | gopkg.in/yaml.v3 v3.0.1 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/mvcc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/igrmk/treemap/v2 v2.0.1 h1:Jhy4z3yhATvYZMWCmxsnHO5NnNZBdueSzvxh6353l+0= 4 | github.com/igrmk/treemap/v2 v2.0.1/go.mod h1:PkTPvx+8OHS8/41jnnyVY+oVsfkaOUZGcr+sfonosd4= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 8 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 9 | github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4 h1:qk1XyC6UGfPa51PGmsTQJavyhfMLScqw97pEV3sFClI= 10 | github.com/umpc/go-sortedmap v0.0.0-20180422175548-64ab94c482f4/go.mod h1:X6iKjXCleSyo/LZzKZ9zDF/ZB2L9gC36I5gLMf32w3M= 11 | golang.org/x/exp v0.0.0-20220317015231-48e79f11773a h1:DAzrdbxsb5tXNOhMCSwF7ZdfMbW46hE9fSVO6BsmUZM= 12 | golang.org/x/exp v0.0.0-20220317015231-48e79f11773a/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 13 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/mvcc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | func withLock(mutex sync.Locker, action func()) { 9 | if mutex == nil || action == nil { 10 | return 11 | } 12 | 13 | mutex.Lock() 14 | action() 15 | mutex.Unlock() 16 | } 17 | 18 | func main() { 19 | inMemory := NewInMemoryStorage() 20 | s := NewScheduler(inMemory) 21 | 22 | wg := sync.WaitGroup{} 23 | wg.Add(1) 24 | 25 | parallelTx2 := func() { 26 | defer wg.Done() 27 | tx2 := s.StartTransaction() 28 | tx2.Set("key_2", "value_2") 29 | time.Sleep(time.Millisecond * 200) 30 | tx2.Set("key_1", "value_1") 31 | _ = tx2.Commit() 32 | } 33 | 34 | tx1 := s.StartTransaction() 35 | tx1.Get("key_1") 36 | go parallelTx2() 37 | time.Sleep(time.Millisecond * 100) 38 | tx1.Get("key_2") 39 | _ = tx1.Commit() 40 | 41 | wg.Wait() 42 | } 43 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/mvcc/storage_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestStorage(t *testing.T) { 9 | t.Parallel() 10 | 11 | s := NewInMemoryStorage() 12 | s.Set(1, map[string]string{"key_0": "value_0"}) 13 | s.Set(2, map[string]string{"key_1": "value_2"}) 14 | s.Set(3, map[string]string{"key_1": "value_3"}) 15 | s.Set(4, map[string]string{"key_1": "value_4"}) 16 | 17 | assert.Equal(t, "", s.Get(1, "key_1")) 18 | assert.Equal(t, "value_2", s.Get(2, "key_1")) 19 | assert.Equal(t, "value_3", s.Get(3, "key_1")) 20 | assert.Equal(t, "value_4", s.Get(4, "key_1")) 21 | assert.Equal(t, "value_4", s.Get(5, "key_1")) 22 | 23 | assert.False(t, s.ExistsBetween(0, 1, map[string]string{"key_1": ""})) 24 | assert.True(t, s.ExistsBetween(2, 4, map[string]string{"key_1": ""})) 25 | assert.True(t, s.ExistsBetween(3, 5, map[string]string{"key_1": ""})) 26 | } 27 | -------------------------------------------------------------------------------- /lessons/9_lesson_concurrency_patterns/mvcc/transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type scheduler interface { 8 | get(int32, string) string 9 | commit(int32, map[string]string) bool 10 | } 11 | 12 | type Transaction struct { 13 | modified map[string]string 14 | cached map[string]string 15 | 16 | scheduler scheduler 17 | 18 | identifier int32 19 | finished bool 20 | } 21 | 22 | func newTransaction(scheduler scheduler, id int32) Transaction { 23 | return Transaction{ 24 | modified: make(map[string]string), 25 | cached: make(map[string]string), 26 | scheduler: scheduler, 27 | identifier: id, 28 | } 29 | } 30 | 31 | func (t *Transaction) Set(key, value string) { 32 | if t.finished || key == "" || value == "" { 33 | return 34 | } 35 | 36 | t.modified[key] = value 37 | } 38 | 39 | func (t *Transaction) Get(key string) string { 40 | if t.finished { 41 | return "" 42 | } 43 | 44 | if value, found := t.modified[key]; found { 45 | return value 46 | } 47 | 48 | if value, found := t.cached[key]; found { 49 | return value 50 | } 51 | 52 | value := t.scheduler.get(t.identifier, key) 53 | t.cached[key] = value 54 | return value 55 | } 56 | 57 | func (t *Transaction) Commit() error { 58 | if t.finished { 59 | return nil 60 | } 61 | 62 | if len(t.modified) == 0 { 63 | t.finished = true 64 | return nil 65 | } 66 | 67 | if succeed := t.scheduler.commit(t.identifier, t.modified); !succeed { 68 | return errors.New("transactions conflict") 69 | } 70 | 71 | return nil 72 | } 73 | 74 | func (t *Transaction) Rollback() { 75 | if t.finished { 76 | return 77 | } 78 | 79 | t.finished = true 80 | } 81 | --------------------------------------------------------------------------------