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/frontend/compute/manager.ts
Views: 687
1
/*
2
Client side compute servers manager
3
4
Used from a browser client frontend to manage what compute servers
5
are available and how they are used for a given project.
6
7
When doing dev from the browser console, do:
8
9
cc.client.project_client.computeServers('...project_id...')
10
*/
11
12
import { SYNCDB_PARAMS, decodeUUIDtoNum } from "@cocalc/util/compute/manager";
13
import { webapp_client } from "@cocalc/frontend/webapp-client";
14
import debug from "debug";
15
import { once } from "@cocalc/util/async-utils";
16
import { EventEmitter } from "events";
17
import { excludeFromComputeServer } from "@cocalc/frontend/file-associations";
18
19
const log = debug("cocalc:frontend:compute:manager");
20
21
export class ComputeServersManager extends EventEmitter {
22
private sync_db;
23
private project_id;
24
25
constructor(project_id: string) {
26
super();
27
this.project_id = project_id;
28
this.sync_db = webapp_client.sync_db({
29
project_id,
30
...SYNCDB_PARAMS,
31
});
32
this.sync_db.on("change", () => {
33
this.emit("change");
34
});
35
// It's reasonable to have many clients, e.g., one for each open file
36
this.setMaxListeners(100);
37
log("created", this.project_id);
38
}
39
40
close = () => {
41
delete computeServerManagerCache[this.project_id];
42
this.sync_db.close();
43
};
44
45
// save the current state to the backend. This is critical to do, e.g., before
46
// opening a file and after calling connectComputeServerToPath, since otherwise
47
// the project doesn't even know that the file should open on the compute server
48
// until after it has opened it, which is disconcerting and not efficient (but
49
// does mostly work, though it is intentionally making things really hard on ourselves).
50
save = async () => {
51
await this.sync_db.save();
52
};
53
54
getComputeServers = () => {
55
const servers = {};
56
const cursors = this.sync_db.get_cursors({ excludeSelf: 'never' }).toJS();
57
for (const client_id in cursors) {
58
const server = cursors[client_id];
59
servers[decodeUUIDtoNum(client_id)] = {
60
time: server.time,
61
...server.locs[0],
62
};
63
}
64
return servers;
65
};
66
67
// Call this if you want the compute server with given id to
68
// connect and handle being the server for the given path.
69
connectComputeServerToPath = ({ id, path }: { id: number; path: string }) => {
70
if (id == 0) {
71
this.disconnectComputeServer({ path });
72
return;
73
}
74
assertSupportedPath(path);
75
this.sync_db.set({ id, path, open: true });
76
this.sync_db.commit();
77
};
78
79
// Call this if you want no compute servers to provide the backend server
80
// for given path.
81
disconnectComputeServer = ({ path }: { path: string }) => {
82
this.sync_db.delete({ path });
83
this.sync_db.commit();
84
};
85
86
// For interactive debugging -- display in the console how things are configured.
87
showStatus = () => {
88
console.log(JSON.stringify(this.sync_db.get().toJS(), undefined, 2));
89
};
90
91
// Returns the explicitly set server id for the given
92
// path, if one is set. Otherwise, return undefined
93
// if nothing is explicitly set for this path.
94
getServerIdForPath = async (path: string): Promise<number | undefined> => {
95
const { sync_db } = this;
96
if (sync_db.get_state() == "init") {
97
await once(sync_db, "ready");
98
}
99
if (sync_db.get_state() != "ready") {
100
throw Error("syncdb not ready");
101
}
102
return sync_db.get_one({ path })?.get("id");
103
};
104
}
105
106
function assertSupportedPath(path: string) {
107
if (excludeFromComputeServer(path)) {
108
throw Error(
109
`Opening '${path}' on a compute server is not yet supported -- copy it to the project and open it there instead`,
110
);
111
}
112
}
113
114
const computeServerManagerCache: {
115
[project_id: string]: ComputeServersManager;
116
} = {};
117
118
export const computeServers = (project_id: string) => {
119
if (computeServerManagerCache[project_id]) {
120
return computeServerManagerCache[project_id];
121
}
122
const m = new ComputeServersManager(project_id);
123
computeServerManagerCache[project_id] = m;
124
return m;
125
};
126
127
export default computeServers;
128
129