Skip to content

Commit 93c474c

Browse files
authored
Add status updates when connecting to compute instance. Close #1928 (#1929)
1 parent 283ee04 commit 93c474c

File tree

8 files changed

+202
-55
lines changed

8 files changed

+202
-55
lines changed

src/common/compute/interactive/message.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
}(this, function() {
1111
const Constants = makeEnum('STDOUT', 'STDERR', 'RUN', 'ADD_ARTIFACT', 'KILL',
1212
'ADD_FILE', 'REMOVE_FILE', 'ADD_USER_DATA', 'COMPLETE', 'ERROR', 'SET_ENV',
13-
'SAVE_ARTIFACT');
13+
'SAVE_ARTIFACT', 'STATUS');
1414

1515
function makeEnum() {
1616
const names = Array.prototype.slice.call(arguments);

src/common/compute/interactive/session-with-queue.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ define([
6363
}
6464
}
6565

66-
static async new(id, config) {
67-
return await Session.new(id, config, SessionWithQueue);
66+
static new(id, config) {
67+
return Session.new(id, config, SessionWithQueue);
6868
}
6969
}
7070

src/common/compute/interactive/session.js

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -163,38 +163,60 @@ define([
163163
return new Session(this.channel);
164164
}
165165

166-
static async new(computeID, config={}, SessionClass=InteractiveSession) {
167-
const channel = await createMessageChannel(computeID, config);
168-
const session = new SessionClass(channel);
169-
return session;
170-
}
171-
}
172-
173-
async function createMessageChannel(computeID, config) {
174-
const address = gmeConfig.extensions.InteractiveComputeHost ||
175-
getDefaultServerURL();
176-
177-
const connectedWs = await new Promise((resolve, reject) => {
178-
const ws = new WebSocket(address);
179-
ws.onopen = () => {
180-
ws.send(JSON.stringify([computeID, config, getGMEToken()]));
181-
ws.onmessage = async (wsMsg) => {
182-
const data = await Task.getMessageData(wsMsg);
183-
184-
const msg = Message.decode(data);
185-
if (msg.type === Message.COMPLETE) {
186-
const err = msg.data;
187-
if (err) {
166+
static new(computeID, config={}, SessionClass=InteractiveSession) {
167+
const address = gmeConfig.extensions.InteractiveComputeHost ||
168+
getDefaultServerURL();
169+
170+
let createSession;
171+
createSession = new PromiseEvents(function(resolve, reject) {
172+
const ws = new WebSocket(address);
173+
ws.onopen = () => {
174+
ws.send(JSON.stringify([computeID, config, getGMEToken()]));
175+
ws.onmessage = async (wsMsg) => {
176+
const data = await Task.getMessageData(wsMsg);
177+
178+
const msg = Message.decode(data);
179+
if (msg.type === Message.COMPLETE) {
180+
const err = msg.data;
181+
if (err) {
182+
reject(err);
183+
} else {
184+
const channel = new MessageChannel(ws);
185+
const session = new SessionClass(channel);
186+
resolve(session);
187+
}
188+
} else if (msg.type === Message.ERROR) {
189+
const err = msg.data;
188190
reject(err);
189-
} else {
190-
resolve(ws);
191+
} else if (msg.type === Message.STATUS) {
192+
createSession.emit('update', msg.data);
191193
}
192-
}
194+
};
193195
};
194-
};
195-
});
196+
});
197+
198+
return createSession;
199+
}
200+
}
196201

197-
return new MessageChannel(connectedWs);
202+
class PromiseEvents extends Promise {
203+
constructor(fn) {
204+
super(fn);
205+
this._handlers = {};
206+
}
207+
208+
on(event, fn) {
209+
if (!this._handlers[event]) {
210+
this._handlers[event] = [];
211+
}
212+
this._handlers[event].push(fn);
213+
}
214+
215+
emit(event) {
216+
const handlers = this._handlers[event] || [];
217+
const args = Array.prototype.slice.call(arguments, 1);
218+
handlers.forEach(fn => fn.apply(null, args));
219+
}
198220
}
199221

200222
function getDefaultServerURL() {

src/routers/InteractiveCompute/Session.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ class Session extends EventEmitter {
4040
const name = 'DeepForge Interactive Session';
4141
const computeJob = new ComputeJob(hash, name);
4242
this.jobInfo = this.compute.startJob(computeJob);
43+
this.compute.on('update', async (id, status) =>
44+
this.clientSocket.send(Message.encode(-1, Message.STATUS, status))
45+
);
4346
this.compute.on('end', async (id, info) => {
4447
const isError = this.clientSocket.readyState === WebSocket.OPEN &&
4548
info.status !== ComputeClient.SUCCESS;
4649
if (isError) {
47-
this.clientSocket.send(Message.encode(Message.ERROR, info));
50+
this.clientSocket.send(Message.encode(-1, Message.ERROR, info));
4851
}
4952
await this.compute.purgeJob(computeJob);
5053
});

src/visualizers/widgets/InteractiveEditor/InteractiveEditorWidget.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ define([
1414
DeepForge,
1515
) {
1616
const COMPUTE_MESSAGE = 'Compute Required. Click to configure.';
17+
const COMPUTE_LOADING_MESSAGE = 'Connecting to Compute Instance...';
18+
const LoaderHTML = '<div class="lds-ripple"><div></div><div></div></div>';
1719
class InteractiveEditorWidget {
1820
constructor(container) {
1921
this.showComputeShield(container);
@@ -22,13 +24,21 @@ define([
2224
showComputeShield(container) {
2325
const overlay = $('<div>', {class: 'compute-shield'});
2426
container.append(overlay);
25-
const msg = $('<span>');
26-
msg.text(COMPUTE_MESSAGE);
27+
overlay.append($('<div>', {class: 'filler'}));
28+
const loader = $(LoaderHTML);
29+
overlay.append(loader);
30+
const msg = $('<span>', {class: 'title'});
2731
overlay.append(msg);
32+
const subtitle = $('<span>', {class: 'subtitle'});
33+
overlay.append(subtitle);
34+
msg.text(COMPUTE_MESSAGE);
35+
loader.addClass('hidden');
36+
subtitle.addClass('hidden');
37+
2838
overlay.on('click', async () => {
2939
const {id, config} = await this.promptComputeConfig();
3040
try {
31-
this.session = await this.createInteractiveSession(id, config);
41+
this.session = await this.createInteractiveSession(id, config, overlay);
3242
const features = this.getCapabilities();
3343
if (features.save) {
3444
DeepForge.registerAction('Save', 'save', 10, () => this.save());
@@ -38,6 +48,10 @@ define([
3848
} catch (err) {
3949
const title = 'Compute Creation Error';
4050
const body = 'Unable to create compute. Please verify the credentials are correct.';
51+
msg.text(COMPUTE_MESSAGE);
52+
loader.addClass('hidden');
53+
subtitle.addClass('hidden');
54+
4155
// TODO: Detect authorization errors...
4256
const dialog = new InformDialog(title, body);
4357
dialog.show();
@@ -94,15 +108,43 @@ define([
94108
return {id, config};
95109
}
96110

97-
async createInteractiveSession(computeId, config) {
98-
return await Session.new(computeId, config);
111+
showComputeLoadingStatus(status, overlay) {
112+
const msg = overlay.find('.subtitle');
113+
const loader = overlay.find('.lds-ripple');
114+
const title = overlay.find('.title');
115+
116+
title.text(COMPUTE_LOADING_MESSAGE);
117+
loader.removeClass('hidden');
118+
msg.removeClass('hidden');
119+
return msg;
120+
}
121+
122+
updateComputeLoadingStatus(status, subtitle) {
123+
const displayText = status === 'running' ?
124+
'Configuring environment' :
125+
status.substring(0, 1).toUpperCase() + status.substring(1);
126+
subtitle.text(`${displayText}...`);
127+
}
128+
129+
async createInteractiveSession(computeId, config, overlay) {
130+
const createSession = Session.new(computeId, config);
131+
132+
const msg = this.showComputeLoadingStatus(status, overlay);
133+
this.updateComputeLoadingStatus('Connecting', msg);
134+
createSession.on(
135+
'update',
136+
status => this.updateComputeLoadingStatus(status, msg)
137+
);
138+
const session = await createSession;
139+
return session;
99140
}
100141

101142
destroy() {
102143
const features = this.getCapabilities();
103144
if (features.save) {
104145
DeepForge.unregisterAction('Save');
105146
}
147+
this.session.close();
106148
}
107149

108150
updateNode(/*desc*/) {

src/visualizers/widgets/InteractiveEditor/styles/InteractiveEditorWidget.css

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,46 @@
1313
top: 0;
1414
z-index: 100; }
1515

16-
.compute-shield span {
16+
.compute-shield .invisible {
17+
visibility: hidden; }
18+
.compute-shield .filler {
19+
margin-top: 25%; }
20+
.compute-shield .title {
1721
color: whitesmoke;
1822
display: block;
1923
font-size: 2em;
2024
margin: auto;
21-
padding-top: 25%;
2225
text-align: center; }
26+
.compute-shield .subtitle {
27+
color: whitesmoke;
28+
display: block;
29+
font-size: 1.5em;
30+
margin: auto;
31+
text-align: center; }
32+
.compute-shield .lds-ripple {
33+
margin: auto;
34+
display: block;
35+
position: relative;
36+
width: 80px;
37+
height: 80px; }
38+
.compute-shield .lds-ripple div {
39+
position: absolute;
40+
border: 4px solid #fff;
41+
opacity: 1;
42+
border-radius: 50%;
43+
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; }
44+
.compute-shield .lds-ripple div:nth-child(2) {
45+
animation-delay: -0.5s; }
46+
@keyframes lds-ripple {
47+
0% {
48+
top: 36px;
49+
left: 36px;
50+
width: 0;
51+
height: 0;
52+
opacity: 1; }
53+
100% {
54+
top: 0px;
55+
left: 0px;
56+
width: 72px;
57+
height: 72px;
58+
opacity: 0; } }

src/visualizers/widgets/InteractiveEditor/styles/InteractiveEditorWidget.scss

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,62 @@
1616
z-index: 100;
1717
}
1818

19-
.compute-shield span {
20-
color: whitesmoke;
21-
display: block;
22-
font-size: 2em;
23-
margin: auto;
24-
padding-top: 25%;
25-
text-align: center;
19+
.compute-shield {
20+
.hidden {
21+
display: none;
22+
}
23+
24+
.filler {
25+
margin-top: 25%;
26+
}
27+
28+
.title {
29+
color: whitesmoke;
30+
display: block;
31+
font-size: 2em;
32+
margin: auto;
33+
text-align: center;
34+
}
35+
36+
.subtitle {
37+
color: whitesmoke;
38+
display: block;
39+
font-size: 1.5em;
40+
margin: auto;
41+
text-align: center;
42+
}
43+
44+
.lds-ripple {
45+
margin: auto;
46+
display: block;
47+
position: relative;
48+
width: 80px;
49+
height: 80px;
50+
}
51+
.lds-ripple div {
52+
position: absolute;
53+
border: 4px solid #fff;
54+
opacity: 1;
55+
border-radius: 50%;
56+
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
57+
}
58+
.lds-ripple div:nth-child(2) {
59+
animation-delay: -0.5s;
60+
}
61+
@keyframes lds-ripple {
62+
0% {
63+
top: 36px;
64+
left: 36px;
65+
width: 0;
66+
height: 0;
67+
opacity: 1;
68+
}
69+
100% {
70+
top: 0px;
71+
left: 0px;
72+
width: 72px;
73+
height: 72px;
74+
opacity: 0;
75+
}
76+
}
2677
}

src/visualizers/widgets/TensorPlotter/TensorPlotterWidget.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,6 @@ define([
6464
this._logger.debug('ctor finished');
6565
}
6666

67-
async createInteractiveSession(computeId, config) {
68-
const session = await Session.new(computeId, config);
69-
this.initSession(session);
70-
this.artifactLoader.session = session;
71-
return session;
72-
}
73-
7467
async getAuthenticationConfig (dataInfo) {
7568
const {backend} = dataInfo;
7669
const metadata = Storage.getStorageMetadata(backend);
@@ -87,8 +80,8 @@ define([
8780
}
8881
}
8982

90-
async initSession (session) {
91-
await session.whenConnected();
83+
async onComputeInitialized (session) {
84+
this.artifactLoader.session = session;
9285
const initCode = await this.getInitializationCode();
9386
await session.addFile('utils/init.py', initCode);
9487
await session.addFile('utils/explorer_helpers.py', HELPERS_PY);

0 commit comments

Comments
 (0)