Experimental unix socket mode

This commit is contained in:
Rory&
2025-12-16 11:12:31 +01:00
parent 01fa1a9f2a
commit 9b786925b7
4 changed files with 205 additions and 47 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<scripts>
<script value="start:cdn" />
</scripts>
<node-interpreter value="/nix/store/dcdc33kdjdhjnzg6rkmd0cx4kpwl8cac-nodejs-20.17.0/bin/node" />
<node-interpreter value="project" />
<package-manager value="npm" />
<envs />
<EXTENSION ID="com.fapiko.jetbrains.plugins.better_direnv.runconfigs.NodeRunConfiguration">
+53 -43
View File
@@ -24,12 +24,19 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="HighlightingSettingsPerFile">
<setting file="file://$USER_HOME$/.cache/JetBrains/WebStorm2024.3/javascript/typings/node/20.17.10/node_modules/@types/node/crypto.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/amqplib/index.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/amqplib/properties.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/jsonwebtoken/index.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/node/events.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/result/node_modules/lambert-server/src/Server.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/result/node_modules/lambert-server/src/Utils.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/ws/index.d.mts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/ws/index.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/@types/ws/package.json" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/data-source/DataSource.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/driver/types/ReplicationMode.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/Brackets.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/SelectQueryBuilder.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/WhereExpressionBuilder.d.ts" root0="SKIP_INSPECTION" />
<setting file="file://$PROJECT_DIR$/node_modules/typeorm/repository/BaseEntity.d.ts" root0="SKIP_INSPECTION" />
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="ProjectErrors" />
@@ -43,56 +50,56 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix",
"NIXITCH_NIX_CONF_DIR": "",
"NIXITCH_NIX_OTHER_STORES": "",
"NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source",
"NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile",
"NIXITCH_NIX_REMOTE": "",
"NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory",
"Node.js.Server.ts.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "master",
"javascript.nodejs.core.library.configured.version": "24.8.0",
"javascript.nodejs.core.library.typings.version": "24.7.0",
"last_opened_file_path": "/home/Rory/git/spacebar/server-master/src/schemas/api/users",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.standard": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.standard": "",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_interpreter_path": "node",
"nodejs_package_manager_path": "npm",
"npm.Start API.executor": "Run",
"npm.Start CDN.executor": "Run",
"npm.Start Gateway.executor": "Run",
"npm.build.executor": "Run",
"npm.start.executor": "Debug",
"prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier",
"settings.editor.selected.configurable": "com.github.copilot.settings.customInstructions.CopilotInstructionsConfigurable",
"ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;/etc/nix/nixpkgs-config.nix&quot;,
&quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_PATH&quot;: &quot;/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source&quot;,
&quot;NIXITCH_NIX_PROFILES&quot;: &quot;/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile&quot;,
&quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;,
&quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;/nix/var/nix/profiles/per-user/Rory&quot;,
&quot;Node.js.Server.ts.executor&quot;: &quot;Debug&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;javascript.nodejs.core.library.configured.version&quot;: &quot;24.8.0&quot;,
&quot;javascript.nodejs.core.library.typings.version&quot;: &quot;24.7.0&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/Rory/git/spacebar/server-master/src/util/migration/postgres&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.standard&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.standard&quot;: &quot;&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_interpreter_path&quot;: &quot;node&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;npm.Start API.executor&quot;: &quot;Run&quot;,
&quot;npm.Start CDN.executor&quot;: &quot;Run&quot;,
&quot;npm.Start Gateway.executor&quot;: &quot;Run&quot;,
&quot;npm.build.executor&quot;: &quot;Run&quot;,
&quot;npm.start.executor&quot;: &quot;Debug&quot;,
&quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/prettier&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;com.github.copilot.settings.customInstructions.CopilotInstructionsConfigurable&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/typescript/lib&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
],
"GitStage.ChangesTree.GroupingKeys": [
"directory",
"module",
"repository"
&quot;GitStage.ChangesTree.GroupingKeys&quot;: [
&quot;directory&quot;,
&quot;module&quot;,
&quot;repository&quot;
]
}
}]]></component>
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/util/migration/postgres" />
<recent name="$PROJECT_DIR$/src/schemas/api/users" />
<recent name="$PROJECT_DIR$/src/util/entities" />
<recent name="$PROJECT_DIR$/src/gateway/opcodes" />
<recent name="$PROJECT_DIR$/src/api/routes/users/@me/billing" />
<recent name="$PROJECT_DIR$/src/util/util/networking/abuseipdb" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/schemas/api/guilds" />
@@ -117,6 +124,7 @@
<entry key="Start CDN" value="STOPPED" />
<entry key="Start Gateway" value="STOPPED" />
<entry key="Start bundle" value="STOPPED" />
<entry key="build" value="STOPPED" />
</map>
</value>
</entry>
@@ -165,6 +173,8 @@
<workItem from="1764432507485" duration="30779000" />
<workItem from="1764521899659" duration="22029000" />
<workItem from="1765142585081" duration="16912000" />
<workItem from="1765849511651" duration="7921000" />
<workItem from="1765861713706" duration="18045000" />
</task>
<servers />
</component>
+1 -3
View File
@@ -187,9 +187,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
}
break;
case "CHANNEL_CREATE":
if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) {
return;
}
if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) return;
this.events[id] = await listenEvent(id, consumer, listenOpts);
break;
case "RELATIONSHIP_ADD":
+150
View File
@@ -21,7 +21,14 @@ import { RabbitMQ } from "./RabbitMQ";
import EventEmitter from "events";
import { EVENT, Event } from "../interfaces";
import { randomUUID } from "crypto";
import path from "path";
import { Socket } from "node:net";
import { FSWatcher } from "node:fs";
import { Stopwatch } from "./Stopwatch";
import { Config } from "./Config";
export const events = new EventEmitter();
let unixSocketListener: UnixSocketListener | null = null;
let unixSocketWriter: UnixSocketWriter | null = null;
export async function emitEvent(payload: Omit<Event, "created_at">) {
const id = (payload.guild_id || payload.channel_id || payload.user_id) as string;
@@ -42,6 +49,12 @@ export async function emitEvent(payload: Omit<Event, "created_at">) {
// todo: should we retry publishng the event?
console.log("[RabbitMQ] ", e);
}
} else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) {
if (!unixSocketWriter) {
unixSocketWriter = new UnixSocketWriter(process.env.EVENT_SOCKET_PATH);
await unixSocketWriter.init();
}
await unixSocketWriter.emit(payload);
} else if (process.env.EVENT_TRANSMISSION === "process") {
process.send?.({ type: "event", event: payload, id } as ProcessEvent);
} else {
@@ -57,6 +70,14 @@ export async function initEvent() {
// use event emitter
// use process messages
}
await listenEvent("spacebar", async (event) => {
console.log("[Event] Received spacebar event:", event);
if ((event.event as string) === "SB_RELOAD_CONFIG") {
console.log("[Event] Reloading config due to RELOAD_CONFIG event");
await Config.init(true);
}
});
}
export interface EventOpts extends Event {
@@ -84,6 +105,12 @@ export async function listenEvent(event: string, callback: (event: EventOpts) =>
return await rabbitListen(channel, event, callback, {
acknowledge: opts?.acknowledge,
});
} else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) {
if (!unixSocketListener) {
unixSocketListener = new UnixSocketListener(path.join(process.env.EVENT_SOCKET_PATH, `${process.pid}.sock`));
await unixSocketListener.init();
}
return await unixSocketListener.listen(event, callback);
} else if (process.env.EVENT_TRANSMISSION === "process") {
const cancel = async () => {
process.removeListener("message", listener);
@@ -155,3 +182,126 @@ async function rabbitListen(channel: Channel, id: string, callback: (event: Even
return cancel;
}
class UnixSocketListener {
eventEmitter: EventEmitter;
socketPath: string;
constructor(socketPath: string) {
this.eventEmitter = new EventEmitter();
this.socketPath = socketPath;
}
async init() {
const net = await import("net");
const server = net.createServer((socket) => {
socket.on("connect", () => {
console.log("[Events] Unix socket client connected");
});
socket.on("data", (data) => {
try {
const payload = JSON.parse(data.toString());
this.eventEmitter.emit(payload.id, payload.event);
} catch (e) {
console.error("[Events] Failed to parse unix socket data:", e);
}
});
});
server.listen(this.socketPath, () => {
console.log(`Unix socket server listening on ${this.socketPath}`);
});
const shutdown = () => {
console.log("[Events] Closing unix socket server");
server.close();
process.exit(0);
};
for (const sig of ["SIGINT", "SIGTERM", "SIGQUIT"] as const) {
process.on(sig, shutdown);
}
}
async listen(event: string, callback: (event: EventOpts) => unknown): Promise<() => Promise<void>> {
const listener = (data: any) => {
callback({
...data,
cancel,
});
};
this.eventEmitter.addListener(event, listener);
const cancel = async () => {
this.eventEmitter.removeListener(event, listener);
this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() - 1);
};
this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() + 1);
return cancel;
}
}
class UnixSocketWriter {
socketPath: string;
clients: { [key: string]: Socket } = {};
watcher: FSWatcher;
constructor(socketPath: string) {
this.socketPath = socketPath;
}
async init() {
const net = await import("net");
const fs = await import("fs");
if (!fs.opendirSync(this.socketPath)) throw new Error("Unix socket path does not exist or is not a directory: " + this.socketPath);
console.log("[Events] Unix socket writer initializing for", this.socketPath);
const connect = (file: string) => {
const fullPath = path.join(this.socketPath, file);
this.clients[fullPath] = net.createConnection(fullPath, () => {
console.log("[Events] Unix socket client connected to", fullPath);
});
};
// connect to all sockets, now and in the future
this.watcher = fs.watch(this.socketPath, {}, (eventType, filename) => {
console.log("[Events] Unix socket writer received watch sig", eventType, filename);
connect(filename!);
});
// connect to existing sockets if any
fs.readdir(this.socketPath, (err, files) => {
if (err) return console.error("[Events] Unix socket writer failed to read directory:", err);
console.log("[Events] Unix socket writer found existing sockets:", files);
files.forEach((file) => {
connect(file);
});
});
}
async emit(event: Event) {
if (!this.clients) throw new Error("UnixSocketWriter not initialized");
const tsw = Stopwatch.startNew();
const payload = JSON.stringify({ id: (event.guild_id || event.channel_id || event.user_id) as string, event });
for (const socket of Object.entries(this.clients)) {
if(socket[1].destroyed) {
console.log("[Events] Unix socket writer found destroyed socket, removing:", socket[0]);
delete this.clients[socket[0]];
continue;
}
try {
socket[1].write(payload);
} catch (e) {
console.error("[Events] Unix socket writer failed to write to socket", socket[0], ":", e);
}
}
console.log(`[Events] Unix socket writer emitted to ${Object.entries(this.clients).length} sockets in ${tsw.elapsed().totalMilliseconds}ms`);
}
}