Add shared process active message (#16)
* Add shared process active message * Add client function for calling bootstrap fork
This commit is contained in:
parent
72bf4547d4
commit
d827015b40
@ -155,6 +155,15 @@ export class Client {
|
||||
return this.doSpawn(modulePath, args, options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* VS Code specific.
|
||||
* Forks a module from bootstrap-fork
|
||||
* @param modulePath Path of the module
|
||||
*/
|
||||
public bootstrapFork(modulePath: string): ChildProcess {
|
||||
return this.doSpawn(modulePath, [], undefined, true, true);
|
||||
}
|
||||
|
||||
public createConnection(path: string, callback?: () => void): Socket;
|
||||
public createConnection(port: number, callback?: () => void): Socket;
|
||||
public createConnection(target: string | number, callback?: () => void): Socket {
|
||||
@ -176,13 +185,14 @@ export class Client {
|
||||
return socket;
|
||||
}
|
||||
|
||||
private doSpawn(command: string, args: string[] = [], options?: SpawnOptions, isFork: boolean = false): ChildProcess {
|
||||
private doSpawn(command: string, args: string[] = [], options?: SpawnOptions, isFork: boolean = false, isBootstrapFork: boolean = true): ChildProcess {
|
||||
const id = this.sessionId++;
|
||||
const newSess = new NewSessionMessage();
|
||||
newSess.setId(id);
|
||||
newSess.setCommand(command);
|
||||
newSess.setArgsList(args);
|
||||
newSess.setIsFork(isFork);
|
||||
newSess.setIsBootstrapFork(isBootstrapFork);
|
||||
if (options) {
|
||||
if (options.cwd) {
|
||||
newSess.setCwd(options.cwd);
|
||||
|
@ -5,6 +5,7 @@ import * as stream from "stream";
|
||||
import { TextEncoder } from "text-encoding";
|
||||
import { NewSessionMessage, ServerMessage, SessionDoneMessage, SessionOutputMessage, ShutdownSessionMessage, IdentifySessionMessage, ClientMessage, NewConnectionMessage, ConnectionEstablishedMessage, NewConnectionFailureMessage, ConnectionCloseMessage, ConnectionOutputMessage } from "../proto";
|
||||
import { SendableConnection } from "../common/connection";
|
||||
import { ServerOptions } from "./server";
|
||||
|
||||
export interface Process {
|
||||
stdin?: stream.Writable;
|
||||
@ -22,7 +23,7 @@ export interface Process {
|
||||
title?: number;
|
||||
}
|
||||
|
||||
export const handleNewSession = (connection: SendableConnection, newSession: NewSessionMessage, onExit: () => void): Process => {
|
||||
export const handleNewSession = (connection: SendableConnection, newSession: NewSessionMessage, serverOptions: ServerOptions | undefined, onExit: () => void): Process => {
|
||||
let process: Process;
|
||||
|
||||
const env = {} as any;
|
||||
@ -44,7 +45,15 @@ export const handleNewSession = (connection: SendableConnection, newSession: New
|
||||
};
|
||||
let proc: cp.ChildProcess;
|
||||
if (newSession.getIsFork()) {
|
||||
proc = cp.fork(newSession.getCommand(), newSession.getArgsList());
|
||||
if (!serverOptions) {
|
||||
throw new Error("No forkProvider set for bootstrap-fork request");
|
||||
}
|
||||
|
||||
if (!serverOptions.forkProvider) {
|
||||
throw new Error("No forkProvider set for server options");
|
||||
}
|
||||
|
||||
proc = serverOptions.forkProvider(newSession);
|
||||
} else {
|
||||
proc = cp.spawn(newSession.getCommand(), newSession.getArgsList(), options);
|
||||
}
|
||||
@ -107,7 +116,7 @@ export const handleNewSession = (connection: SendableConnection, newSession: New
|
||||
|
||||
export const handleNewConnection = (connection: SendableConnection, newConnection: NewConnectionMessage, onExit: () => void): net.Socket => {
|
||||
const id = newConnection.getId();
|
||||
let socket: net.Socket;
|
||||
let socket: net.Socket;
|
||||
let didConnect = false;
|
||||
const connectCallback = () => {
|
||||
didConnect = true;
|
||||
@ -134,7 +143,7 @@ export const handleNewConnection = (connection: SendableConnection, newConnectio
|
||||
const servMsg = new ServerMessage();
|
||||
servMsg.setConnectionFailure(errMsg);
|
||||
connection.send(servMsg.serializeBinary());
|
||||
|
||||
|
||||
onExit();
|
||||
}
|
||||
});
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as os from "os";
|
||||
import * as cp from "child_process";
|
||||
import * as path from "path";
|
||||
import { mkdir } from "fs";
|
||||
import { promisify } from "util";
|
||||
import { TextDecoder } from "text-encoding";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import { ClientMessage, WorkingInitMessage, ServerMessage } from "../proto";
|
||||
import { ClientMessage, WorkingInitMessage, ServerMessage, NewSessionMessage } from "../proto";
|
||||
import { evaluate } from "./evaluate";
|
||||
import { ReadWriteConnection } from "../common/connection";
|
||||
import { Process, handleNewSession, handleNewConnection } from "./command";
|
||||
@ -13,6 +14,8 @@ import * as net from "net";
|
||||
export interface ServerOptions {
|
||||
readonly workingDirectory: string;
|
||||
readonly dataDirectory: string;
|
||||
|
||||
forkProvider?(message: NewSessionMessage): cp.ChildProcess;
|
||||
}
|
||||
|
||||
export class Server {
|
||||
@ -22,7 +25,7 @@ export class Server {
|
||||
|
||||
public constructor(
|
||||
private readonly connection: ReadWriteConnection,
|
||||
options?: ServerOptions,
|
||||
private readonly options?: ServerOptions,
|
||||
) {
|
||||
connection.onMessage((data) => {
|
||||
try {
|
||||
@ -89,7 +92,7 @@ export class Server {
|
||||
if (message.hasNewEval()) {
|
||||
evaluate(this.connection, message.getNewEval()!);
|
||||
} else if (message.hasNewSession()) {
|
||||
const session = handleNewSession(this.connection, message.getNewSession()!, () => {
|
||||
const session = handleNewSession(this.connection, message.getNewSession()!, this.options, () => {
|
||||
this.sessions.delete(message.getNewSession()!.getId());
|
||||
});
|
||||
this.sessions.set(message.getNewSession()!.getId(), session);
|
||||
|
@ -17,8 +17,6 @@ message ClientMessage {
|
||||
|
||||
// node.proto
|
||||
NewEvalMessage new_eval = 9;
|
||||
|
||||
SharedProcessInitMessage shared_process_init = 10;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +37,9 @@ message ServerMessage {
|
||||
EvalDoneMessage eval_done = 10;
|
||||
|
||||
WorkingInitMessage init = 11;
|
||||
|
||||
// vscode.proto
|
||||
SharedProcessActiveMessage shared_process_active = 12;
|
||||
}
|
||||
}
|
||||
|
||||
|
14
packages/protocol/src/proto/client_pb.d.ts
vendored
14
packages/protocol/src/proto/client_pb.d.ts
vendored
@ -52,11 +52,6 @@ export class ClientMessage extends jspb.Message {
|
||||
getNewEval(): node_pb.NewEvalMessage | undefined;
|
||||
setNewEval(value?: node_pb.NewEvalMessage): void;
|
||||
|
||||
hasSharedProcessInit(): boolean;
|
||||
clearSharedProcessInit(): void;
|
||||
getSharedProcessInit(): vscode_pb.SharedProcessInitMessage | undefined;
|
||||
setSharedProcessInit(value?: vscode_pb.SharedProcessInitMessage): void;
|
||||
|
||||
getMsgCase(): ClientMessage.MsgCase;
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ClientMessage.AsObject;
|
||||
@ -79,7 +74,6 @@ export namespace ClientMessage {
|
||||
connectionOutput?: command_pb.ConnectionOutputMessage.AsObject,
|
||||
connectionClose?: command_pb.ConnectionCloseMessage.AsObject,
|
||||
newEval?: node_pb.NewEvalMessage.AsObject,
|
||||
sharedProcessInit?: vscode_pb.SharedProcessInitMessage.AsObject,
|
||||
}
|
||||
|
||||
export enum MsgCase {
|
||||
@ -93,7 +87,6 @@ export namespace ClientMessage {
|
||||
CONNECTION_OUTPUT = 7,
|
||||
CONNECTION_CLOSE = 8,
|
||||
NEW_EVAL = 9,
|
||||
SHARED_PROCESS_INIT = 10,
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,6 +146,11 @@ export class ServerMessage extends jspb.Message {
|
||||
getInit(): WorkingInitMessage | undefined;
|
||||
setInit(value?: WorkingInitMessage): void;
|
||||
|
||||
hasSharedProcessActive(): boolean;
|
||||
clearSharedProcessActive(): void;
|
||||
getSharedProcessActive(): vscode_pb.SharedProcessActiveMessage | undefined;
|
||||
setSharedProcessActive(value?: vscode_pb.SharedProcessActiveMessage): void;
|
||||
|
||||
getMsgCase(): ServerMessage.MsgCase;
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): ServerMessage.AsObject;
|
||||
@ -177,6 +175,7 @@ export namespace ServerMessage {
|
||||
evalFailed?: node_pb.EvalFailedMessage.AsObject,
|
||||
evalDone?: node_pb.EvalDoneMessage.AsObject,
|
||||
init?: WorkingInitMessage.AsObject,
|
||||
sharedProcessActive?: vscode_pb.SharedProcessActiveMessage.AsObject,
|
||||
}
|
||||
|
||||
export enum MsgCase {
|
||||
@ -192,6 +191,7 @@ export namespace ServerMessage {
|
||||
EVAL_FAILED = 9,
|
||||
EVAL_DONE = 10,
|
||||
INIT = 11,
|
||||
SHARED_PROCESS_ACTIVE = 12,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ if (goog.DEBUG && !COMPILED) {
|
||||
* @private {!Array<!Array<number>>}
|
||||
* @const
|
||||
*/
|
||||
proto.ClientMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10]];
|
||||
proto.ClientMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9]];
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
@ -57,8 +57,7 @@ proto.ClientMessage.MsgCase = {
|
||||
NEW_CONNECTION: 6,
|
||||
CONNECTION_OUTPUT: 7,
|
||||
CONNECTION_CLOSE: 8,
|
||||
NEW_EVAL: 9,
|
||||
SHARED_PROCESS_INIT: 10
|
||||
NEW_EVAL: 9
|
||||
};
|
||||
|
||||
/**
|
||||
@ -104,8 +103,7 @@ proto.ClientMessage.toObject = function(includeInstance, msg) {
|
||||
newConnection: (f = msg.getNewConnection()) && command_pb.NewConnectionMessage.toObject(includeInstance, f),
|
||||
connectionOutput: (f = msg.getConnectionOutput()) && command_pb.ConnectionOutputMessage.toObject(includeInstance, f),
|
||||
connectionClose: (f = msg.getConnectionClose()) && command_pb.ConnectionCloseMessage.toObject(includeInstance, f),
|
||||
newEval: (f = msg.getNewEval()) && node_pb.NewEvalMessage.toObject(includeInstance, f),
|
||||
sharedProcessInit: (f = msg.getSharedProcessInit()) && vscode_pb.SharedProcessInitMessage.toObject(includeInstance, f)
|
||||
newEval: (f = msg.getNewEval()) && node_pb.NewEvalMessage.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -187,11 +185,6 @@ proto.ClientMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
reader.readMessage(value,node_pb.NewEvalMessage.deserializeBinaryFromReader);
|
||||
msg.setNewEval(value);
|
||||
break;
|
||||
case 10:
|
||||
var value = new vscode_pb.SharedProcessInitMessage;
|
||||
reader.readMessage(value,vscode_pb.SharedProcessInitMessage.deserializeBinaryFromReader);
|
||||
msg.setSharedProcessInit(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@ -302,14 +295,6 @@ proto.ClientMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
node_pb.NewEvalMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = this.getSharedProcessInit();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
10,
|
||||
f,
|
||||
vscode_pb.SharedProcessInitMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -592,36 +577,6 @@ proto.ClientMessage.prototype.hasNewEval = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional SharedProcessInitMessage shared_process_init = 10;
|
||||
* @return {proto.SharedProcessInitMessage}
|
||||
*/
|
||||
proto.ClientMessage.prototype.getSharedProcessInit = function() {
|
||||
return /** @type{proto.SharedProcessInitMessage} */ (
|
||||
jspb.Message.getWrapperField(this, vscode_pb.SharedProcessInitMessage, 10));
|
||||
};
|
||||
|
||||
|
||||
/** @param {proto.SharedProcessInitMessage|undefined} value */
|
||||
proto.ClientMessage.prototype.setSharedProcessInit = function(value) {
|
||||
jspb.Message.setOneofWrapperField(this, 10, proto.ClientMessage.oneofGroups_[0], value);
|
||||
};
|
||||
|
||||
|
||||
proto.ClientMessage.prototype.clearSharedProcessInit = function() {
|
||||
this.setSharedProcessInit(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return{!boolean}
|
||||
*/
|
||||
proto.ClientMessage.prototype.hasSharedProcessInit = function() {
|
||||
return jspb.Message.getField(this, 10) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
@ -648,7 +603,7 @@ if (goog.DEBUG && !COMPILED) {
|
||||
* @private {!Array<!Array<number>>}
|
||||
* @const
|
||||
*/
|
||||
proto.ServerMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10,11]];
|
||||
proto.ServerMessage.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10,11,12]];
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
@ -665,7 +620,8 @@ proto.ServerMessage.MsgCase = {
|
||||
CONNECTION_ESTABLISHED: 8,
|
||||
EVAL_FAILED: 9,
|
||||
EVAL_DONE: 10,
|
||||
INIT: 11
|
||||
INIT: 11,
|
||||
SHARED_PROCESS_ACTIVE: 12
|
||||
};
|
||||
|
||||
/**
|
||||
@ -713,7 +669,8 @@ proto.ServerMessage.toObject = function(includeInstance, msg) {
|
||||
connectionEstablished: (f = msg.getConnectionEstablished()) && command_pb.ConnectionEstablishedMessage.toObject(includeInstance, f),
|
||||
evalFailed: (f = msg.getEvalFailed()) && node_pb.EvalFailedMessage.toObject(includeInstance, f),
|
||||
evalDone: (f = msg.getEvalDone()) && node_pb.EvalDoneMessage.toObject(includeInstance, f),
|
||||
init: (f = msg.getInit()) && proto.WorkingInitMessage.toObject(includeInstance, f)
|
||||
init: (f = msg.getInit()) && proto.WorkingInitMessage.toObject(includeInstance, f),
|
||||
sharedProcessActive: (f = msg.getSharedProcessActive()) && vscode_pb.SharedProcessActiveMessage.toObject(includeInstance, f)
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -805,6 +762,11 @@ proto.ServerMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
reader.readMessage(value,proto.WorkingInitMessage.deserializeBinaryFromReader);
|
||||
msg.setInit(value);
|
||||
break;
|
||||
case 12:
|
||||
var value = new vscode_pb.SharedProcessActiveMessage;
|
||||
reader.readMessage(value,vscode_pb.SharedProcessActiveMessage.deserializeBinaryFromReader);
|
||||
msg.setSharedProcessActive(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@ -931,6 +893,14 @@ proto.ServerMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.WorkingInitMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
f = this.getSharedProcessActive();
|
||||
if (f != null) {
|
||||
writer.writeMessage(
|
||||
12,
|
||||
f,
|
||||
vscode_pb.SharedProcessActiveMessage.serializeBinaryToWriter
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1273,6 +1243,36 @@ proto.ServerMessage.prototype.hasInit = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional SharedProcessActiveMessage shared_process_active = 12;
|
||||
* @return {proto.SharedProcessActiveMessage}
|
||||
*/
|
||||
proto.ServerMessage.prototype.getSharedProcessActive = function() {
|
||||
return /** @type{proto.SharedProcessActiveMessage} */ (
|
||||
jspb.Message.getWrapperField(this, vscode_pb.SharedProcessActiveMessage, 12));
|
||||
};
|
||||
|
||||
|
||||
/** @param {proto.SharedProcessActiveMessage|undefined} value */
|
||||
proto.ServerMessage.prototype.setSharedProcessActive = function(value) {
|
||||
jspb.Message.setOneofWrapperField(this, 12, proto.ServerMessage.oneofGroups_[0], value);
|
||||
};
|
||||
|
||||
|
||||
proto.ServerMessage.prototype.clearSharedProcessActive = function() {
|
||||
this.setSharedProcessActive(undefined);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether this field is set.
|
||||
* @return{!boolean}
|
||||
*/
|
||||
proto.ServerMessage.prototype.hasSharedProcessActive = function() {
|
||||
return jspb.Message.getField(this, 12) != null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
|
@ -13,6 +13,9 @@ message NewSessionMessage {
|
||||
string cwd = 5;
|
||||
TTYDimensions tty_dimensions = 6;
|
||||
bool is_fork = 7;
|
||||
|
||||
// Janky, but required for having custom handling of the bootstrap fork
|
||||
bool is_bootstrap_fork = 8;
|
||||
}
|
||||
|
||||
// Sent when starting a session failed.
|
||||
|
4
packages/protocol/src/proto/command_pb.d.ts
vendored
4
packages/protocol/src/proto/command_pb.d.ts
vendored
@ -28,6 +28,9 @@ export class NewSessionMessage extends jspb.Message {
|
||||
getIsFork(): boolean;
|
||||
setIsFork(value: boolean): void;
|
||||
|
||||
getIsBootstrapFork(): boolean;
|
||||
setIsBootstrapFork(value: boolean): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): NewSessionMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: NewSessionMessage): NewSessionMessage.AsObject;
|
||||
@ -47,6 +50,7 @@ export namespace NewSessionMessage {
|
||||
cwd: string,
|
||||
ttyDimensions?: TTYDimensions.AsObject,
|
||||
isFork: boolean,
|
||||
isBootstrapFork: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,8 @@ proto.NewSessionMessage.toObject = function(includeInstance, msg) {
|
||||
envMap: (f = msg.getEnvMap(true)) ? f.toArray() : [],
|
||||
cwd: msg.getCwd(),
|
||||
ttyDimensions: (f = msg.getTtyDimensions()) && proto.TTYDimensions.toObject(includeInstance, f),
|
||||
isFork: msg.getIsFork()
|
||||
isFork: msg.getIsFork(),
|
||||
isBootstrapFork: msg.getIsBootstrapFork()
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -154,6 +155,10 @@ proto.NewSessionMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setIsFork(value);
|
||||
break;
|
||||
case 8:
|
||||
var value = /** @type {boolean} */ (reader.readBool());
|
||||
msg.setIsBootstrapFork(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
break;
|
||||
@ -239,6 +244,13 @@ proto.NewSessionMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getIsBootstrapFork();
|
||||
if (f) {
|
||||
writer.writeBool(
|
||||
8,
|
||||
f
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -378,6 +390,23 @@ proto.NewSessionMessage.prototype.setIsFork = function(value) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional bool is_bootstrap_fork = 8;
|
||||
* Note that Boolean fields may be set to 0/1 when serialized from a Java server.
|
||||
* You should avoid comparisons like {@code val === true/false} in those cases.
|
||||
* @return {boolean}
|
||||
*/
|
||||
proto.NewSessionMessage.prototype.getIsBootstrapFork = function() {
|
||||
return /** @type {boolean} */ (jspb.Message.getFieldProto3(this, 8, false));
|
||||
};
|
||||
|
||||
|
||||
/** @param {boolean} value */
|
||||
proto.NewSessionMessage.prototype.setIsBootstrapFork = function(value) {
|
||||
jspb.Message.setField(this, 8, value);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
|
@ -1,9 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message SharedProcessInitMessage {
|
||||
uint64 window_id = 1;
|
||||
string log_directory = 2;
|
||||
|
||||
// Maps to `"vs/platform/log/common/log".LogLevel`
|
||||
uint32 log_level = 3;
|
||||
// Sent when a shared process becomes active
|
||||
message SharedProcessActiveMessage {
|
||||
string socket_path = 1;
|
||||
}
|
28
packages/protocol/src/proto/vscode_pb.d.ts
vendored
28
packages/protocol/src/proto/vscode_pb.d.ts
vendored
@ -3,31 +3,23 @@
|
||||
|
||||
import * as jspb from "google-protobuf";
|
||||
|
||||
export class SharedProcessInitMessage extends jspb.Message {
|
||||
getWindowId(): number;
|
||||
setWindowId(value: number): void;
|
||||
|
||||
getLogDirectory(): string;
|
||||
setLogDirectory(value: string): void;
|
||||
|
||||
getLogLevel(): number;
|
||||
setLogLevel(value: number): void;
|
||||
export class SharedProcessActiveMessage extends jspb.Message {
|
||||
getSocketPath(): string;
|
||||
setSocketPath(value: string): void;
|
||||
|
||||
serializeBinary(): Uint8Array;
|
||||
toObject(includeInstance?: boolean): SharedProcessInitMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: SharedProcessInitMessage): SharedProcessInitMessage.AsObject;
|
||||
toObject(includeInstance?: boolean): SharedProcessActiveMessage.AsObject;
|
||||
static toObject(includeInstance: boolean, msg: SharedProcessActiveMessage): SharedProcessActiveMessage.AsObject;
|
||||
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
||||
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
||||
static serializeBinaryToWriter(message: SharedProcessInitMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): SharedProcessInitMessage;
|
||||
static deserializeBinaryFromReader(message: SharedProcessInitMessage, reader: jspb.BinaryReader): SharedProcessInitMessage;
|
||||
static serializeBinaryToWriter(message: SharedProcessActiveMessage, writer: jspb.BinaryWriter): void;
|
||||
static deserializeBinary(bytes: Uint8Array): SharedProcessActiveMessage;
|
||||
static deserializeBinaryFromReader(message: SharedProcessActiveMessage, reader: jspb.BinaryReader): SharedProcessActiveMessage;
|
||||
}
|
||||
|
||||
export namespace SharedProcessInitMessage {
|
||||
export namespace SharedProcessActiveMessage {
|
||||
export type AsObject = {
|
||||
windowId: number,
|
||||
logDirectory: string,
|
||||
logLevel: number,
|
||||
socketPath: string,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ var jspb = require('google-protobuf');
|
||||
var goog = jspb;
|
||||
var global = Function('return this')();
|
||||
|
||||
goog.exportSymbol('proto.SharedProcessInitMessage', null, global);
|
||||
goog.exportSymbol('proto.SharedProcessActiveMessage', null, global);
|
||||
|
||||
/**
|
||||
* Generated by JsPbCodeGenerator.
|
||||
@ -21,12 +21,12 @@ goog.exportSymbol('proto.SharedProcessInitMessage', null, global);
|
||||
* @extends {jspb.Message}
|
||||
* @constructor
|
||||
*/
|
||||
proto.SharedProcessInitMessage = function(opt_data) {
|
||||
proto.SharedProcessActiveMessage = function(opt_data) {
|
||||
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
|
||||
};
|
||||
goog.inherits(proto.SharedProcessInitMessage, jspb.Message);
|
||||
goog.inherits(proto.SharedProcessActiveMessage, jspb.Message);
|
||||
if (goog.DEBUG && !COMPILED) {
|
||||
proto.SharedProcessInitMessage.displayName = 'proto.SharedProcessInitMessage';
|
||||
proto.SharedProcessActiveMessage.displayName = 'proto.SharedProcessActiveMessage';
|
||||
}
|
||||
|
||||
|
||||
@ -41,8 +41,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) {
|
||||
* for transitional soy proto support: http://goto/soy-param-migration
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.SharedProcessInitMessage.toObject(opt_includeInstance, this);
|
||||
proto.SharedProcessActiveMessage.prototype.toObject = function(opt_includeInstance) {
|
||||
return proto.SharedProcessActiveMessage.toObject(opt_includeInstance, this);
|
||||
};
|
||||
|
||||
|
||||
@ -51,14 +51,12 @@ proto.SharedProcessInitMessage.prototype.toObject = function(opt_includeInstance
|
||||
* @param {boolean|undefined} includeInstance Whether to include the JSPB
|
||||
* instance for transitional soy proto support:
|
||||
* http://goto/soy-param-migration
|
||||
* @param {!proto.SharedProcessInitMessage} msg The msg instance to transform.
|
||||
* @param {!proto.SharedProcessActiveMessage} msg The msg instance to transform.
|
||||
* @return {!Object}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.toObject = function(includeInstance, msg) {
|
||||
proto.SharedProcessActiveMessage.toObject = function(includeInstance, msg) {
|
||||
var f, obj = {
|
||||
windowId: msg.getWindowId(),
|
||||
logDirectory: msg.getLogDirectory(),
|
||||
logLevel: msg.getLogLevel()
|
||||
socketPath: msg.getSocketPath()
|
||||
};
|
||||
|
||||
if (includeInstance) {
|
||||
@ -72,23 +70,23 @@ proto.SharedProcessInitMessage.toObject = function(includeInstance, msg) {
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format).
|
||||
* @param {jspb.ByteSource} bytes The bytes to deserialize.
|
||||
* @return {!proto.SharedProcessInitMessage}
|
||||
* @return {!proto.SharedProcessActiveMessage}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.deserializeBinary = function(bytes) {
|
||||
proto.SharedProcessActiveMessage.deserializeBinary = function(bytes) {
|
||||
var reader = new jspb.BinaryReader(bytes);
|
||||
var msg = new proto.SharedProcessInitMessage;
|
||||
return proto.SharedProcessInitMessage.deserializeBinaryFromReader(msg, reader);
|
||||
var msg = new proto.SharedProcessActiveMessage;
|
||||
return proto.SharedProcessActiveMessage.deserializeBinaryFromReader(msg, reader);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Deserializes binary data (in protobuf wire format) from the
|
||||
* given reader into the given message object.
|
||||
* @param {!proto.SharedProcessInitMessage} msg The message object to deserialize into.
|
||||
* @param {!proto.SharedProcessActiveMessage} msg The message object to deserialize into.
|
||||
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
|
||||
* @return {!proto.SharedProcessInitMessage}
|
||||
* @return {!proto.SharedProcessActiveMessage}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
proto.SharedProcessActiveMessage.deserializeBinaryFromReader = function(msg, reader) {
|
||||
while (reader.nextField()) {
|
||||
if (reader.isEndGroup()) {
|
||||
break;
|
||||
@ -96,16 +94,8 @@ proto.SharedProcessInitMessage.deserializeBinaryFromReader = function(msg, reade
|
||||
var field = reader.getFieldNumber();
|
||||
switch (field) {
|
||||
case 1:
|
||||
var value = /** @type {number} */ (reader.readUint64());
|
||||
msg.setWindowId(value);
|
||||
break;
|
||||
case 2:
|
||||
var value = /** @type {string} */ (reader.readString());
|
||||
msg.setLogDirectory(value);
|
||||
break;
|
||||
case 3:
|
||||
var value = /** @type {number} */ (reader.readUint32());
|
||||
msg.setLogLevel(value);
|
||||
msg.setSocketPath(value);
|
||||
break;
|
||||
default:
|
||||
reader.skipField();
|
||||
@ -119,10 +109,10 @@ proto.SharedProcessInitMessage.deserializeBinaryFromReader = function(msg, reade
|
||||
/**
|
||||
* Class method variant: serializes the given message to binary data
|
||||
* (in protobuf wire format), writing to the given BinaryWriter.
|
||||
* @param {!proto.SharedProcessInitMessage} message
|
||||
* @param {!proto.SharedProcessActiveMessage} message
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.SharedProcessInitMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
proto.SharedProcessActiveMessage.serializeBinaryToWriter = function(message, writer) {
|
||||
message.serializeBinaryToWriter(writer);
|
||||
};
|
||||
|
||||
@ -131,7 +121,7 @@ proto.SharedProcessInitMessage.serializeBinaryToWriter = function(message, write
|
||||
* Serializes the message to binary data (in protobuf wire format).
|
||||
* @return {!Uint8Array}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.serializeBinary = function() {
|
||||
proto.SharedProcessActiveMessage.prototype.serializeBinary = function() {
|
||||
var writer = new jspb.BinaryWriter();
|
||||
this.serializeBinaryToWriter(writer);
|
||||
return writer.getResultBuffer();
|
||||
@ -143,26 +133,12 @@ proto.SharedProcessInitMessage.prototype.serializeBinary = function() {
|
||||
* writing to the given BinaryWriter.
|
||||
* @param {!jspb.BinaryWriter} writer
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
proto.SharedProcessActiveMessage.prototype.serializeBinaryToWriter = function (writer) {
|
||||
var f = undefined;
|
||||
f = this.getWindowId();
|
||||
if (f !== 0) {
|
||||
writer.writeUint64(
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getLogDirectory();
|
||||
f = this.getSocketPath();
|
||||
if (f.length > 0) {
|
||||
writer.writeString(
|
||||
2,
|
||||
f
|
||||
);
|
||||
}
|
||||
f = this.getLogLevel();
|
||||
if (f !== 0) {
|
||||
writer.writeUint32(
|
||||
3,
|
||||
1,
|
||||
f
|
||||
);
|
||||
}
|
||||
@ -171,55 +147,25 @@ proto.SharedProcessInitMessage.prototype.serializeBinaryToWriter = function (wri
|
||||
|
||||
/**
|
||||
* Creates a deep clone of this proto. No data is shared with the original.
|
||||
* @return {!proto.SharedProcessInitMessage} The clone.
|
||||
* @return {!proto.SharedProcessActiveMessage} The clone.
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.SharedProcessInitMessage} */ (jspb.Message.cloneMessage(this));
|
||||
proto.SharedProcessActiveMessage.prototype.cloneMessage = function() {
|
||||
return /** @type {!proto.SharedProcessActiveMessage} */ (jspb.Message.cloneMessage(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint64 window_id = 1;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.getWindowId = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 1, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.SharedProcessInitMessage.prototype.setWindowId = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional string log_directory = 2;
|
||||
* optional string socket_path = 1;
|
||||
* @return {string}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.getLogDirectory = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
|
||||
proto.SharedProcessActiveMessage.prototype.getSocketPath = function() {
|
||||
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
|
||||
};
|
||||
|
||||
|
||||
/** @param {string} value */
|
||||
proto.SharedProcessInitMessage.prototype.setLogDirectory = function(value) {
|
||||
jspb.Message.setField(this, 2, value);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* optional uint32 log_level = 3;
|
||||
* @return {number}
|
||||
*/
|
||||
proto.SharedProcessInitMessage.prototype.getLogLevel = function() {
|
||||
return /** @type {number} */ (jspb.Message.getFieldProto3(this, 3, 0));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} value */
|
||||
proto.SharedProcessInitMessage.prototype.setLogLevel = function(value) {
|
||||
jspb.Message.setField(this, 3, value);
|
||||
proto.SharedProcessActiveMessage.prototype.setSocketPath = function(value) {
|
||||
jspb.Message.setField(this, 1, value);
|
||||
};
|
||||
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { SharedProcessInitMessage } from "@coder/protocol/src/proto";
|
||||
import { field, logger } from "@coder/logger";
|
||||
import { ServerMessage, SharedProcessActiveMessage } from "@coder/protocol/src/proto";
|
||||
import { Command, flags } from "@oclif/command";
|
||||
import { logger, field } from "@coder/logger";
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import { requireModule } from "./vscode/bootstrapFork";
|
||||
import * as WebSocket from "ws";
|
||||
import { createApp } from "./server";
|
||||
import { SharedProcess } from './vscode/sharedProcess';
|
||||
import { requireModule } from "./vscode/bootstrapFork";
|
||||
import { SharedProcess, SharedProcessState } from './vscode/sharedProcess';
|
||||
|
||||
export class Entry extends Command {
|
||||
|
||||
@ -69,15 +70,22 @@ export class Entry extends Command {
|
||||
logger.info("Initializing", field("data-dir", dataDir), field("working-dir", workingDir));
|
||||
const sharedProcess = new SharedProcess(dataDir);
|
||||
logger.info("Starting shared process...", field("socket", sharedProcess.socketPath));
|
||||
sharedProcess.onWillRestart(() => {
|
||||
logger.info("Restarting shared process...");
|
||||
|
||||
sharedProcess.ready.then(() => {
|
||||
logger.info("Shared process has restarted!");
|
||||
});
|
||||
});
|
||||
sharedProcess.ready.then(() => {
|
||||
logger.info("Shared process has started up!");
|
||||
const sendSharedProcessReady = (socket: WebSocket) => {
|
||||
const active = new SharedProcessActiveMessage();
|
||||
active.setSocketPath(sharedProcess.socketPath);
|
||||
const serverMessage = new ServerMessage();
|
||||
serverMessage.setSharedProcessActive(active);
|
||||
socket.send(serverMessage.serializeBinary());
|
||||
};
|
||||
sharedProcess.onState((event) => {
|
||||
if (event.state === SharedProcessState.Stopped) {
|
||||
logger.error("Shared process stopped. Restarting...", field("error", event.error));
|
||||
} else if (event.state === SharedProcessState.Starting) {
|
||||
logger.info("Starting shared process...");
|
||||
} else if (event.state === SharedProcessState.Ready) {
|
||||
logger.info("Shared process is ready!");
|
||||
app.wss.clients.forEach((c) => sendSharedProcessReady(c));
|
||||
}
|
||||
});
|
||||
|
||||
const app = createApp((app) => {
|
||||
@ -108,13 +116,12 @@ export class Entry extends Command {
|
||||
let clientId = 1;
|
||||
app.wss.on("connection", (ws, req) => {
|
||||
const id = clientId++;
|
||||
const spm = (<any>req).sharedProcessInit as SharedProcessInitMessage;
|
||||
if (!spm) {
|
||||
logger.warn("Received a socket without init data. Not sure how this happened.");
|
||||
|
||||
return;
|
||||
if (sharedProcess.state === SharedProcessState.Ready) {
|
||||
sendSharedProcessReady(ws);
|
||||
}
|
||||
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress), field("window_id", spm.getWindowId()), field("log_directory", spm.getLogDirectory()));
|
||||
|
||||
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress));
|
||||
|
||||
ws.on("close", (code) => {
|
||||
logger.info(`WebSocket closed \u001B[0m${req.url}`, field("client", id), field("code", code));
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { ReadWriteConnection } from "@coder/protocol";
|
||||
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
|
||||
import { NewSessionMessage } from '@coder/protocol/src/proto';
|
||||
import { ChildProcess } from "child_process";
|
||||
import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as ws from "ws";
|
||||
import * as url from "url";
|
||||
import { ClientMessage, SharedProcessInitMessage } from '@coder/protocol/src/proto';
|
||||
import { forkModule } from "./vscode/bootstrapFork";
|
||||
|
||||
export const createApp = (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions): {
|
||||
readonly express: express.Application;
|
||||
@ -19,27 +20,7 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi
|
||||
const wss = new ws.Server({ server });
|
||||
|
||||
wss.shouldHandle = (req): boolean => {
|
||||
if (typeof req.url === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsedUrl = url.parse(req.url, true);
|
||||
const sharedProcessInit = parsedUrl.query["shared_process_init"];
|
||||
if (typeof sharedProcessInit === "undefined" || Array.isArray(sharedProcessInit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const msg = ClientMessage.deserializeBinary(Buffer.from(sharedProcessInit, "base64"));
|
||||
if (!msg.hasSharedProcessInit()) {
|
||||
return false;
|
||||
}
|
||||
const spm = msg.getSharedProcessInit()!;
|
||||
(<any>req).sharedProcessInit = spm;
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should handle auth here
|
||||
return true;
|
||||
};
|
||||
|
||||
@ -59,7 +40,20 @@ export const createApp = (registerMiddleware?: (app: express.Application) => voi
|
||||
onClose: (cb): void => ws.addEventListener("close", () => cb()),
|
||||
};
|
||||
|
||||
const server = new Server(connection, options);
|
||||
const server = new Server(connection, options ? {
|
||||
...options,
|
||||
forkProvider: (message: NewSessionMessage): ChildProcess => {
|
||||
let proc: ChildProcess;
|
||||
|
||||
if (message.getIsBootstrapFork()) {
|
||||
proc = forkModule(message.getCommand());
|
||||
} else {
|
||||
throw new Error("No support for non bootstrap-forking yet");
|
||||
}
|
||||
|
||||
return proc;
|
||||
},
|
||||
} : undefined);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -9,41 +9,47 @@ import { ParsedArgs } from "vs/platform/environment/common/environment";
|
||||
import { LogLevel } from "vs/platform/log/common/log";
|
||||
import { Emitter, Event } from '@coder/events/src';
|
||||
|
||||
export enum SharedProcessState {
|
||||
Stopped,
|
||||
Starting,
|
||||
Ready,
|
||||
}
|
||||
|
||||
export type SharedProcessEvent = {
|
||||
readonly state: SharedProcessState.Ready | SharedProcessState.Starting;
|
||||
} | {
|
||||
readonly state: SharedProcessState.Stopped;
|
||||
readonly error: string;
|
||||
}
|
||||
|
||||
export class SharedProcess {
|
||||
public readonly socketPath: string = path.join(os.tmpdir(), `.vscode-online${Math.random().toString()}`);
|
||||
private _ready: Promise<void> | undefined;
|
||||
private _state: SharedProcessState = SharedProcessState.Stopped;
|
||||
private activeProcess: ChildProcess | undefined;
|
||||
private ipcHandler: StdioIpcHandler | undefined;
|
||||
private readonly willRestartEmitter: Emitter<void>;
|
||||
private readonly onStateEmitter: Emitter<SharedProcessEvent>;
|
||||
|
||||
public constructor(
|
||||
private readonly userDataDir: string,
|
||||
) {
|
||||
this.willRestartEmitter = new Emitter();
|
||||
this.onStateEmitter = new Emitter();
|
||||
|
||||
this.restart();
|
||||
}
|
||||
|
||||
public get onWillRestart(): Event<void> {
|
||||
return this.willRestartEmitter.event;
|
||||
public get onState(): Event<SharedProcessEvent> {
|
||||
return this.onStateEmitter.event;
|
||||
}
|
||||
|
||||
public get ready(): Promise<void> {
|
||||
return this._ready!;
|
||||
public get state(): SharedProcessState {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public restart(): void {
|
||||
if (this.activeProcess) {
|
||||
this.willRestartEmitter.emit();
|
||||
}
|
||||
|
||||
if (this.activeProcess && !this.activeProcess.killed) {
|
||||
this.activeProcess.kill();
|
||||
}
|
||||
|
||||
let resolve: () => void;
|
||||
let reject: (err: Error) => void;
|
||||
|
||||
const extensionsDir = path.join(this.userDataDir, "extensions");
|
||||
const mkdir = (dir: string): void => {
|
||||
try {
|
||||
@ -57,14 +63,18 @@ export class SharedProcess {
|
||||
mkdir(this.userDataDir);
|
||||
mkdir(extensionsDir);
|
||||
|
||||
this._ready = new Promise<void>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
this.setState({
|
||||
state: SharedProcessState.Starting,
|
||||
});
|
||||
|
||||
let resolved: boolean = false;
|
||||
this.activeProcess = forkModule("vs/code/electron-browser/sharedProcess/sharedProcessMain");
|
||||
this.activeProcess.on("exit", () => {
|
||||
this.activeProcess.on("exit", (err) => {
|
||||
if (this._state !== SharedProcessState.Stopped) {
|
||||
this.setState({
|
||||
error: `Exited with ${err}`,
|
||||
state: SharedProcessState.Stopped,
|
||||
});
|
||||
}
|
||||
this.restart();
|
||||
});
|
||||
this.ipcHandler = new StdioIpcHandler(this.activeProcess);
|
||||
@ -86,11 +96,20 @@ export class SharedProcess {
|
||||
});
|
||||
this.ipcHandler.once("handshake:im ready", () => {
|
||||
resolved = true;
|
||||
resolve();
|
||||
this.setState({
|
||||
state: SharedProcessState.Ready,
|
||||
});
|
||||
});
|
||||
this.activeProcess.stderr.on("data", (data) => {
|
||||
if (!resolved) {
|
||||
reject(data.toString());
|
||||
this.setState({
|
||||
error: data.toString(),
|
||||
state: SharedProcessState.Stopped,
|
||||
});
|
||||
if (!this.activeProcess) {
|
||||
return;
|
||||
}
|
||||
this.activeProcess.kill();
|
||||
} else {
|
||||
logger.named("SHRD PROC").debug("stderr", field("message", data.toString()));
|
||||
}
|
||||
@ -103,4 +122,9 @@ export class SharedProcess {
|
||||
}
|
||||
this.ipcHandler = undefined;
|
||||
}
|
||||
|
||||
private setState(event: SharedProcessEvent): void {
|
||||
this._state = event.state;
|
||||
this.onStateEmitter.emit(event);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user