Я работаю над расширением, которое периодически отправляет запросы к HTTP API. Если открыто несколько окон VSCode, похоже, что все они отправляют такие запросы, что не оптимально.
Поэтому я хочу убедиться, что запросы выполняются только в одном месте, например. в одном окне VSCode или глобальном процессе, а затем пересылается в другие окна для обновления древовидного представления.
Как можно осуществить эту синхронизацию?
@LexLi. Это тоже был мой вывод. Но я не уверен, как реализовать эту синхронизацию. Я не нашел много механизмов для отправки сообщений между окнами VSCode. Есть SecretStorage, но он явно не предназначен для синхронизации.
@LexLi. Есть ли у вас какие-либо предложения, как это сделать? Какой механизм межпроцессного взаимодействия вы бы использовали?
@LexLi. Нет решения? Возможно, это было не так просто, как вы думали?
@LexLi. Проблема в том, что большинство коммуникаций, например. WebSockets, это сервер-клиент, но здесь все окна равны. Непонятно, где запускать сервер.
Я нашел способ, который может сработать: сохранить данные в файле (в каталоге, заданном context.globalStorageUri()
) и использовать fs.watch()
, чтобы увидеть, когда они были изменены.
@LexLi. Процесс вне VSCode, который все пользователи расширения должны запускать вручную? Или расширение должно запускать его из одного окна VSCode? Все окна затем попытаются запустить процесс.
@LexLi. Учитывая, насколько это «тривиально», вы, похоже, неспособны показать мне, как это сделать.
Внедрить межпроцессное взаимодействие на основе сети. Если вы запустите vscode/ваше расширение, проверьте, прослушивает ли что-нибудь порт/сокет xyz, если нет, запустите сервер, если порт уже используется, попробуйте подключиться к нему. Или реализовать «файл блокировки». Это не так сложно, как кажется.
Как сказано в моем комментарии, вам необходимо реализовать межпроцессное взаимодействие (IPC).
Самый «простой» способ добиться этого — сетевые сокеты.
const { createServer, Socket } = require("net");
const fs = require("fs");
const os = require("os");
const LOCKFILE_LOCATION = `${os.tmpdir()}/my-lockfile.pid`;
const HOST = "127.0.0.5";
const PORT = 9317;
console.info("Started...");
console.info("Check fo lockfile %s", LOCKFILE_LOCATION)
if (fs.existsSync(LOCKFILE_LOCATION)) {
console.info("Lockfile exists, start in client mode");
let client = new Socket();
client.once("close", () => {
console.info("Socket closed");
let pid = Number(fs.readFileSync(LOCKFILE_LOCATION, "utf-8"));
setTimeout(() => {
try {
// 0 is a special signal that doesnt actually kill the process
// process.kill will throw a error
process.kill(pid, 0);
} catch (error) {
if (error.code === "ESRCH") {
// process is not running
// cleanup & exit or swpan new server here
fs.rmSync(LOCKFILE_LOCATION);
console.info("Lock/pid file removed");
// use a random setTimeout/sleep before creating a new server
// the main process part below could be outsourced in a function, which you can call here again
} else if (error.code === "EPERM") {
console.info("process is maybe running");
} else {
console.error("Could not check if main process is running", error);
}
}
}, 100);
});
client.on("data", (data) => {
console.info("data from main/server process", data.toString());
});
client.connect(PORT, HOST);
} else {
console.info("Lockfile does not exists, start in server mode");
// store the PID in the lockfile to check if the process is running on exit
// if not, delete the lockfile
fs.writeFileSync(LOCKFILE_LOCATION, `${process.pid}`);
let server = createServer();
server.on("connection", (socket) => {
socket.write(`Hello client, server here ${process.pid}. You want some http requests data?`);
});
// 127.0.0.5 used on purpose
server.listen(PORT, HOST, () => {
console.info(`Server listening on tcp://${HOST}${PORT}`);
});
}
Приведенный выше пример не идеален, но является хорошей отправной точкой для понимания концепции.
Например. что происходит, когда основное окно процесса/кода закрыто, но остальные все еще открыты? Затем вы можете создать новый сетевой сокет. Будьте осторожны и не пытайтесь создать несколько сокетов. Чтобы избежать этого, можно использовать случайный сон/setTimeout
.
Видите ли, при работе с IPC следует учитывать и другие вещи.
Обновлено: Чтобы лучше показать, как осуществляется синхронизация, я немного изменил приведенный выше код.
let server = createServer();
let clients = new Set();
// this simulates your http/api requests
// unref is used that the process exits
setInterval(() => {
console.info("Broadcast data to %d clients", clients.size);
let data = JSON.stringify({
boolean: true,
timestamp: Date.now(),
string: "Hello Client"
});
Array.from(clients).forEach((socket, i) =>{
socket.write(data, () => {
console.info("Data writen to client #%d", i);
});
});
}, 5000).unref();
server.on("connection", (socket) => {
console.info("Client connected");
socket.once("close", () => {
console.info("Client disconnected");
clients.delete(socket);
});
socket.write(`Hello client, server here ${process.pid}. You want some http requests data?`);
clients.add(socket);
});
Вместо setInterval
отправляйте туда HTTP-запросы, а когда вы получите необходимые данные, отправьте их через сетевой сокет всем подключенным клиентам.
То есть синхронизация здесь в основном осуществляется с использованием файла (файла блокировки)?
Синхронизация сама по себе осуществляется через сетевой сокет. В основном процессе (где создается tcp-сервер) выполняйте запросы HTTP API и отправляйте их всем подключенным клиентам. Это ваша синхронизация. Файл блокировки предназначен только для того, чтобы сообщить другим VS Code Windows, следует ли им запускаться как клиенты и подключаться к серверу или запускать запуск.
@md2perpe Я обновил код, чтобы лучше показать, как можно выполнить синхронизацию.
@md2perpe Есть ли у вас вопросы по коду или понятно, как отправлять данные с сервера (главное окно) клиентам?
Я понимаю, как работает код. Процесс, который первым на шаре, сообщает другим процессам «Я делаю это», создавая файл блокировки, а затем запускает сервер. Затем другие процессы подключаются к серверу, чтобы получить от него данные. Если сервер или процесс, запускающий его, завершает работу или умирает, первый процесс, который это заметит, должен запустить новый сервер. Есть некоторые пробелы, которые могут привести к гонке; если это проблема, следует использовать мьютекс.
@md2perpe Если это решение, с которым вы можете работать, отметьте его как таковое.
В VS Code API нет ничего подобного, поэтому, вероятно, вам придется разработать/реализовать его самостоятельно.