Threads
Overview
Threads are a way to, not just run code in the background, but actually run code as an entirely separate process, this can be used for a variety of functionality including detatched services and more.
A thread is a separate process that is spawned from the main process, while keeping an IPC connection alive, and then, similar to Workers, used to communicate with it. Threads contain a set a set of lifecycle hooks that are separate from the main application, including init
, start
and stop
these are the methods the thread manager will look for and attempt to initialise.
Due to it being a separate process, when you extend the BaseThread
class this will come with a variety of built-in functionality including logging, dispatching controller events and more, see @sodacore/core - threads for more information.
Prerequesites
Threads are also wrapped and need to be imported, therefore you need to ensure the same prerequisites are met as with Workers.
Usage
In the below demo we are going to create a thread that is going to run a "debug" server, in which we can dispatch events to a thread controller.
// File: src/threads/debug-server.ts
import type { IConfig } from '@sodacore/http';
import { type Server, serve } from 'bun';
import { Thread, Utils, BaseThread } from '@sodacore/core';
import { Registry } from '@sodacore/registry';
// Our decorator that defines our path, and then a callback function we can use to define some configs at runtime.
@Thread(Utils.resolve(import.meta.filename, '../example.js'), () => ({
httpConfig: Registry.get('@http:config'),
}))
export default class ExampleThread extends BaseThread {
private port!: number;
private server!: Server;
// This method is called on init of the application,
// so let's add some logic to set the port to the normal
// http port + 1.
public async onInit() {
const config = this.getArgv<IConfig>('httpConfig');
this.port = (config?.port ?? 3000) + 1;
}
// This method will actually create the http server, but
// then on request, it will accept the request, and pass
// it to the main thread to be processed and expects a
// html string in the return.
public async onStart() {
this.info(`Starting debug http server, on port: ${this.port}.`);
this.server = serve({
port: this.port,
fetch: async request => {
// Let's dispatch the event.
const result = await this.dispatch<{ html: string }>('example:handle', {
url: request.url,
});
// And then send a response.
return new Response(result.html, {
status: 200,
headers: {
'Content-Type': 'text/html',
},
});
},
});
}
// Triggered when the thread is stopped, let's just log
// that we are shutting down the server, and stop it.
public async onStop() {
this.info('Shutting down debug http server.');
this.server.stop();
}
}
Fairly simple right? We have our basic events, onInit
, onStart
and onStop
, and we use them to prepare our http server, the rest is then handled via the main thread.
Note:
onInit
,onStart
andonStop
are built-in events that, via the IPC and base thread handler, will be called if they exist, you can omit them if you don't need them, learn more here: @sodacore/core - thread events.
Now to support the above we need to create a thread controller to handle it, this is where the disaptched event will go to.
// File: src/controllers/debug.ts
import { Controller, ThreadContext } from '@sodacore/core';
@Controller('example')
export default class ThreadController {
// This method as you can see by `example` namespace against the class
// and then the method name `handle`.
public async handle(context: ThreadContext) {
// Let's get the context data.
const data = context.getContext<{ url: string }>();
// Let's create a page.
const html = `
<html>
<head>
<title>Sodacore Debug Example</title>
<style>
body { background-color: #19171E; }
body * { color: #FFFFFF; font-family: sans-serif; }
</style>
</head>
<body>
<h1>Hello World!</h1>
<p>Request URL: ${data.url}</p>
<p>Generated at: ${new Date().toISOString()}</p>
</body>
</html>
`;
// Return the html.
return { html };
}
}
A simple example that shows that we generate a html page, add the given URL to it, and then return it.
Note when working with thread dispatched events (either dispatching to the thread or from the thread), the way to send back data is via the return object.
If you run this, you can then test your endpoint on the normal http port + 1, and then see the response.