So what exactly is an Event Loop?

Introduction

Most of you are knowing that I am working a lot with Redis. And some of you might also know that Redis OSS 'standalone' is mainly single-threaded. The reason why it can achieve that high throughputs on a single instance is that it uses an event loop. But what the hell is an event loop and how does it work?

First of all, let me tell you the story behind this article. It all started last weekend. For some reason, I found the time to read a Kotlin book. Not sure why I did, guess I just had the feeling that I am too long disconnected from actual development tasks and wanted to explore one of the comparable new programming languages. The book was great and I had the impression that Kotlin is actually quite nice. Then I went back to my main task (helping to enable the Technical Field at Redis Labs) and had to work on a slide about the Redis event loop. A look at the source code (and the following article https://redis.io/topics/internals-rediseventlib), raised a question for me: Why is there a TimeEvent? The answer might finally sound simple, but just the fact that I dug a bit deeper into the event loop topic caused the idea to just implement a very simple event loop in Kotlin. So I did a bit more research and found also very good explanations of how the Node.js event loop works.

So this article has the intention to share my learnings by using this light-weighted event loop that I implemented as an academic example


What is kEventLib?

So kEventLib is a light-weighted event loop implementation in Kotlin. As said, this project has more academical character. The idea is to illustrate how an event loop works by giving me the chance to play a bit around with Kotlin.


What's an Event?

We are defining a generic event as something which can happen at a specific time, has a type and a payload:



More specific events were derived from Event:
  • SimpleEvent: An event without a specific time. It doesn't matter exactly when such an event should be executed
  • TimedEvent: An event which allows passing a delay, which means that the event should not be executed before this time is over
Timed events are having a lower priority than non-timed events. So we will execute non-timed events first, but we are considering that timed events are deferred to be executed in the future.
An excellent example of timed events would be 'disk write' events in Redis or async calls in Node.js.


Why do we need an Event Queue and Event Buffer?

I decided to implement two different structures, dependent on if it is about a non-timed event or a timed event.
  • EventQueue: We are using the event queue to process the events in the order of their appearance. Node.js is using a stack instead of a queue because calls can be nested, and so a call-stack makes more sense.
  • EventBuffer: This structure is used to buffer timed events. My naive event-loop works in a way that timed events are only processed after all non-timed events are processed. You could indeed think of more sophisticated scheduling approaches.

What does the Event Loop?

The event loop is a ... loop which runs a function call in a single thread:


Processing an event means to check first if the event queue is empty. If not, then we are processing one of the queued events. If it is empty, then we start processing the buffered events. All buffered events that are in the past will be processed, whereby the event with the minimum timestamp (the one which happened earliest) will be processed first.
It can happen that no event can be processed. Then an EmptyEvent is returned. It can also happen that an error occurs when submitting an event to the loop. This will return an ErrorEvent. Such an error is caused by the fact that either the event loop's queue or buffer is fully utilized. The 'submitter' would then need to implement a back-pressure mechanism.


Show me an Example!

Here some example code:



 The execution output (handling an event just prints some details about it) looks like:



Events that are printed with the prefix '-1' are non-timed events. Otherwise, the prefix is the timestamp (to which the event was deferred to).

You can see that the non-timed events were executed first. Then we didn't submit non-timed for a while (due to the sleep after every submission of a timed event) which is the reason why some of the timed events are executed. Then we are executing the non-timed events again and finally the deferred timed ones.

 I hope you enjoyed reading this post. The full source code can be found here:  https://github.com/nosqlgeek/kEventLib .