Oversimplified — code-executor
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:
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.
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
.
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:
- 05AB1E
- Bash
- Brainfuck
- C
- Cplusplus
- Golfscript
- Java
- Javascript
- Perl
- Python
- Ruby
- Rust
- 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.
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.