Skip to content

Commit 8617568

Browse files
committed
Merge remote-tracking branch 'origin/master' into delete-project-data
2 parents 2c27cb2 + d4e2291 commit 8617568

File tree

151 files changed

+4237
-2732
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

151 files changed

+4237
-2732
lines changed

src/compute/compute/lib/listings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ export async function initListings({
5252
project_id,
5353
compute_server_id,
5454
getListing,
55-
createWatcher: (path: string, debounceMs: number) =>
56-
new Watcher(path, debounceMs),
55+
createWatcher: (path: string, debounce: number) =>
56+
new Watcher(path, { debounce }),
5757
onDeletePath: (path) => {
5858
logger.debug("onDeletePath -- TODO:", { path });
5959
},

src/packages/api-client/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ the settings of your project.
1010
```sh
1111
export PROJECT_ID=6640ddad-4bdd-4745-8e63-8db74686a20e
1212
export API_KEY=sk-FcnRs3NxsTZROgbF000001
13-
export API_SERVER=http://localhost:9001
13+
export API_SERVER=http://127.0.0.1:9001
1414
```
1515

1616
The run code in node from the shell in the current directory:

src/packages/backend/path-watcher.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
*/
55

66
/*
7+
Watch A DIRECTORY for changes. Use ./watcher.ts for a single file.
8+
9+
710
Slightly generalized fs.watch that works even when the directory doesn't exist,
811
but also doesn't provide any information about what changed.
912
@@ -76,11 +79,18 @@ export class Watcher extends EventEmitter {
7679
private exists: boolean;
7780
private watchContents?: FSWatcher;
7881
private watchExistence?: FSWatcher;
82+
private interval_ms: number;
7983
private debounce_ms: number;
8084
private debouncedChange: any;
8185
private log: Function;
8286

83-
constructor(path: string, debounce_ms: number) {
87+
constructor(
88+
path: string,
89+
{
90+
debounce: debounce_ms = 0,
91+
interval: interval_ms,
92+
}: { debounce?: number; interval?: number } = {},
93+
) {
8494
super();
8595
this.log = logger.extend(path).debug;
8696
this.log(`initializing: poll=${POLLING}`);
@@ -89,10 +99,13 @@ export class Watcher extends EventEmitter {
8999
}
90100
this.path = path.startsWith("/") ? path : join(process.env.HOME, path);
91101
this.debounce_ms = debounce_ms;
92-
this.debouncedChange = debounce(this.change.bind(this), this.debounce_ms, {
93-
leading: true,
94-
trailing: true,
95-
}).bind(this);
102+
this.interval_ms = interval_ms ?? DEFAULT_POLL_MS;
103+
this.debouncedChange = this.debounce_ms
104+
? debounce(this.change, this.debounce_ms, {
105+
leading: true,
106+
trailing: true,
107+
}).bind(this)
108+
: this.change;
96109
this.init();
97110
}
98111

@@ -109,8 +122,16 @@ export class Watcher extends EventEmitter {
109122
}
110123
}
111124

125+
private chokidarOptions = () => {
126+
return {
127+
...ChokidarOpts,
128+
interval: this.interval_ms,
129+
binaryInterval: this.interval_ms,
130+
};
131+
};
132+
112133
private initWatchContents(): void {
113-
this.watchContents = watch(this.path, ChokidarOpts);
134+
this.watchContents = watch(this.path, this.chokidarOptions());
114135
this.watchContents.on("all", this.debouncedChange);
115136
this.watchContents.on("error", (err) => {
116137
this.log(`error watching listings -- ${err}`);
@@ -119,7 +140,7 @@ export class Watcher extends EventEmitter {
119140

120141
private async initWatchExistence(): Promise<void> {
121142
const containing_path = path_split(this.path).head;
122-
this.watchExistence = watch(containing_path, ChokidarOpts);
143+
this.watchExistence = watch(containing_path, this.chokidarOptions());
123144
this.watchExistence.on("all", this.watchExistenceChange(containing_path));
124145
this.watchExistence.on("error", (err) => {
125146
this.log(`error watching for existence of ${this.path} -- ${err}`);
@@ -147,9 +168,9 @@ export class Watcher extends EventEmitter {
147168
}
148169
};
149170

150-
private change(): void {
171+
private change = (): void => {
151172
this.emit("change");
152-
}
173+
};
153174

154175
public close(): void {
155176
this.watchExistence?.close();

src/packages/backend/tcp/locked-socket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ It is not used in any other way.
6767
*/
6868
export async function connectToLockedSocket({
6969
port,
70-
host = "localhost",
70+
host = "127.0.0.1",
7171
token,
7272
timeout = 5, // in seconds (not milliseconds)
7373
}: {

src/packages/backend/watcher.ts

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,78 @@
44
*/
55

66
/*
7-
Watch a file for changes
7+
Watch one SINGLE FILE for changes. Use ./path-watcher.ts for a directory.
88
9-
Watch for changes to the given file. Returns obj, which
9+
Watch for changes to the given file, which means the mtime changes or the
10+
mode changes (e.g., readonly versus readwrite). Returns obj, which
1011
is an event emitter with events:
1112
12-
- 'change', ctime - when file changes or is created
13+
- 'change', ctime, stats - when file changes or is created
1314
- 'delete' - when file is deleted
1415
1516
and a method .close().
1617
1718
Only fires after the file definitely has not had its
18-
ctime changed for at least debounce ms (this is the atomic
19-
option to chokidar). Does NOT fire when the file first
20-
has ctime changed.
19+
ctime changed for at least debounce ms. Does NOT
20+
fire when the file first has ctime changed.
21+
22+
NOTE: for directories we use chokidar in path-watcher. However,
23+
for a single file using polling, chokidar is horribly buggy and
24+
lacking in functionality (e.g., https://github.com/paulmillr/chokidar/issues/1132),
25+
and declared all bugs fixed, so we steer clear. It had a lot of issues
26+
with just noticing actual file changes.
27+
28+
We *always* use polling to fully support networked filesystems.
2129
*/
2230

2331
import { EventEmitter } from "node:events";
24-
import { watch, FSWatcher } from "chokidar";
32+
import { unwatchFile, watchFile } from "node:fs";
2533
import { getLogger } from "./logger";
2634
import { debounce as lodashDebounce } from "lodash";
2735

28-
const L = getLogger("watcher");
36+
const logger = getLogger("backend:watcher");
2937

3038
export class Watcher extends EventEmitter {
3139
private path: string;
32-
private interval: number;
33-
private watcher: FSWatcher;
3440

35-
constructor(path: string, interval: number = 300, debounce: number = 0) {
41+
constructor(
42+
path: string,
43+
{ debounce, interval = 300 }: { debounce?: number; interval?: number } = {},
44+
) {
3645
super();
3746
this.path = path;
38-
this.interval = interval;
39-
40-
L.debug(`${path}: interval=${interval}, debounce=${debounce}`);
41-
this.watcher = watch(this.path, {
42-
interval: this.interval,
43-
// polling is critical for network mounted file systems,
44-
// and given architecture of cocalc there is no easy way around this.
45-
// E.g., on compute servers, everything breaks involving sync or cloudfs,
46-
// and in shared project s3/gcsfuse/sshfs would all break. So we
47-
// use polling.
48-
usePolling: true,
49-
persistent: false,
50-
alwaysStat: true,
51-
atomic: true,
52-
});
53-
this.watcher.on("unlink", () => {
54-
this.emit("delete");
55-
});
56-
this.watcher.on("unlinkDir", () => {
57-
this.emit("delete");
58-
});
59-
60-
const emitChange = lodashDebounce(
61-
(ctime) => this.emit("change", ctime),
62-
debounce,
63-
);
64-
this.watcher.on("error", (err) => {
65-
L.debug("WATCHER error -- ", err);
66-
});
67-
68-
this.watcher.on("change", (_, stats) => {
69-
if (stats == null) {
70-
L.debug("WATCHER change with no stats (shouldn't happen)", { path });
71-
return;
72-
}
73-
emitChange(stats.ctime);
74-
});
47+
48+
logger.debug("watchFile", { path, debounce, interval });
49+
watchFile(this.path, { persistent: false, interval }, this.handleChange);
50+
51+
if (debounce) {
52+
this.emitChange = lodashDebounce(this.emitChange, debounce);
53+
}
7554
}
7655

77-
close = async () => {
56+
private emitChange = (stats) => {
57+
this.emit("change", stats.ctime, stats);
58+
};
59+
60+
private handleChange = (curr, prev) => {
61+
const path = this.path;
62+
if (!curr.dev) {
63+
logger.debug("handleChange: delete", { path });
64+
this.emit("delete");
65+
return;
66+
}
67+
if (curr.mtimeMs == prev.mtimeMs && curr.mode == prev.mode) {
68+
logger.debug("handleChange: access but no change", { path });
69+
// just *accessing* triggers watchFile (really StatWatcher), of course.
70+
return;
71+
}
72+
logger.debug("handleChange: change", { path });
73+
this.emitChange(curr);
74+
};
75+
76+
close = () => {
77+
logger.debug("close", this.path);
7878
this.removeAllListeners();
79-
await this.watcher.close();
79+
unwatchFile(this.path, this.handleChange);
8080
};
8181
}

src/packages/frontend/account/editor-settings/checkboxes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const EDITOR_SETTINGS_CHECKBOXES: { [setting: string]: string | Rendered } = {
3030
build_on_save: "build LaTex/Rmd files whenever it is saved to disk",
3131
show_exec_warning: "warn that certain files are not directly executable",
3232
ask_jupyter_kernel: "ask which kernel to use for a new Jupyter Notebook",
33+
disable_jupyter_virtualization:
34+
"render entire notebook instead of just visible part (slower and not recommended)",
3335
jupyter_classic: (
3436
<span>
3537
<A href="https://github.com/sagemathinc/cocalc/issues/7706">

src/packages/frontend/account/other-settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ export function OtherSettings(props: Readonly<Props>): JSX.Element {
402402

403403
function render_language_model(): Rendered {
404404
return (
405-
<LabeledRow label={<>Default Language Model</>}>
405+
<LabeledRow label={<>Default AI Language Model</>}>
406406
<LLMSelector model={model} setModel={setModel} />
407407
</LabeledRow>
408408
);

src/packages/frontend/antd-bootstrap.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ import {
3535
Tooltip,
3636
} from "antd";
3737
import { MouseEventHandler } from "react";
38+
3839
import { inDarkMode } from "@cocalc/frontend/account/dark-mode";
39-
import { r_join } from "@cocalc/frontend/components/r_join";
4040
import { Gap } from "@cocalc/frontend/components/gap";
41+
import { r_join } from "@cocalc/frontend/components/r_join";
4142
import { COLORS } from "@cocalc/util/theme";
4243

4344
// Note regarding buttons -- there are 6 semantics meanings in bootstrap, but

0 commit comments

Comments
 (0)