Skip to content
Open
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9ff2d0e
docs: create author and build your own async article info
chlin501 Oct 23, 2025
37bba0d
docs: add outline and update some sentences
chlin501 Oct 23, 2025
564a371
docs: update build your own async article
chlin501 Oct 27, 2025
ee2ecb4
docs: unified typesetting style
chlin501 Oct 27, 2025
24db0ad
docs: update worker section and some typesetting
chlin501 Oct 27, 2025
3a0fd86
docs: add LL scheduler next batch paragraph
chlin501 Oct 28, 2025
0a9ae12
docs: add LL scheduler least loaded paragraph
chlin501 Oct 28, 2025
79374c7
docs: remove line number in code block
chlin501 Oct 28, 2025
ff588ee
docs: update dir layout and code compilation info
chlin501 Oct 28, 2025
85b3320
docs: update scheduler, and some compilation info
chlin501 Oct 29, 2025
02fe4cf
docs: update authors info and photo image
chlin501 Oct 29, 2025
2642be7
docs: update execution commands, and runtime info
chlin501 Oct 30, 2025
4b29b0e
docs: update higher overview class diagram
chlin501 Oct 30, 2025
6da8b38
docs: add conclusion
chlin501 Oct 30, 2025
800d9a7
chore: add graalvm tag
chlin501 Oct 30, 2025
7dbf1f4
docs: fix typos, and rephrase some sentencesh
chlin501 Oct 30, 2025
b31458c
docs: add an example that runs the async program
chlin501 Nov 2, 2025
db9e1f1
docs: append fiber text to zio, and cats effect
chlin501 Nov 2, 2025
06d1c2d
docs: add plural to the noun
chlin501 Nov 2, 2025
27fc9d7
docs: update profile photo
chlin501 Nov 11, 2025
f89e5c8
docs: update the article based on review
chlin501 Nov 11, 2025
f8501b2
docs: rename the async name to concurrent
chlin501 Nov 11, 2025
13b99cd
docs: update make targets' line number
chlin501 Nov 11, 2025
27f58ea
docs: update wording for the introduction
chlin501 Nov 11, 2025
ddda574
docs: update the links to makefile line number
chlin501 Nov 11, 2025
4f1ca09
docs: update wording in introduction section
chlin501 Nov 11, 2025
606d061
docs: rephrase wording to alternative apporach
chlin501 Nov 11, 2025
00e0f9c
docs: update photo image
chlin501 Nov 12, 2025
8977216
Merge remote-tracking branch 'upstream/main' into build-your-own-async
chlin501 Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
docs: update the article based on review
  • Loading branch information
chlin501 committed Nov 11, 2025
commit f89e5c8e3beb137419e5d43c37f95f7e30c4f144
20 changes: 10 additions & 10 deletions src/data/articles/build-your-own-async/index.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: "Build your own async"
excerpt: "This article provides some information on how to build your own async in Scala"
title: "Coroutines, Event Loops - Build Your Own in Scala"
excerpt: "What better way to learn about concurrency than roll your own?"
category: guide
tags: [async, coroutines, graalvm, scala, scala-3]
tags: [coroutines, graalvm, scala, scala-3]
publishedDate: 2025-10-23
updatedDate: 2025-10-23
author: chia-hung-lin
Expand All @@ -12,19 +12,19 @@ difficulty: intermediate

## Introduction

