feat(api): Add support for multithreaded API servers

This commit is contained in:
Jesse Wierzbinski 2024-05-13 11:36:46 -10:00
parent e502a2d8c8
commit 6c3fcf699e
No known key found for this signature in database
6 changed files with 66 additions and 1 deletions

49
cli/commands/start.ts Normal file
View file

@ -0,0 +1,49 @@
import os from "node:os";
import { Flags } from "@oclif/core";
import { BaseCommand } from "~cli/base";
export default class Start extends BaseCommand<typeof Start> {
static override args = {};
static override description = "Starts Lysand";
static override examples = [
"<%= config.bin %> <%= command.id %> --threads 4",
"<%= config.bin %> <%= command.id %> --all-threads",
];
static override flags = {
threads: Flags.integer({
char: "t",
description: "Number of threads to use",
default: 1,
exclusive: ["all-threads"],
}),
"all-threads": Flags.boolean({
description: "Use all available threads",
default: false,
exclusive: ["threads"],
}),
silent: Flags.boolean({
description: "Don't show logs in console",
default: false,
}),
};
public async run(): Promise<void> {
const { flags } = await this.parse(Start);
const numCPUs = flags["all-threads"] ? os.cpus().length : flags.threads;
for (let i = 0; i < numCPUs; i++) {
const args = ["bun", "index.ts"];
if (i !== 0 || flags.silent) {
args.push("--silent");
}
Bun.spawn(args, {
stdio: ["inherit", "inherit", "inherit"],
env: { ...process.env },
});
}
}
}

View file

@ -3,6 +3,7 @@ import EmojiAdd from "./commands/emoji/add";
import EmojiDelete from "./commands/emoji/delete"; import EmojiDelete from "./commands/emoji/delete";
import EmojiImport from "./commands/emoji/import"; import EmojiImport from "./commands/emoji/import";
import EmojiList from "./commands/emoji/list"; import EmojiList from "./commands/emoji/list";
import Start from "./commands/start";
import UserCreate from "./commands/user/create"; import UserCreate from "./commands/user/create";
import UserDelete from "./commands/user/delete"; import UserDelete from "./commands/user/delete";
import UserList from "./commands/user/list"; import UserList from "./commands/user/list";
@ -18,6 +19,7 @@ export const commands = {
"emoji:delete": EmojiDelete, "emoji:delete": EmojiDelete,
"emoji:list": EmojiList, "emoji:list": EmojiList,
"emoji:import": EmojiImport, "emoji:import": EmojiImport,
start: Start,
}; };
if (import.meta.path === Bun.main) { if (import.meta.path === Bun.main) {

View file

@ -20,6 +20,12 @@ export const setupDatabase = async (
try { try {
await client.connect(); await client.connect();
} catch (e) { } catch (e) {
if (
(e as Error).message ===
"Client has already been connected. You cannot reuse a client."
)
return;
await logger.logError(LogLevel.CRITICAL, "Database", e as Error); await logger.logError(LogLevel.CRITICAL, "Database", e as Error);
await logger.log( await logger.log(

View file

@ -18,7 +18,8 @@ import type { APIRouteExports } from "~types/api";
const timeAtStart = performance.now(); const timeAtStart = performance.now();
const isEntry = import.meta.path === Bun.main; const isEntry =
import.meta.path === Bun.main && !process.argv.includes("--silent");
let dualServerLogger: LogManager | MultiLogManager = new LogManager( let dualServerLogger: LogManager | MultiLogManager = new LogManager(
Bun.file("/dev/null"), Bun.file("/dev/null"),

View file

@ -4,6 +4,7 @@ import type { Hono } from "hono";
export const createServer = (config: Config, app: Hono) => export const createServer = (config: Config, app: Hono) =>
Bun.serve({ Bun.serve({
port: config.http.bind_port, port: config.http.bind_port,
reusePort: true,
tls: config.http.tls.enabled tls: config.http.tls.enabled
? { ? {
key: Bun.file(config.http.tls.key), key: Bun.file(config.http.tls.key),

View file

@ -1,17 +1,23 @@
import { randomBytes } from "node:crypto"; import { randomBytes } from "node:crypto";
import { consoleLogger } from "@loggers";
import { asc, inArray, like } from "drizzle-orm"; import { asc, inArray, like } from "drizzle-orm";
import type { Status } from "~database/entities/Status"; import type { Status } from "~database/entities/Status";
import { db } from "~drizzle/db"; import { db } from "~drizzle/db";
import { setupDatabase } from "~drizzle/db";
import { Notes, Tokens, Users } from "~drizzle/schema"; import { Notes, Tokens, Users } from "~drizzle/schema";
import { app } from "~index"; import { app } from "~index";
import { Note } from "~packages/database-interface/note"; import { Note } from "~packages/database-interface/note";
import { User } from "~packages/database-interface/user"; import { User } from "~packages/database-interface/user";
await setupDatabase(consoleLogger);
/** /**
* This allows us to send a test request to the server even when it isnt running * This allows us to send a test request to the server even when it isnt running
* @param req Request to send * @param req Request to send
* @returns Response from the server * @returns Response from the server
*/ */
export async function sendTestRequest(req: Request) { export async function sendTestRequest(req: Request) {
// return fetch(req);
return app.fetch(req); return app.fetch(req);
} }