diff --git a/5-go-command.md b/5-go-command.md
index 1446fa057..800b96fcd 100644
--- a/5-go-command.md
+++ b/5-go-command.md
@@ -101,4 +101,4 @@ Command `go get` **harus dijalankan dalam folder project**. Jika dijalankan di-l
## A.5.6. Command `go mod vendor`
-Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada bab [A.58. Go Vendoring](A-58-go-vendoring.md).
+Command ini digunakan untuk vendoring. Lebih detailnya akan dibahas di akhir serial chapter A, pada bab [A.58. Go Vendoring](A-go-vendoring.md).
diff --git a/56-waitgroup.md b/56-waitgroup.md
index 53035ff86..b0e059564 100644
--- a/56-waitgroup.md
+++ b/56-waitgroup.md
@@ -56,6 +56,8 @@ Output program di atas.
![Contoh penerapan `sync.WaitGroup`](images/A.56_1_waitgroup.png)
+> `sync.WaitGroup` merupakan salah satu tipe yang *thread safe*. Kita tidak perlu khawatir terhadap potensi *race condition* karena variabel bertipe ini aman untuk digunakan di banyak goroutine secara paralel.
+
## A.56.2. Perbedaan WaitGroup Dengan Channel
Bukan sebuah perbandingan yang valid, tapi jika dibandingkan maka perbedaan antara channel dan `sync.WaitGroup` kurang lebih sebagai berikut:
diff --git a/57-mutex.md b/57-mutex.md
index 6110524f3..ca2eb5c12 100644
--- a/57-mutex.md
+++ b/57-mutex.md
@@ -167,6 +167,8 @@ func main() {
}
```
+> `sync.Mutex` merupakan salah satu tipe yang *thread safe*. Kita tidak perlu khawatir terhadap potensi *race condition* karena variabel bertipe ini aman untuk digunakan di banyak goroutine secara paralel.
+
---
diff --git a/A-61-pipeline-context-cancellation.md b/A-61-pipeline-context-cancellation.md
deleted file mode 100644
index 735dd8605..000000000
--- a/A-61-pipeline-context-cancellation.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# A.61. Concurrency Pattern: Context Cancellation Pipeline
-
-In progress.
diff --git a/A-59-concurrency-pipeline.md b/A-concurrency-pipeline.md
similarity index 98%
rename from A-59-concurrency-pipeline.md
rename to A-concurrency-pipeline.md
index ec8287b8a..d02b4f99a 100644
--- a/A-59-concurrency-pipeline.md
+++ b/A-concurrency-pipeline.md
@@ -146,7 +146,7 @@ O iya untuk logging pembuatan file saya tampilkan setiap 100 file di-generate, a
Oke, generator sudah siap, jalankan.
-![Generate dummy files](images/A.59_1_generate_dummy_files.png)
+![Generate dummy files](images/A_concurrency_pipeline_1_generate_dummy_files.png)
Bisa dilihat sebanyak 3000 dummy file di-generate pada folder temporary os, di sub folder `chapter-A.59-pipeline-temp`.
@@ -246,7 +246,7 @@ Cukup panjang isi fungsi ini, tapi isinya cukup *straightforward* kok.
Semoga cukup jelas. Kalo iya, jalankan programnya.
-![Generate dummy files](images/A.59_2_rename_sequentially.png)
+![Generate dummy files](images/A_concurrency_pipeline_2_rename_sequentially.png)
Selesai dalam waktu **1,17 detik**, lumayan untuk eksekusi proses sekuensial.
@@ -525,7 +525,7 @@ Kita lakukan perulangan terhadap channel tersebut, lalu hitung jumlah file yang
Ok, sekarang program sudah siap. Mari kita jalankan untuk melihat hasilnya.
-![Rename file concurrently](images/A.59_3_rename_concurrently.png)
+![Rename file concurrently](images/A_concurrency_pipeline_3_rename_concurrently.png)
Bisa dilihat bedanya, untuk rename 3000 file menggunakan cara sekuensial membutuhkan waktu `1.17` detik, sedangkan dengan metode pipeline butuh hanya `0.72` detik. Bedanya hampir **40%**! dan ini hanya 3000 file saja, bayangkan kalau jutaan file, mungkin lebih kerasa perbandingan performnya.
@@ -541,5 +541,5 @@ Ok sekian untuk chapter panjang ini.
Source code praktek pada bab ini tersedia di Github
diff --git a/A-58-go-vendoring.md b/A-go-vendoring.md
similarity index 94%
rename from A-58-go-vendoring.md
rename to A-go-vendoring.md
index a54ab97d9..c7d99cf97 100644
--- a/A-58-go-vendoring.md
+++ b/A-go-vendoring.md
@@ -40,7 +40,7 @@ func main() {
Setelah itu jalankan command `go mod vendor` untuk vendoring *3rd party library* yang dipergunakan, dalam contoh ini adlah gubrak.
-![Vendoring](images/A.58_1_vendor.png)
+![Vendoring](images/A_go_vendoring_1_vendor.png)
Bisa dilihat, sekarang library gubrak *source code*-nya disimpan dalam folder `vendor`. Nah ini juga akan berlaku untuk semua *library* lainnya yg digunakan jika ada.
@@ -72,5 +72,5 @@ Untuk penggunaan vendor apakah wajib? menurut saya tidak. Sesuaikan kebutuhan sa
Source code praktek pada bab ini tersedia di Github
diff --git a/A-pipeline-context-cancellation.md b/A-pipeline-context-cancellation.md
new file mode 100644
index 000000000..efa945872
--- /dev/null
+++ b/A-pipeline-context-cancellation.md
@@ -0,0 +1,455 @@
+# A.61. Concurrency Pattern: Context Cancellation Pipeline
+
+Pada bab ini kita akan belajar tentang salah satu *concurrency pattern* di Go, yaitu **cancellation**. Cancellation merupakan mekanisme untuk menggagalkan secara paksa proses konkuren yang sedang berjalan, entah itu karena ada timeout, ada error, atau ada faktor lain.
+
+Disini kita akan gunakan salah satu API milik Go yang tersedia untuk *cancellation*, yaitu `context.Context`.
+
+Context digunakan untuk mendefinisikan tipe *context* yang didalamnya ada beberapa hal yaitu: informasi *deadlines*, signal *cancellation*, dan data untuk keperluan komunikasi antar API atau antar proses.
+
+## A.61.1. Skenario Praktek
+
+Kita akan modifikasi file program [`1-generate-dummy-files-concurrently.go` yang pada chapter sebelumnya sudah dibuat](/A-simplified-fan-in-fan-out-pipeline.html). Pada program tersebut akan kita tambahkan mekanisme cancellation ketika ada timeout.
+
+Jadi kurang lebih akan ada dua result:
+
+- Proses sukses, karena *execution time* dibawah timeout.
+- Proses digagalkan secara paksa ditengah jalan, karena *running time* sudah melebihi batas timeout.
+
+## A.61.2. Program Generate Dummy File *Concurrently*
+
+Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi *concurrent* tanpa *cancellation*. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, disini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+
+#### • Import Packages dan Definisi Variabel
+
+```go
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "sync"
+ "time"
+)
+
+const totalFile = 3000
+const contentLength = 5000
+
+var tempPath = filepath.Join(os.Getenv("TEMP"), "chapter-A.61-pipeline-cancellation-context")
+```
+
+#### • Definisi struct `FileInfo`
+
+```go
+type FileInfo struct {
+ Index int
+ FileName string
+ WorkerIndex int
+ Err error
+}
+```
+
+#### • Fungsi `init()` dan `main()`
+
+```go
+func init() {
+ rand.Seed(time.Now().UnixNano())
+}
+
+func main() {
+ log.Println("start")
+ start := time.Now()
+
+ generateFiles()
+
+ duration := time.Since(start)
+ log.Println("done in", duration.Seconds(), "seconds")
+}
+```
+
+#### • Fungsi `randomString()`
+
+```go
+func randomString(length int) string {
+ letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ b := make([]rune, length)
+ for i := range b {
+ b[i] = letters[rand.Intn(len(letters))]
+ }
+
+ return string(b)
+}
+```
+
+#### • Fungsi `generateFiles()`
+
+```go
+func generateFiles() {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes()
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterTotal := 0
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ counterTotal++
+ }
+
+ log.Printf("%d/%d of total files created", counterSuccess, counterTotal)
+}
+```
+#### • Fungsi `generateFileIndexes()`
+
+```go
+func generateFileIndexes() <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+#### • Fungsi `dispatchWorkers()`
+
+```go
+func createFiles(chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Hasil eksekusi program:
+
+![Concurrent without cancellation](images/A_pipeline_context_cancellation_1_concurrent_without_cancellation.png)
+
+## A.61.3. Program Generate Dummy File *Concurrently* dan Mekanisme *Cancellation*
+
+Ok, sekarang kita akan refactor kode tersebut, kita tambahkan mekanisme *cancellation* menggunakan `context.Context` API. Silakan duplikasi file program, lalu ikuti petunjuk berikut.
+
+#### • Import package `context`
+
+Tambahkan package `context` dalam block import packages.
+
+```go
+import (
+ "context"
+
+ // ...
+)
+```
+
+#### • Tambahkan definisi konstanta timeout
+
+Disini saya tentukan timeout adalah 3 menit. Nantinya kita akan modifikasi angka timeout untuk keperluan testing.
+
+```go
+const timeoutDuration = 3 * time.Second
+```
+
+#### • Penerapan context di fungsi `main()`
+
+Pada fungsi main, lakukan sedikit perubahan. Yang sebelumnya ada statement berikut:
+
+```go
+generateFiles()
+```
+
+Ubah menjadi berikut:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Fungsi `generateFilesWithContext()` merupakan fungsi yang sama persis dengan `generateFiles()` (yang tentunya akan kita buat). Perbedaannya adalah hanya pada fungsi baru ini ada satu argument baru yaitu data `context.Context`.
+
+Ini merupakan salah satu idiomatic Go untuk cara penulisan fungsi yang *cancellable*. Umumnya akan ada fungsi tanpa context dan fungsi yang ada context-nya. Contohnya seperti berikut:
+
+```go
+generateFiles()
+generateFilesWithContext(ctx)
+```
+
+Dimisalkan lagi jika argument context adalah wajib pada sebuah fungsi, maka cukup gunakan 1 fungsi saja, yang ada `WithContext()`-nya dihapus, tapi satu fungsi yang ada ditambahkan context. Contohnya:
+
+```go
+generateFiles(ctx)
+```
+
+Pada contoh ini kita akan siapkan dua fungsi, yang ada context-nya dan yang tidak.
+
+Ok lanjut ke pembahasan. Fungsi `context.WithTimeout` digunakan untuk menambahkan timeout pada sebuah context. Parameter pertama fungsi ini adalah objek context juga. Pada contoh di atas, karena sebelumnya belum ada objek context, maka kita buat objek context baru lewat `context.Background()`.
+
+Cara pembuatan object context sendiri sebenarnya ada 3:
+
+1. Menggunakan fungsi `context.Background()`. Fungsi tersebut menghasilkan objek context yang data di dalamnya adalah kosong dan tidak memiliki deadline. Context ini biasanya digunakan untuk inisialisasi object context baru yang nanti akan di-chain dengan fungsi `context.With...`.
+2. Menggunakan fungsi `context.TODO()`. Fungsi ini menghasilkan objek context baru seperti `context.Background()`. Context buatan fungsi TODO ini biasanya digunakan dalam situasi ketika belum jelas nantinya harus menggunakan jenis context apa (apakah dengan timeout, apakah dengan cancel).
+3. Menggunakan fungsi `context.With...`. Fungsi ini sebenarnya bukan digunakan untuk inisialisasi objek konteks baru, tapi digunakan untuk menambahkan informasi tertentu pada *copied* context yang disisipkan di parameter pertama pemanggilan fungsi. Ada 3 buah fungsi `context.With...` yang bisa digunakan, yaitu:
+ A. Fungsi `context.WithCancel(ctx) (ctx, cancel)`. Fungsi ini digunakan untuk menambahkan fasilitas *cancellable* pada context yang disipkan sebagai parameter pertama pemanggilan fungsi. Lewat nilai balik kedua, yaitu `cancel` yang tipenya `context.CancelFunc`, kita bisa secara paksa meng-*cancel* context ini.
+ B. Fungsi `context.WithDeadline(ctx, time.Time) (ctx, cancel)`. Fungsi ini juga menambahkan fitur *cancellable* pada context, tapi selain itu juga menambahkan informasi deadline yang dimana jika waktu sekarang sudah melebih deadline yang sudah ditentukan maka context otomatis di-cancel secara paksa.
+ B. Fungsi `context.WithTimeout(ctx, time.Duration) (ctx, cancel)`. Fungsi ini sama seperti `context.WithDeadline()`, bedanya pada parameter kedua argument bertipe durasi (bukan objek `time.Time`).
+
+Kesamaan dari ketiga fungsi `context.With...` adalah sama-sama menambahkan fasilitas *cancellable* yang bisa dieksekusi lewat nilai balik kedua fungsi tersebut (yang tipenya `context.CancelFunc`).
+
+Jadi pada contoh yang kita tulis di atas, kurang lebih yang akan dilakukan adalah:
+
+* Kira buat object context baru lewat `context.Background()`.
+* Objek context tersebut ditambahkan fasilitas *cancellable* didalamnya, dan juga auto cancel ketika timeout menggunakan `context.WithTimeout()`, dengan durasi timeout adalah `timeoutDuration`.
+* Fungsi `generateFilesWithContext()` dipanggil dengan disisipkan object context.
+* Callback `context.CancelFunc` dipanggil secara deferred. Ini merupakan idiomatic Go dalam penerapan context. Meskipun context sudah punya timeout atau deadline dan kita tidak perlu meng-*cancel* context secara manual, sangat dianjurkan untuk tetap memanggil callback `cancel()` tersebut secara deferred.
+
+#### • Modifikasi fungsi `generateFiles()`
+
+Isi dari fungsi `generateFiles()` kita ubah menjadi pemanggilan fungsi `generateFilesWithContext()` dengan parameter context kosong.
+
+```go
+func generateFiles() {
+ generateFilesWithContext(context.Background())
+}
+```
+
+Pada fungsi `generateFilesWithContext()` sendiri, isinya adalah isi `generateFiles()` sebelumnya tapi ditambahkan beberapa hal. Silakan tulis dulu kode berikut.
+
+```go
+func generateFilesWithContext(ctx context.Context) {
+ os.RemoveAll(tempPath)
+ os.MkdirAll(tempPath, os.ModePerm)
+
+ done := make(chan int)
+
+ go func() {
+ // pipeline 1: job distribution
+ chanFileIndex := generateFileIndexes(ctx)
+
+ // pipeline 2: the main logic (creating files)
+ createFilesWorker := 100
+ chanFileResult := createFiles(ctx, chanFileIndex, createFilesWorker)
+
+ // track and print output
+ counterSuccess := 0
+ for fileResult := range chanFileResult {
+ if fileResult.Err != nil {
+ log.Printf("error creating file %s. stack trace: %s", fileResult.FileName, fileResult.Err)
+ } else {
+ counterSuccess++
+ }
+ }
+
+ // notify that the process is complete
+ done <- counterSuccess
+ }()
+
+ select {
+ case <-ctx.Done():
+ log.Printf("generation process stopped. %s", ctx.Err())
+ case counterSuccess := <-done:
+ log.Printf("%d/%d of total files created", counterSuccess, totalFile)
+ }
+}
+```
+
+Penambahan yang dimaksud adalah, statement pipelines dibungus dengan sebuah goroutine IIF, yang di akhir fungsi kita kirim informasi jumlah file yang berhasil di-generate (`counterSuccess`) ke sebuah channel bernama `done`.
+
+Channel `done` ini kita gunakan sebagai indikator bahwa proses pipeline sudah selesai secara keseluruhan.
+
+Selain goroutine, di akhir fungsi `generateFilesWithContext()` sendiri ditambahkan *channel selection* dengan isi dua buah cases.
+
+Case pertama adalah ketika channel done pada context `ctx` menerima data. Cara penggunaannya seperti ini `<-ctx.Done()`. Ketika channel done milik context ini menerima data, berarti context telah di-cancel secara paksa. Cancel-nya bisa karena memang context melebihi timeout yang sudah ditentukan, atau di-cancel secara eksplisit lewat pemanggilan callback `context.CancelFunc`. Untuk mengetahui alasan cancel bisa dengan cara mengakses method error milik contex, yaitu: `ctx.Err()`.
+
+Jadi pada contoh di atas, ketika context timeout atau di-cancel secara eksplisit (via callback `cancel`), maka case pertama akan terpenuhi dan message ditampilkan.
+
+Untuk case kedua akan terpenuhi ketika proses pipeline sudah selesai secara keseluruhan. Bisa dilihat di akhir goroutine, disitu channel `done` dikirimi informasi `counterSuccess`. Ketika ini terjadi maka kondisi case kedua terpenuhi, lalu ditampilkan informasi file yang sudah sukses dibuat.
+
+Nah jadi lewat seleksi kondisi 2 case di atas, kita bisa dengan mudah mengidentifikasi apakah proses selesai sepenuhnya, ataukah cancelled ditengah jalan karena timeout ataupun karena di-cancel secara eksplisit.
+
+Selain beberapa hal yang sudah saya sampaikan, ada *minor changes* lainnya, yaitu pada pemanggilan fungsi `generateFileIndexes()` dan `createFiles()` ditambahkan argument context.
+
+#### • Penambahan context pada fungsi `generateFiles()`
+
+Kenapa ini perlu? karena **meski eksekusi fungsi `generateFilesWithContext()` otomatis di stop ketika cancelled, proses di dalamnya akan tetap berjalan jika tidak di-*handle* dengan baik *cancellation*-nya.**
+
+Maka dari itu kita perlu memodifikasi, memastikan bahwa cancellation juga diberlakukan dalam level sub proses.
+
+Silakan tulis kode berikut pada fungsi `generateFileIndexes()`.
+
+```go
+func generateFileIndexes(ctx context.Context) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ go func() {
+ for i := 0; i < totalFile; i++ {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ chanOut <- FileInfo{
+ Index: i,
+ FileName: fmt.Sprintf("file-%d.txt", i),
+ }
+ }
+ }
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Dibanding sebelumnya, perbedaannya adalah ada *channel selection*. Jadi di bagian pengiriman jobs, ketika sebelum semua jobs dikirim tapi ada notif untuk cancel maka kita akan skip pengiriman *remainin* jobs secara paksa.
+
+* Jika ada notif cancel paksa, maka case pertama akan terpenuhi, dan perulangan di-`break`.
+* Selebihnya, pengiriman jobs akan berlangsung seperti normalnya.
+
+#### • Penambahan context pada fungsi `createFiles()`
+
+Hal yang sama (cancel di level sub prosees) juga perlu diterapkan pada `createFiles()`, karena jika tidak, maka proses pembuatan file akan tetap berjalan sesuai dengan jumlah jobs yang dikirim meskipun sudah di-cancel secara paksa.
+
+Sebenarnya penambahan cancellation pada fungsi `generateFiles()` sudah cukup, karena ketika cancelled maka sisa jobs tidak akan dikirim. Tapi pada contoh ini penulis ingin ketika cancelled, maka tidak hanya pengiriman jobs tapi eksekusi jobs juga di-stop secara paksa (meski mungkin masih ada sebagian jobs yang masih dalam antrian).
+
+Silakan tulis kode berikut.
+
+```go
+func createFiles(ctx context.Context, chanIn <-chan FileInfo, numberOfWorkers int) <-chan FileInfo {
+ chanOut := make(chan FileInfo)
+
+ wg := new(sync.WaitGroup)
+ wg.Add(numberOfWorkers)
+
+ go func() {
+ for workerIndex := 0; workerIndex < numberOfWorkers; workerIndex++ {
+ go func(workerIndex int) {
+ for job := range chanIn {
+ select {
+ case <-ctx.Done():
+ break
+ default:
+ filePath := filepath.Join(tempPath, job.FileName)
+ content := randomString(contentLength)
+ err := ioutil.WriteFile(filePath, []byte(content), os.ModePerm)
+
+ log.Println("worker", workerIndex, "working on", job.FileName, "file generation")
+
+ chanOut <- FileInfo{
+ FileName: job.FileName,
+ WorkerIndex: workerIndex,
+ Err: err,
+ }
+ }
+ }
+
+ wg.Done()
+ }(workerIndex)
+ }
+ }()
+
+ go func() {
+ wg.Wait()
+ close(chanOut)
+ }()
+
+ return chanOut
+}
+```
+
+Penambahannya juga sama seperti fungsi-fungsi yang lain, yaitu dengan menambahkan *channel selection*. Ketika ada notifikasi cancel maka perulangan jobs di break. Selebihnya maka harus berjalan seperti normalnya.
+
+## A.61.4. Test Eksekusi Program
+
+Jalankan program ke-dua, lihat hasilnya. Karena sebelumnya kita sudah set durasi timeout adalah **3 detik**, maka jika proses belum selesai sebelum durasi tersebut akan di-cancel secara paksa.
+
+![Context timeout](images/A_pipeline_context_cancellation_2_context_timeout.png)
+
+Cukup mudah bukan?
+
+Silakan coba modifikasi durasinya dengan nilai lebih besar, misalnya **15 detik**, lalu coba jalankan.
+
+![Concurrent without cancellation](images/A_pipeline_context_cancellation_1_concurrent_without_cancellation.png)
+
+Bisa dilihat, di gambar di atas, jika program selesai sebelum **15 detik** maka aman.
+
+## A.61.5. Cancel Context Secara Paksa (Tanpa Timeout)
+
+Coba lakukan modifikasi sedikit pada bagian pemanggilan generate files, dari:
+
+```go
+ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
+defer cancel()
+generateFilesWithContext(ctx)
+```
+
+Ke:
+
+```go
+ctx, cancel := context.WithCancel(context.Background())
+defer cancel()
+time.AfterFunc(timeoutDuration, cancel)
+generateFilesWithContext(ctx)
+```
+
+Lalu coba jalankan, maka hasilnya adalah tetap sama. Jika eksekusi program melebihi context maka akan di-cancel secara paksa, selebihnya aman.
+
+Perbedannya ada pada penerapan *cancellation*-nya. Pada contoh ini kita tidak menggunakan timeout, melainkan menggunakan *explicit cancel* dengan mensimulasi timeout menggunakan `time.AfterFunc()`.
+
+---
+
+
+
Source code praktek pada bab ini tersedia di Github
diff --git a/A-60-simplified-fan-in-fan-out-pipeline.md b/A-simplified-fan-in-fan-out-pipeline.md
similarity index 92%
rename from A-60-simplified-fan-in-fan-out-pipeline.md
rename to A-simplified-fan-in-fan-out-pipeline.md
index 48b38ab0b..9bbc32f9f 100644
--- a/A-60-simplified-fan-in-fan-out-pipeline.md
+++ b/A-simplified-fan-in-fan-out-pipeline.md
@@ -1,6 +1,6 @@
# A.60. Concurrency Pattern: Simplified Fan-out Fan-in Pipeline
-Pada chapter sebelumnya, yaitu [A.59. Concurrency Pattern: Pipeline](/A-59-concurrency-pipeline.html), kita telah mempelajari tentang pipeline pattern, yang dimana pattern tersebut merupakan rekomendasi dari tim Go dalam meng-*handle* jenis kasus sekarangkain proses yang berjalan secara konkuren.
+Pada chapter sebelumnya, yaitu [A.59. Concurrency Pattern: Pipeline](/A-concurrency-pipeline.html), kita telah mempelajari tentang pipeline pattern, yang dimana pattern tersebut merupakan rekomendasi dari tim Go dalam meng-*handle* jenis kasus sekarangkain proses yang berjalan secara konkuren.
> Penulis sangat anjurkan untuk mencoba mempelajari praktek chapter sebelumnya terlebih dahulu jika belum. Karena chapter kali ini ada hubungannya dengan chapter tersebut.
@@ -12,12 +12,12 @@ Ok, agar lebih jelas mari kita mulai praktek.
## A.60.1. Skenario Praktek
-Kita akan modifikasi file program [`1-dummy-file-generator.go` yang pada chapter sebelumnya sudah dibuat](/A-59-concurrency-pipeline.html). Kita rubah mekanisme generate dummy files-nya dari sekuensial ke konkuren.
-
-Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi sekuensialnya. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, disini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+Kita akan modifikasi file program [`1-dummy-file-generator.go` yang pada chapter sebelumnya sudah dibuat](/A-concurrency-pipeline.html). Kita rubah mekanisme generate dummy files-nya dari sekuensial ke konkuren.
## A.60.2. Program Generate Dummy File *Sequentially*
+Ok langsung saja, pertama yang perlu dipersiapkan adalah tulis dulu kode program versi sekuensialnya. Bisa langsung copy-paste, atau tulis dari awal dengan mengikut tutorial ini secara keseluruhan. Untuk penjelasan detail program versi sekuensial silakan merujuk ke chapter sebelumnya saja, disini kita tulis langsung agar bisa cepat dimulai bagian program konkuren.
+
Siapkan folder projek baru, isinya satu buah file `1-generate-dummy-files-sequentially.go`.
#### • Import Packages dan Definisi Variabel
@@ -101,7 +101,7 @@ Pada bagian fungsi `generateFiles()` kali ini sedikit berbeda dibanding sebelumn
Kita lanjut dulu saja. Berikut adalah output jika program di atas di-run.
-![Generate dummy files sequentially](images/A.60_1_generate_dummy_files_sequentially.png)
+![Generate dummy files sequentially](images/A_simplified_fan_in_fan_out_pipeline_1_generate_dummy_files_sequentially.png)
## A.60.3. Program Generate Dummy File *Concurrently*
@@ -325,16 +325,23 @@ Saya akan coba jalankan program pertama dan kedua, lalu mari kita lihat perbedaa
#### • Program Generate Dummy File *Sequentially*
-![Generate dummy files sequentially](images/A.60_2_benchmark.png)
+![Generate dummy files sequentially](images/A_simplified_fan_in_fan_out_pipeline_2_benchmark.png)
Testing di awal chapter ini hasilnya butuh sekitar **19 detik** untuk menyelesaikan generate dummy files sebanyak 3000 secara sekuensial. Tapi kali ini lebih lambat, yaitu **23 detik** dan ini wajar, karena di tiap operasi kita munculkan log ke stdout (via `log.Println()`).
#### • Program Generate Dummy File *Concurrently*
-![Generate dummy files concurrently](images/A.60_3_concurrent.png)
+![Generate dummy files concurrently](images/A_simplified_fan_in_fan_out_pipeline_3_concurrent.png)
Bandingkan dengan ini, **3 detik** saja! luar biasa sekali bukan beda performanya. Dan pastinya akan lebih cepat lagi kalau kita hapus statement untuk logging ke stdout (`log.Println()`).
Nah dari sini semoga cukup jelas ya bedanya kalau dari sisi performa. Inilah pentingnya kenapa konkurensi di Go harus diterapkan (untuk kasus yang memang bisa di-konkurensikan prosesnya). Tapi temen-temen juga harus hati-hati dalam mendesain pipeline dan menentukan jumlah workernya, karena jika tidak tepat bisa makan *resources* seperti CPU dan RAM cukup tinggi.
> Untuk menentukan jumlah worker yang ideal, caranya adalah dengan coba-coba dan disesuaikan dengan spesifikasi server/laptopnya. Jadi tidak ada angka yang pasti berapa jumlah worker ideal. Sangat tergantung ke banyak hal (jenis proses, jumlah pipeline, jumlah worker per pipeline, spesifikasi hardware, dsb).
+
+---
+
+
+
Source code praktek pada bab ini tersedia di Github
diff --git a/B-23-server-handle-cancelled-http-request.md b/B-server-handler-http-request-cancellation.md
similarity index 88%
rename from B-23-server-handle-cancelled-http-request.md
rename to B-server-handler-http-request-cancellation.md
index 67bcda635..8f32f4633 100644
--- a/B-23-server-handle-cancelled-http-request.md
+++ b/B-server-handler-http-request-cancellation.md
@@ -1,4 +1,4 @@
-# B.23. Server Handler untuk Cancelled Client HTTP Request
+# B.23. Server Handler HTTP Request Cancellation
Dalam konteks web application, kadang kala sebuah http request butuh waktu cukup lama untuk selesai, bisa jadi karena kode yang kurang dioptimasi, atau prosesnya memang lama, atau mungkin faktor lainnya. Dari sisi client, biasanya ada handler untuk cancel request jika melebihi batas timeout yang sudah didefinisikan, dan ketika itu terjadi di client akan sangat mudah untuk antisipasinya.
@@ -6,6 +6,8 @@ Berbeda dengan handler di back end-nya, by default request yang sudah di-cancel
Pada bab ini kita akan belajar caranya.
+> Bab ini fokus terhadap cancellation pada client http request. Untuk cancellation pada proses konkuren silakan merujuk ke [A.61. Concurrency Pattern: Context Cancellation Pipeline](A-pipeline-context-cancellation.md).
+
## B.32.1. Praktek
Dari objek `*http.Request` bisa diambil objek context lewat method `.Context()`, dan dari context tersebut kita bisa mendeteksi apakah sebuah request di-cancel atau tidak oleh client.
@@ -80,7 +82,7 @@ Jalankan kode lalu test hasilnya.
curl -X GET http://localhost:8080/
```
-![Cancelled client http request](images/B.23_1_cancelled_request_get.png)
+![Cancelled client http request](images/b_server_handler_http_request_cancellation_1_cancelled_request_get.png)
Pada gambar di atas terdapat dua request, yg pertama sukses dan yang kedua adalah cancelled. Pesan `request cancelled` muncul ketika client http request dibatalkan.
@@ -110,11 +112,11 @@ Jalankan ulang program kemudian test.
curl -X POST http://localhost:8080/ -H 'Content-Type: application/json' -d '{}'
```
-![Cancelled client http request](images/B.23_2_cancelled request_with_payload.png)
+![Cancelled client http request](images/b_server_handler_http_request_cancellation_2_cancelled request_with_payload.png)
---
Source code praktek pada bab ini tersedia di Github
diff --git a/C-35-dockerize-golang.md b/C-dockerize-golang.md
similarity index 97%
rename from C-35-dockerize-golang.md
rename to C-dockerize-golang.md
index 0a36401a0..eed186fea 100644
--- a/C-35-dockerize-golang.md
+++ b/C-dockerize-golang.md
@@ -120,7 +120,7 @@ export INSTANCE_ID="my first instance"
go run main.go
```
-![Hello world](images/c-35-1-start-hello-world.png)
+![Hello world](images/c_dockerize_golang_1_start_hello_world.png)
Ok bisa dilihat aplikasi berjalan sesuai harapan. Selanjutnya kita akan *dockerize* aplikasi hello world ini.
@@ -202,7 +202,7 @@ docker build -t my-image-hello-world .
Kurang lebih outputnya seperti gambar berikut. O iya gunakan *command* `docker images` untuk menampilkan list semua image yang ada di lokal.
-![Build Image](images/c-35-2-build-image.png)
+![Build Image](images/c_dockerize_golang_2_build_image.png)
#### • Create Container
@@ -225,7 +225,7 @@ Command di atas akan menjalankan sebuah proses yang isinya kurang lebih berikut:
Semoga cukup jelas penjabaran di atas. Setelah container berhasil dibuat, cek menggunakan *command* `docker container ls -a` untuk menampilkan list semua container baik yang sedang running maupun tidak.
-![Create Container](images/c-35-3-create-container.png)
+![Create Container](images/c_dockerize_golang_3_create_container.png)
#### • Start Container
@@ -236,7 +236,7 @@ docker container start my-container-hello-world
docker container ls
```
-![Start Container](images/c-35-4-start-container.png)
+![Start Container](images/c_dockerize_golang_4_start_container.png)
Bisa dilihat, sekarang aplikasi web hello world sudah bisa diakses dari host/komputer yang aplikasi tersebut running dalam container docker.
@@ -321,5 +321,5 @@ docker container stop my-container-hello-world
Source code praktek pada bab ini tersedia di Github
diff --git a/C-28-golang-web-socket.md b/D-golang-web-socket-chatting-app.md
similarity index 96%
rename from C-28-golang-web-socket.md
rename to D-golang-web-socket-chatting-app.md
index 51bc09512..1a61dedb2 100644
--- a/C-28-golang-web-socket.md
+++ b/D-golang-web-socket-chatting-app.md
@@ -1,4 +1,4 @@
-# C.28. Web Socket: Chatting App
+# D.3. Web Socket: Chatting App
Pada bab ini kita akan belajar penerapan web socket di Go, untuk membuat sebuah aplikasi chatting. Web socket server dibuat menggunakan library [Gorilla Web Socket](https://github.com/gorilla/websocket), dan di sisi front end kita menggunakan native API milik javascript yaitu [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications) untuk melakukan komunikasi dengan socket server.
@@ -10,9 +10,9 @@ Nantinya saat testing akan ada banyak user terhubung dengan socket server, dalam
Kurang lebih aplikasi yang kita kembangkan seperti gambar di bawah ini.
-![Chatting App](images/C.28_3_chatting.png)
+![Chatting App](images/d_golang_web_socket_chatting_app_3_chatting.png)
-## CB.28.1. Back End
+## D.3.1. Back End
Buat folder projek baru.
@@ -216,7 +216,7 @@ Method `.WriteJSON()` milik `websocket.Conn` digunakan untuk mengirim data dari
Bagian back end sudah cukup. Sekarang lanjut ke layer front end.
-## CB.28.2. Front End
+## D.3.2. Front End
Siapkan terlebih dahulu basis layout front end. Ada dua section penting yg harus disiapkan.
@@ -291,7 +291,7 @@ Tambahkan beberapa stylesheet agar terlihat cantik.
Tampilan sekilas aplikasi bisa dilihat pada gambar di bawah ini.
-![Chatting App Template](images/C.28_1_template.png)
+![Chatting App Template](images/d_golang_web_socket_chatting_app_1_template.png)
OK, sekarang saatnya masuk ke bagian yang paling disukai anak jaman now (?), yaitu javascript. Siapkan beberapa property, satu untuk menampung objek client socket server, dan satu lagi menampung element container (element inilah yang nantinya akan diisi message yang di-broadcast oleh server).
@@ -323,7 +323,7 @@ Fungsi `app.init()` dipanggil pada event `window.onload`.
Di saat pertama kali page load, muncul prompt yang meminta inputan nama user. Nantinya user yang diinput dijadikan sebagai *current* username pada aplikasi chatting ini.
-![Prompt Username](images/C.28_2_prompt_username.png)
+![Prompt Username](images/d_golang_web_socket_chatting_app_2_prompt_username.png)
Property `app.ws` digunakan untuk menampung objek client web socket. Dari objek tersebut, buat 3 buah event listener. Tulis deklarasi event-nya dalam `app.init`.
@@ -392,11 +392,11 @@ app.doSendMessage = function () {
OK, aplikasi sudah siap, mari lanjut ke bagian testing.
-## CB.28.3. Testing
+## D.3.3. Testing
Buka beberapa tab, gunakan username apa saja di masing-masing tab. Coba berinteraksi satu sama lain.
-![Chatting App](images/C.28_3_chatting.png)
+![Chatting App](images/d_golang_web_socket_chatting_app_3_chatting.png)
Bisa dilihat, ketika ada user baru, semua client yang sudah terhubung mendapat pesan **User XXX: connected**.
@@ -404,7 +404,7 @@ Pesan yang ditulis oleh satu client bisa dilihat oleh client lainnya.
Ketika salah satu user leave, pesan **User XXX: disconnected** akan di-broadcast ke semua user lainnya. Pada gambar di bawah ini dicontohkan user **Noval Agung** leave.
-![User leave chat room](images/C.28_4_user_leave.png)
+![User leave chat room](images/d_golang_web_socket_chatting_app_4_user_leave.png)
---
@@ -415,5 +415,5 @@ Ketika salah satu user leave, pesan **User XXX: disconnected** akan di-broadcast
Source code praktek pada bab ini tersedia di Github
diff --git a/C-31-golang-context.md b/D-google-api-search.md
similarity index 91%
rename from C-31-golang-context.md
rename to D-google-api-search.md
index be0cf5180..36bfda780 100644
--- a/C-31-golang-context.md
+++ b/D-google-api-search.md
@@ -1,12 +1,12 @@
-# C.31. Context: Value, Timeout, & Cancellation
+# D.2. Google API Search Dengan Timeout
-Pada bab ini kita akan belajar mengenai pemanfaatan `context.Context` untuk menyisipkan dan membaca data context pada objek `*http.Request`, dan juga untuk handling timeout dan cancelation request.
+Pada bab ini kita akan mencoba studi kasus yaitu membuat web service API untuk *wrap* pencarian ke Google Search API.
Proses pembelajaran dilakukan dengan praktek membuat sebuah aplikasi web service kecil, yang tugasnya melakukan pencarian data. Nantinya akan dibuat juga middleware `MiddlewareUtility`, tugasnya menyisipkan informasi origin dispatcher request, ke dalam context request, sebelum akhirnya sampai pada handler endpoint yg sedang diakses.
-## C.31.1. Context Value
+## D.2.1. Context Value
-Ok, langsung saja, siapkan folder projek baru dengan struktur seperti berikut.
+Ok, langsung saja, siapkan folder projek projek baru dengan struktur seperti berikut.
```bash
mkdir chapter-c31
@@ -14,7 +14,6 @@ cd chapter-c31
go mod init chapter-c31
# then prepare underneath structures
-
tree .
.
├── main.go
@@ -52,7 +51,6 @@ func (c *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Lalu pada file `main.go`, buat satu buah endpoint `/api/search`, dengan isi handler menampilkan data `from` yang diambil dari request context. Data `from` ini di set ke dalam request context oleh middleware `MiddlewareUtility`.
-
```go
package main
@@ -113,9 +111,7 @@ Objek request context bisa didapat lewat pengaksesan method `.Context()` milik `
Pada kode di atas, jika context adalah `nil`, maka di-inisialisasi dengan context baru lewat `context.Background()`.
-> Selain lewat `context.Background()`, pembuatan context juga bisa dilakukan lewat `context.TODO()`. Mengenai perbedaannya silakan cek [di laman dokumentasi `context`](https://golang.org/pkg/context/#Background).
-
-Objek `ctx` yang merupakan `context.Context`, kita tempeli data `from`. Cara melakukannya dengan memanggil statement `context.WithValue()` dengan disisipi 3 buah parameter.
+Objek `ctx` yang merupakan `context.Context` disini kita tempeli data `from`. Cara melakukannya dengan memanggil statement `context.WithValue()` dengan disisipi 3 buah parameter.
1. Parameter ke-1, isinya adalah objek context.
2. Parameter ke-2, isinya key dari data yang akan disimpan.
@@ -127,15 +123,15 @@ Ok, sekarang objek `ctx` sudah dimodifikasi. Objek ini perlu untuk ditempelkan l
Jalankan aplikasi, hasilnya kurang lebih seperti gambar berikut.
-![Context example](images/C.31_1_context_example.png)
+![Context example](images/d_google_api_search_1_context_example.png)
O iya, penulis tidak menggunakan `http://localhost` untuk mengakses aplikasi, melainkan menggunakan `http://mysampletestapp.com`, dengan catatan domain ini sudah saya arahkan ke 127.0.0.1.
-![Etc Host](images/C.31_2_etc_hosts.png)
+![Etc Host](images/d_google_api_search_2_etc_hosts.png)
Ok, untuk sekarang sepertinya cukup jelas mengenai penggunaan context pada objek http request. Tinggal kembangkan saja sesuai kebutuhan, seperti contohnya: context untuk menyimpan data session, yang diambil dari database sessuai dengan session id nya.
-## C.31.2. Context Timeout & Cancelation
+## D.2.2. Context Timeout & Cancellation
Melanjutkan program yang sudah dibuat, nantinya pada endpoint `/api/search` akan dilakukan sebuah pencarian ke Google sesuai dengan keyword yang diinginkan. Pencarian dilakukan dengan memanfaatkan [Custom Search JSON API](https://developers.google.com/custom-search/json-api/v1/overview) milik Google.
@@ -298,11 +294,11 @@ Jika di-summary, maka yang dilakukan oleh fungsi `doSearch` kurang lebih sebagai
Jalankan, lihat hasilnya.
-![Request response OK](images/C.31_3_result_ok.png)
+![Request response OK](images/d_google_api_search_3_result_ok.png)
Informasi tambahan: best practice mengenai cancelation context adalah untuk selalu menambahkan `defer cancel()` setelah (cancelation) context dibuat. Lebih detailnya silakan baca https://blog.golang.org/context.
-## C.31.3. Google Search API Restrictions Referer
+## D.2.3. Google Search API Restrictions Referer
Di bagian awal bab ini, kita belajar mengenai context value. Kenapa penulis memilih menggunakan context untuk menyisipkan data referer, kenapa tidak contoh yg lebih umum seperti session dan lainnya? sebenarnya ada alasannya.
@@ -313,7 +309,7 @@ Silakan coba akses kedua url berikut.
Harusnya yang dikembalikan sama, tapi kenyataannya pengaksesan lewat url `localhost` menghasilkan error.
-![Request response fail](images/C.31_4_result_fail.png)
+![Request response fail](images/d_google_api_search_4_result_fail.png)
Error message:
@@ -321,11 +317,11 @@ Error message:
Error di atas muncul karena, host `localhost` belum didaftarkan pada API console. Berbeda dengan `mysampletestapp.com` yang sudah didaftarkan, host ini berhak mengakses menggunakan API key yang kita gunakan.
-![Api configuration](images/C.31_5_api_hostname.png)
+![Api configuration](images/d_google_api_search_5_api_hostname.png)
---
Source code praktek pada bab ini tersedia di Github
diff --git a/D-1-insert-1mil-csv-record-into-db-in-a-minute.md b/D-insert-1mil-csv-record-into-db-in-a-minute.md
similarity index 98%
rename from D-1-insert-1mil-csv-record-into-db-in-a-minute.md
rename to D-insert-1mil-csv-record-into-db-in-a-minute.md
index a0483e43c..69ebfa72d 100644
--- a/D-1-insert-1mil-csv-record-into-db-in-a-minute.md
+++ b/D-insert-1mil-csv-record-into-db-in-a-minute.md
@@ -319,7 +319,7 @@ Di akhir fungsi main ditambahkan log untuk benchmark performa.
Ok, sekarang mari kita coba eksekusi program-nya.
-![Preview](images/d-1-insert-1mil-csv-record-into-db-in-a-minute.png)
+![Preview](images/d_insert_1mil_csv_record_into_db_in_a_minute_1_preview.png)
Nah, bisa dilihat operasi insert selesai dalam waktu sekitar 1 menitan. Saya menggunakan laptop dengan spek berikut untuk run program:
@@ -343,5 +343,5 @@ Praktek pada bab ini sifatnya adalah POC, jadi sangat mungkin diperlukan penyesu
Source code praktek pada bab ini tersedia di Github