Skip to content

Commit 3ca9311

Browse files
committed
Improve loading indicator and avoid stale state:
- clear `usage` on switching to prevent stale usage data, - `useRef` to prevent stale `kernelId` (and outdated responses)
1 parent 65e5547 commit 3ca9311

File tree

1 file changed

+114
-105
lines changed

1 file changed

+114
-105
lines changed

packages/labextension/src/widget.tsx

Lines changed: 114 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useCallback, useState, useEffect } from 'react';
1+
import React, { useRef, useState, useEffect } from 'react';
22
import { ISignal } from '@lumino/signaling';
33
import { ReactWidget, ISessionContext } from '@jupyterlab/apputils';
44
import { IChangedArgs } from '@jupyterlab/coreutils';
@@ -113,12 +113,6 @@ const BlankReason = (props: {
113113
{props.trans.__('No active kernel found.')}
114114
</div>
115115
);
116-
} else if (reason.reason === 'loading') {
117-
return (
118-
<div className="jp-KernelUsage-section-separator">
119-
{props.trans.__('Kernel usage is loading.')}
120-
</div>
121-
);
122116
} else {
123117
return (
124118
<div className="jp-KernelUsage-section-separator">
@@ -149,30 +143,35 @@ const KernelUsage = (props: {
149143
}
150144
}, POLL_INTERVAL_SEC * 1000);
151145

152-
const requestUsage = useCallback((kid: string) => {
146+
const kernelIdRef = useRef<string | undefined>(kernelId);
147+
kernelIdRef.current = kernelId;
148+
149+
const requestUsage = (kid: string) => {
153150
return requestAPI<any>(`get_usage/${kid}`).then((data) => {
154151
// The kernel reply may arrive late due to lax timeouts, so we need to
155152
// check if it is for the current kernel
156-
if (data.kernel_id && data.kernel_id !== kid) {
157-
// Ignore outdated response.
153+
154+
if (kid !== kernelIdRef.current) {
155+
// Ignore outdated response, but preserve current reason
158156
return;
159157
}
160158

161159
if (data.content?.reason) {
162160
const reason = data.content;
163161
setReason(reason);
164162
return;
163+
} else {
164+
setReason(undefined);
165165
}
166166

167167
const usage: Usage = {
168168
...data.content,
169169
timestamp: new Date(),
170170
kernel_id: kid,
171171
};
172-
setReason(undefined);
173172
setUsage(usage);
174173
});
175-
}, []);
174+
};
176175

177176
useEffect(() => {
178177
const createKernelChangeCallback = (panel: IWidgetWithSession) => {
@@ -226,6 +225,8 @@ const KernelUsage = (props: {
226225
setKernelId(kernelId);
227226
const path = panel.sessionContext.session?.model.path;
228227
setPath(path);
228+
setUsage(undefined);
229+
setReason({ reason: 'loading' });
229230
requestUsage(kernelId);
230231
} else {
231232
setKernelId(undefined);
@@ -243,7 +244,11 @@ const KernelUsage = (props: {
243244
};
244245
}, [kernelId]);
245246

246-
if (blankStateReason && !(blankStateReason?.reason === 'timeout' && usage)) {
247+
if (
248+
blankStateReason &&
249+
blankStateReason?.reason !== 'timeout' &&
250+
blankStateReason?.reason !== 'loading'
251+
) {
247252
return (
248253
<>
249254
<h3 className="jp-KernelUsage-section-separator">
@@ -254,108 +259,112 @@ const KernelUsage = (props: {
254259
);
255260
}
256261
if (kernelId) {
257-
if (usage) {
258-
return (
259-
<>
260-
<h3 className="jp-KernelUsage-section-separator">
261-
{props.trans.__('Kernel usage')}
262-
</h3>
263-
{blankStateReason?.reason === 'timeout' ? (
264-
<strong>
265-
{props.trans.__(
266-
'Timed out in: %1 ms',
267-
blankStateReason.timeout_ms
262+
return (
263+
<>
264+
<h3 className="jp-KernelUsage-section-separator">
265+
{props.trans.__('Kernel usage')}
266+
</h3>
267+
{blankStateReason?.reason === 'timeout' ? (
268+
<strong>
269+
{props.trans.__('Timed out in: %1 ms', blankStateReason.timeout_ms)}
270+
</strong>
271+
) : null}
272+
<div className="jp-KernelUsage-separator">
273+
{props.trans.__('Notebook:')} {path}
274+
</div>
275+
<div className="jp-KernelUsage-separator">
276+
{props.trans.__('Kernel ID:')} {kernelId}
277+
</div>
278+
<div
279+
className={
280+
blankStateReason?.reason === 'timeout' ? TIMEOUT_CLASS : ''
281+
}
282+
>
283+
{usage ? (
284+
<>
285+
<div className="jp-KernelUsage-separator">
286+
{props.trans.__('Kernel Host:')} {usage.hostname}
287+
</div>
288+
<div className="jp-KernelUsage-separator">
289+
{props.trans.__('Timestamp:')}{' '}
290+
{usage.timestamp?.toLocaleString()}
291+
</div>
292+
<div className="jp-KernelUsage-separator">
293+
{props.trans.__('Process ID:')} {usage.pid}
294+
</div>
295+
<div className="jp-KernelUsage-separator">
296+
{props.trans.__('CPU:')} {usage.kernel_cpu}
297+
</div>
298+
<div className="jp-KernelUsage-separator">
299+
{props.trans.__('Memory:')}{' '}
300+
{formatForDisplay(usage.kernel_memory)}
301+
</div>
302+
<hr className="jp-KernelUsage-section-separator"></hr>
303+
<h4 className="jp-KernelUsage-section-separator">
304+
{props.trans.__('Host CPU')}
305+
</h4>
306+
{usage.host_cpu_percent && (
307+
<div className="jp-KernelUsage-separator">
308+
{props.trans._n(
309+
'%2%% used on %1 CPU',
310+
'%2%% used on %1 CPUs',
311+
usage.cpu_count,
312+
usage.host_cpu_percent.toFixed(1)
313+
)}
314+
</div>
268315
)}
269-
</strong>
270-
) : null}
271-
<div
272-
className={
273-
blankStateReason?.reason === 'timeout' ? TIMEOUT_CLASS : ''
274-
}
275-
>
276-
<div className="jp-KernelUsage-separator">
277-
{props.trans.__('Kernel Host:')} {usage.hostname}
278-
</div>
279-
<div className="jp-KernelUsage-separator">
280-
{props.trans.__('Notebook:')} {path}
281-
</div>
282-
<div className="jp-KernelUsage-separator">
283-
{props.trans.__('Kernel ID:')} {kernelId}
284-
</div>
285-
<div className="jp-KernelUsage-separator">
286-
{props.trans.__('Timestamp:')} {usage.timestamp?.toLocaleString()}
287-
</div>
288-
<div className="jp-KernelUsage-separator">
289-
{props.trans.__('Process ID:')} {usage.pid}
290-
</div>
291-
<div className="jp-KernelUsage-separator">
292-
{props.trans.__('CPU:')} {usage.kernel_cpu}
293-
</div>
294-
<div className="jp-KernelUsage-separator">
295-
{props.trans.__('Memory:')}{' '}
296-
{formatForDisplay(usage.kernel_memory)}
297-
</div>
298-
<hr className="jp-KernelUsage-section-separator"></hr>
299-
<h4 className="jp-KernelUsage-section-separator">
300-
{props.trans.__('Host CPU')}
301-
</h4>
302-
{usage.host_cpu_percent && (
316+
<h4 className="jp-KernelUsage-section-separator">
317+
{props.trans.__('Host Virtual Memory')}
318+
</h4>
303319
<div className="jp-KernelUsage-separator">
304-
{props.trans._n(
305-
'%2%% used on %1 CPU',
306-
'%2%% used on %1 CPUs',
307-
usage.cpu_count,
308-
usage.host_cpu_percent.toFixed(1)
309-
)}
320+
{props.trans.__('Active:')}{' '}
321+
{formatForDisplay(usage.host_virtual_memory.active)}
310322
</div>
311-
)}
312-
<h4 className="jp-KernelUsage-section-separator">
313-
{props.trans.__('Host Virtual Memory')}
314-
</h4>
315-
<div className="jp-KernelUsage-separator">
316-
{props.trans.__('Active:')}{' '}
317-
{formatForDisplay(usage.host_virtual_memory.active)}
318-
</div>
319-
<div className="jp-KernelUsage-separator">
320-
{props.trans.__('Available:')}{' '}
321-
{formatForDisplay(usage.host_virtual_memory.available)}
322-
</div>
323-
<div className="jp-KernelUsage-separator">
324-
{props.trans.__('Free:')}{' '}
325-
{formatForDisplay(usage.host_virtual_memory.free)}
326-
</div>
327-
<div className="jp-KernelUsage-separator">
328-
{props.trans.__('Inactive:')}{' '}
329-
{formatForDisplay(usage.host_virtual_memory.inactive)}
330-
</div>
331-
{usage.host_virtual_memory.percent && (
332323
<div className="jp-KernelUsage-separator">
333-
{props.trans.__('Percent used:')}{' '}
334-
{usage.host_virtual_memory.percent.toFixed(1)}%
324+
{props.trans.__('Available:')}{' '}
325+
{formatForDisplay(usage.host_virtual_memory.available)}
335326
</div>
336-
)}
337-
<div className="jp-KernelUsage-separator">
338-
{props.trans.__('Total:')}{' '}
339-
{formatForDisplay(usage.host_virtual_memory.total)}
340-
</div>
327+
<div className="jp-KernelUsage-separator">
328+
{props.trans.__('Free:')}{' '}
329+
{formatForDisplay(usage.host_virtual_memory.free)}
330+
</div>
331+
<div className="jp-KernelUsage-separator">
332+
{props.trans.__('Inactive:')}{' '}
333+
{formatForDisplay(usage.host_virtual_memory.inactive)}
334+
</div>
335+
{usage.host_virtual_memory.percent && (
336+
<div className="jp-KernelUsage-separator">
337+
{props.trans.__('Percent used:')}{' '}
338+
{usage.host_virtual_memory.percent.toFixed(1)}%
339+
</div>
340+
)}
341+
<div className="jp-KernelUsage-separator">
342+
{props.trans.__('Total:')}{' '}
343+
{formatForDisplay(usage.host_virtual_memory.total)}
344+
</div>
345+
<div className="jp-KernelUsage-separator">
346+
{props.trans.__('Used:')}{' '}
347+
{formatForDisplay(usage.host_virtual_memory.used)}
348+
</div>
349+
<div className="jp-KernelUsage-separator">
350+
{props.trans.__('Wired:')}{' '}
351+
{formatForDisplay(usage.host_virtual_memory.wired)}
352+
</div>
353+
</>
354+
) : blankStateReason?.reason === 'loading' ? (
341355
<div className="jp-KernelUsage-separator">
342-
{props.trans.__('Used:')}{' '}
343-
{formatForDisplay(usage.host_virtual_memory.used)}
356+
{props.trans.__('Loading…')}
344357
</div>
358+
) : (
345359
<div className="jp-KernelUsage-separator">
346-
{props.trans.__('Wired:')}{' '}
347-
{formatForDisplay(usage.host_virtual_memory.wired)}
360+
{props.trans.__('Usage data is missing')}
348361
</div>
349-
</div>
350-
</>
351-
);
352-
}
362+
)}
363+
</div>
364+
</>
365+
);
353366
}
354-
return (
355-
<>
356-
<h3>{props.trans.__('Kernel usage is missing')}</h3>
357-
</>
358-
);
367+
return <h3>{props.trans.__('Kernel usage is missing')}</h3>;
359368
};
360369

361370
export class KernelUsageWidget extends ReactWidget {

0 commit comments

Comments
 (0)