Path: blob/master/src/packages/project/sage_socket.ts
1447 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { getLogger } from "@cocalc/backend/logger";6import { secretToken } from "@cocalc/project/data";7import { enable_mesg } from "@cocalc/backend/misc_node";8import { CoCalcSocket } from "@cocalc/backend/tcp/enable-messaging-protocol";9import { connectToLockedSocket } from "@cocalc/backend/tcp/locked-socket";10import * as message from "@cocalc/util/message";11import * as common from "./common";12import { forget_port, get_port } from "./port_manager";13import {14SAGE_SERVER_MAX_STARTUP_TIME_S,15restartSageServer,16} from "./sage_restart";17import { until, once } from "@cocalc/util/async-utils";1819const logger = getLogger("get-sage-socket");2021// Get a new connection to the Sage server. If the server22// isn't running, e.g., it was killed due to running out of memory,23// attempt to restart it and try to connect.24export async function getSageSocket(): Promise<CoCalcSocket> {25let socket: CoCalcSocket | undefined = undefined;26await until(27async () => {28try {29socket = await _getSageSocket();30return true;31} catch (err) {32logger.debug(33`unable to get sage socket, so restarting sage server - ${err}`,34);35// Failed for some reason: try to restart one time, then try again.36// We do this because the Sage server can easily get killed due to out of memory conditions.37// But we don't constantly try to restart the server, since it can easily fail to start if38// there is something wrong with a local Sage install.39// Note that restarting the sage server doesn't impact currently running worksheets (they40// have their own process that isn't killed).41try {42// starting the sage server can also easily fail, so must be in the try43await restartSageServer();44// get socket immediately -- don't want to wait up to ~5s!45socket = await _getSageSocket();46return true;47} catch (err) {48logger.debug(49`error restarting sage server or getting socket -- ${err}`,50);51}52return false;53}54},55{56start: 1000,57max: 5000,58decay: 1.5,59timeout: SAGE_SERVER_MAX_STARTUP_TIME_S * 1000,60},61);62if (socket === undefined) {63throw Error("bug");64}65return socket;66}6768async function _getSageSocket(): Promise<CoCalcSocket> {69logger.debug("get sage server port");70const port = await get_port("sage");71logger.debug(`get and unlock socket on port ${port}`);72if (!port) {73throw new Error("port is not set");74}75try {76const sage_socket: CoCalcSocket = await connectToLockedSocket({77port,78token: secretToken,79});80logger.debug("Successfully unlocked a sage session connection.");8182logger.debug("request sage session from server.");83enable_mesg(sage_socket);84sage_socket.write_mesg("json", message.start_session({ type: "sage" }));85logger.debug(86"Waiting to read one JSON message back, which will describe the session....",87);88const [_type, desc] = await once(sage_socket, "mesg", 30000);89logger.debug(`Got message back from Sage server: ${common.json(desc)}`);90sage_socket.pid = desc.pid;91return sage_socket;92} catch (err) {93forget_port("sage");94const msg = `_new_session: sage session denied connection: ${err}`;95logger.debug(`Failed to connect -- ${msg}`);96throw Error(msg);97}98}99100101