When writing programes with the aid of async related tools, have you ever wondered how [async](https://en.wikipedia.org/wiki/Asynchronous_I/O#Light-weight_processes_or_threads) works under the hood? I have a similar question. Here is the journey of exploring an apporach alternative to monadic operations style employeed by libraries such as ZIO Fiber, cats-effect Fiber.
When writing concurrency programs, have you ever wondered how its mechanism may work under the hood? I have a similar question. Here is the journey of exploring an apporach alternative to monadic operations style employeed by libraries such as ZIO Fiber, cats-effect Fiber.

## Concepts

Before starting, two concepts are important, including
Before starting, two concepts are important

1. [Coroutine](#coroutine)

2. [Event Loop](#event-loop)

### Coroutine

[Coroutine](https://en.wikipedia.org/wiki/Coroutine), according to the Wikipedia, allows an execution to be suspended, and resumed from where it was left off. From the code snippet below, we can observe that the coroutine *Gen* **yield**s values at the lines 3rd, 5th, and 7th, and the main thread notifies the coroutine by **send** method at the line 17th; *Gen* instance can then output those values to console at the lines 4th, 6th, and 8th.
[Coroutine](https://en.wikipedia.org/wiki/Coroutine), according to Wikipedia, allows an execution to be suspended, and resumed from where it was left off. From the code snippet below, we can observe that the coroutine *Gen* **yield**s values at the lines 3rd, 5th, and 7th, and the main thread notifies the coroutine by **send** method at the line 17th; *Gen* instance can then output those values to console at the lines 4th, 6th, and 8th.

```scala
class Gen extends Coroutine[String, Int] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is not runnable at this point because if the reader follows along, he/she will not be able to run it until we have the Coroutine definition, which comes later.

I think it's best to cut the code out and explain coroutines in more general terms at this point in the article. You can also refer to the other article on the site that we've written on Kotlin.

An alternative idea is to use pseudocode, the way you did with the event loop, and describe what we expect to happen.

Another option is to keep the code, but explain in the introduction that the best way to read this article is with the full code nearby, instead of replicating everything step by step.

Expand Down Expand Up @@ -401,7 +401,7 @@ Whilst searching the next batch's range, first keep the value of current round (

Second, find the next multiple of value (from the line 7th to 8th). The line 7th by adding `batch size - 1` to **worker length** ensures the obtained number `multiple` is at least as large as the next multiple, and smaller than the one after that. Then, using that number, i.e., `mutliple`, subtracts the modulo value for acquiring the desired next multiple of number. For instance, with the setting of 22 workers, and batch size 8, the **multiple** value is 29, which is larger than the next multiple value 24, but is smaller than its next multiple of value after 24, which is 32.

Third, calculate the next batch's range. The line 9th makes sure the next value will rorate when exceeding the expected next multiple of number. And the line 10th picks up the minimum value between **workers length**, and `next + batch size`, setting the end of range value to the worker length when the `next + batch size` exceeds the value of **worker length**. Again with the setting of 22 workers, and batch size 8, when the `next + batch size` reaches 24, the logic picks up the worker length 22 instead.
Third, calculate the next batch's range. The line 9th makes sure the next value will rotate when exceeding the expected next multiple of number. And the line 10th picks up the minimum value between **workers length**, and `next + batch size`, setting the end of range value to the worker length when the `next + batch size` exceeds the value of **worker length**. Again with the setting of 22 workers, and batch size 8, when the `next + batch size` reaches 24, the logic picks up the worker length 22 instead.

Therefore, configuring 22 workers with 8 as its batch size, the range of next batch values in sequence should be (0, 8), (8, 16), (16, 22), and then start over from (0, 8) again.

Expand All @@ -421,7 +421,7 @@ final case class LeastLoadedScheduler[T](
}
```

2. Pick up the lightest loading *Worker*
2. Pick up the lightest loading *worker*

In order to find the least loaded worker, the logic first checks if the worker length is smaller than the batch size - if true, the entire wokrer list is returned (at the line 3rd); otherwise, the next batch range is calculated (at the line 5th), and then the logic picks up the worker list based on the range given.

Expand Down Expand Up @@ -460,7 +460,7 @@ override def leastLoaded(): (Worker[T], LeastLoadedScheduler[T]) = {

Finally, it is the time to orchestrate the entire flow. The *Runtime* object in fact performs following functions

1. At the beginning, it creates a scheduler (at the line 5th), and a queue (at the line 6th) shared with all tasks for signifying when a task accomplishes its execution
1. At the beginning, it creates a scheduler (at the line 5th), and a queue (at the line 6th) shared with all tasks for signalling when a task accomplishes its execution

```scala
final def apply(): Runtime = {
Expand Down Expand Up @@ -675,7 +675,7 @@ curl -s -L -O --create-dirs --output-dir ./async/libs \

## Conclusion

We visit the concepts that compries async for developing software project. Then, we walk through the components that could be used to build such tool. Benefits of understanding async mechenism under the hood not merely help developers grasp an idea that can improve the software responsiveness, but also assist developers comprehend the structure and relationship between necessary components. With this mindset, developers may solve problems from different angles, and may have a better idea when making a trade-off.
We revisited some concurrency primitives that are necessary for complex software, and we learned about the internal mechanism by writing our own. Hopefully, understanding this machinery under the hood should both help you internalize ideas that can improve the software performance, but also assist you in visualizing the structure and relationship between the necessary components. With this mindset, you may solve problems from different angles, and may have a better idea when making a trade-off.

## References

Expand Down