CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/sage_socket.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { getLogger } from "@cocalc/backend/logger";
7
import { enable_mesg } from "@cocalc/backend/misc_node";
8
import { CoCalcSocket } from "@cocalc/backend/tcp/enable-messaging-protocol";
9
import { connectToLockedSocket } from "@cocalc/backend/tcp/locked-socket";
10
import * as message from "@cocalc/util/message";
11
import { retry_until_success } from "@cocalc/util/misc";
12
import * as common from "./common";
13
import { forget_port, get_port } from "./port_manager";
14
import {
15
SAGE_SERVER_MAX_STARTUP_TIME_S,
16
restart_sage_server,
17
} from "./sage_restart";
18
import { getSecretToken } from "./servers/secret-token";
19
import { CB } from "@cocalc/util/types/callback";
20
21
const winston = getLogger("sage-socket");
22
23
// Get a new connection to the Sage server. If the server
24
// isn't running, e.g., it was killed due to running out of memory,
25
// attempt to restart it and try to connect.
26
export async function get_sage_socket(): Promise<CoCalcSocket> {
27
let socket: CoCalcSocket | undefined;
28
const try_to_connect = async (cb: CB) => {
29
try {
30
socket = await _get_sage_socket();
31
cb();
32
} catch (err) {
33
// Failed for some reason: try to restart one time, then try again.
34
// We do this because the Sage server can easily get killed due to out of memory conditions.
35
// But we don't constantly try to restart the server, since it can easily fail to start if
36
// there is something wrong with a local Sage install.
37
// Note that restarting the sage server doesn't impact currently running worksheets (they
38
// have their own process that isn't killed).
39
try {
40
await restart_sage_server();
41
// success at restarting sage server: *IMMEDIATELY* try to connect
42
socket = await _get_sage_socket();
43
cb();
44
} catch (err) {
45
// won't actually try to restart if called recently.
46
cb(err);
47
}
48
}
49
};
50
51
return new Promise((resolve, reject) => {
52
retry_until_success({
53
f: try_to_connect,
54
start_delay: 50,
55
max_delay: 5000,
56
factor: 1.5,
57
max_time: SAGE_SERVER_MAX_STARTUP_TIME_S * 1000,
58
log(m) {
59
return winston.debug(`get_sage_socket: ${m}`);
60
},
61
cb(err) {
62
if (socket == null) {
63
reject("failed to get sage socket");
64
} else if (err) {
65
reject(err);
66
} else {
67
resolve(socket);
68
}
69
},
70
});
71
});
72
}
73
74
async function _get_sage_socket(): Promise<CoCalcSocket> {
75
winston.debug("get sage server port");
76
const port = await get_port("sage");
77
winston.debug("get and unlock socket");
78
if (port == null) throw new Error("port is null");
79
try {
80
const sage_socket: CoCalcSocket | undefined = await connectToLockedSocket({
81
port,
82
token: getSecretToken(),
83
});
84
winston.debug("Successfully unlocked a sage session connection.");
85
86
winston.debug("request sage session from server.");
87
enable_mesg(sage_socket);
88
sage_socket.write_mesg("json", message.start_session({ type: "sage" }));
89
winston.debug(
90
"Waiting to read one JSON message back, which will describe the session...."
91
);
92
// TODO: couldn't this just hang forever :-(
93
return new Promise<CoCalcSocket>((resolve) => {
94
sage_socket.once("mesg", (_type, desc) => {
95
winston.debug(
96
`Got message back from Sage server: ${common.json(desc)}`
97
);
98
sage_socket.pid = desc.pid;
99
resolve(sage_socket);
100
});
101
});
102
} catch (err2) {
103
forget_port("sage");
104
const msg = `_new_session: sage session denied connection: ${err2}`;
105
throw new Error(msg);
106
}
107
}
108
109