Oversimplified — code-executor

Ashikka Gupta
csivit
Published in
5 min readJan 28, 2021
A random laptop

Ever wanted to make your own online coding platform, but got stuck with the technicalities of code execution and job scheduling. Don’t worry, we @csivitu have just the perfect solution for this; code-executor - A CLI/library to execute code against test cases in various languages and obtain relevant results.

What is code-executor?

code-executor is a Node.js library built for the purpose of executing code in an isolated container against user-defined test cases. code-executor allows you to run arbitrary code in scalable, secure containers and returns metrics for each test case, such as the time taken, errors that occurred (if any), and the status (pass/fail). This library uses a master-worker structure to run programs, which makes it scalable across servers as long as they use the same redis instance.

How does everything work?

Before we dive into the intricacies of code-executor, let us familiarize ourselves with a few terms.

Job refers to the task of execution of a single program which is passed using a queue to the workers.

Queue is a linear structure that follows First In First Out (FIFO) approach. A good example of a queue is any queue of consumers for a resource where the consumer that came first is served first.

The main classes in code-executor are:

A random boss

1. CodeExecutor Class

This class is the employer/boss which is responsible for assigning work to a set of employees, i.e Workers. These workers respond to the employer on completing their tasks. Now, this boss manages all the tasks ( processes) that coming up by adding them to a queue, which is stored in the redis instance and managed by the bull library. This abstract process synchronization ensures that no two workers are working on the same job.

A random employee

2. Worker Class

We can think of the Worker class as an employee that is assigned all the work. Once the Workers finish executing the code, they respond to the CodeExector , i.e the employer, and the backend can respond with success or failure, and other details returned by the Worker.

A random builder

3. Builder Class

Till now, we have discussed how the assignment of jobs takes place using our code-executor, but how do we actually run the code?

We use dockerode which is a Node.js module for Docker’s Remote API. We first build docker containers for the supported languages namely:

  1. 05AB1E
  2. Bash
  3. Brainfuck
  4. C
  5. Cplusplus
  6. Golfscript
  7. Java
  8. Javascript
  9. Perl
  10. Python
  11. Ruby
  12. Rust
  13. Swift

If you’ve got a language that would like to see in the above list, just go to the official Github repo, add a Dockerfile for it and file a pull request. We’ll do the rest.

A random runner

2. Runner Class

As the name suggests, the Runner class runs this code against all the provided test cases and timeouts (duh). It also saves code to a randomly generated file.

How to use code-executor?

1. Install it

You can install code-executor using npm.

npm install code-executor --save

The default export from the code-executor library is the CodeExecutor class. This is the master class, which can be run on the backend of your website. The purpose of this class is to assign jobs to the workers ( queue as mentioned before, though this is abstracted so you need not worry about it). You can create an object of CodeExecutor and keep adding jobs to it as you keep getting submissions on your website.

2. Use it

Now, say you received a submission from a user, and you want to run their code against a set of test cases. You can use the following code inside your route handler.

By now, we know how to use the CodeExecutor class to assign jobs to workers. Now, we see how to use the Worker class to create workers that will run your code.

An object of the Worker class has the following important functions:

1. worker.build()

worker.build() is responsible for building docker images on the system. You can pass a list of languages to the function so that it builds images for just the specified languages.

2. worker.start()

On running worker.start(), the current worker starts listening in the redis queue. After this function is executed, whenever there is a new job on the queue that has not been taken by another worker (if any), this worker will take up the job and run the code.

3. worker.pause()

You can pause the execution of a worker with the help of the worker.pause() function.

4. worker.resume()

Executing the worker.resume() function resumes processing jobs from the queue.

That’s all folks!

To learn more about code-executor, please visit the official Github repository. We also are registered as an official library on the npm website. You can also contribute to the project. Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

--

--