Skip to content

Commit

Permalink
Add status updates when connecting to compute instance. Close #1928 (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
brollb authored Sep 29, 2020
1 parent 283ee04 commit 93c474c
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 55 deletions.
2 changes: 1 addition & 1 deletion src/common/compute/interactive/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}(this, function() {
const Constants = makeEnum('STDOUT', 'STDERR', 'RUN', 'ADD_ARTIFACT', 'KILL',
'ADD_FILE', 'REMOVE_FILE', 'ADD_USER_DATA', 'COMPLETE', 'ERROR', 'SET_ENV',
'SAVE_ARTIFACT');
'SAVE_ARTIFACT', 'STATUS');

function makeEnum() {
const names = Array.prototype.slice.call(arguments);
Expand Down
4 changes: 2 additions & 2 deletions src/common/compute/interactive/session-with-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ define([
}
}

static async new(id, config) {
return await Session.new(id, config, SessionWithQueue);
static new(id, config) {
return Session.new(id, config, SessionWithQueue);
}
}

Expand Down
78 changes: 50 additions & 28 deletions src/common/compute/interactive/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,38 +163,60 @@ define([
return new Session(this.channel);
}

static async new(computeID, config={}, SessionClass=InteractiveSession) {
const channel = await createMessageChannel(computeID, config);
const session = new SessionClass(channel);
return session;
}
}

async function createMessageChannel(computeID, config) {
const address = gmeConfig.extensions.InteractiveComputeHost ||
getDefaultServerURL();

const connectedWs = await new Promise((resolve, reject) => {
const ws = new WebSocket(address);
ws.onopen = () => {
ws.send(JSON.stringify([computeID, config, getGMEToken()]));
ws.onmessage = async (wsMsg) => {
const data = await Task.getMessageData(wsMsg);

const msg = Message.decode(data);
if (msg.type === Message.COMPLETE) {
const err = msg.data;
if (err) {
static new(computeID, config={}, SessionClass=InteractiveSession) {
const address = gmeConfig.extensions.InteractiveComputeHost ||
getDefaultServerURL();

let createSession;
createSession = new PromiseEvents(function(resolve, reject) {
const ws = new WebSocket(address);
ws.onopen = () => {
ws.send(JSON.stringify([computeID, config, getGMEToken()]));
ws.onmessage = async (wsMsg) => {
const data = await Task.getMessageData(wsMsg);

const msg = Message.decode(data);
if (msg.type === Message.COMPLETE) {
const err = msg.data;
if (err) {
reject(err);
} else {
const channel = new MessageChannel(ws);
const session = new SessionClass(channel);
resolve(session);
}
} else if (msg.type === Message.ERROR) {
const err = msg.data;
reject(err);
} else {
resolve(ws);
} else if (msg.type === Message.STATUS) {
createSession.emit('update', msg.data);
}
}
};
};
};
});
});

return createSession;
}
}

return new MessageChannel(connectedWs);
class PromiseEvents extends Promise {
constructor(fn) {
super(fn);
this._handlers = {};
}

on(event, fn) {
if (!this._handlers[event]) {
this._handlers[event] = [];
}
this._handlers[event].push(fn);
}

emit(event) {
const handlers = this._handlers[event] || [];
const args = Array.prototype.slice.call(arguments, 1);
handlers.forEach(fn => fn.apply(null, args));
}
}

function getDefaultServerURL() {
Expand Down
5 changes: 4 additions & 1 deletion src/routers/InteractiveCompute/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@ class Session extends EventEmitter {
const name = 'DeepForge Interactive Session';
const computeJob = new ComputeJob(hash, name);
this.jobInfo = this.compute.startJob(computeJob);
this.compute.on('update', async (id, status) =>
this.clientSocket.send(Message.encode(-1, Message.STATUS, status))
);
this.compute.on('end', async (id, info) => {
const isError = this.clientSocket.readyState === WebSocket.OPEN &&
info.status !== ComputeClient.SUCCESS;
if (isError) {
this.clientSocket.send(Message.encode(Message.ERROR, info));
this.clientSocket.send(Message.encode(-1, Message.ERROR, info));
}
await this.compute.purgeJob(computeJob);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ define([
DeepForge,
) {
const COMPUTE_MESSAGE = 'Compute Required. Click to configure.';
const COMPUTE_LOADING_MESSAGE = 'Connecting to Compute Instance...';
const LoaderHTML = '<div class="lds-ripple"><div></div><div></div></div>';
class InteractiveEditorWidget {
constructor(container) {
this.showComputeShield(container);
Expand All @@ -22,13 +24,21 @@ define([
showComputeShield(container) {
const overlay = $('<div>', {class: 'compute-shield'});
container.append(overlay);
const msg = $('<span>');
msg.text(COMPUTE_MESSAGE);
overlay.append($('<div>', {class: 'filler'}));
const loader = $(LoaderHTML);
overlay.append(loader);
const msg = $('<span>', {class: 'title'});
overlay.append(msg);
const subtitle = $('<span>', {class: 'subtitle'});
overlay.append(subtitle);
msg.text(COMPUTE_MESSAGE);
loader.addClass('hidden');
subtitle.addClass('hidden');

overlay.on('click', async () => {
const {id, config} = await this.promptComputeConfig();
try {
this.session = await this.createInteractiveSession(id, config);
this.session = await this.createInteractiveSession(id, config, overlay);
const features = this.getCapabilities();
if (features.save) {
DeepForge.registerAction('Save', 'save', 10, () => this.save());
Expand All @@ -38,6 +48,10 @@ define([
} catch (err) {
const title = 'Compute Creation Error';
const body = 'Unable to create compute. Please verify the credentials are correct.';
msg.text(COMPUTE_MESSAGE);
loader.addClass('hidden');
subtitle.addClass('hidden');

// TODO: Detect authorization errors...
const dialog = new InformDialog(title, body);
dialog.show();
Expand Down Expand Up @@ -94,15 +108,43 @@ define([
return {id, config};
}

async createInteractiveSession(computeId, config) {
return await Session.new(computeId, config);
showComputeLoadingStatus(status, overlay) {
const msg = overlay.find('.subtitle');
const loader = overlay.find('.lds-ripple');
const title = overlay.find('.title');

title.text(COMPUTE_LOADING_MESSAGE);
loader.removeClass('hidden');
msg.removeClass('hidden');
return msg;
}

updateComputeLoadingStatus(status, subtitle) {
const displayText = status === 'running' ?
'Configuring environment' :
status.substring(0, 1).toUpperCase() + status.substring(1);
subtitle.text(`${displayText}...`);
}

async createInteractiveSession(computeId, config, overlay) {
const createSession = Session.new(computeId, config);

const msg = this.showComputeLoadingStatus(status, overlay);
this.updateComputeLoadingStatus('Connecting', msg);
createSession.on(
'update',
status => this.updateComputeLoadingStatus(status, msg)
);
const session = await createSession;
return session;
}

destroy() {
const features = this.getCapabilities();
if (features.save) {
DeepForge.unregisterAction('Save');
}
this.session.close();
}

updateNode(/*desc*/) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,46 @@
top: 0;
z-index: 100; }

.compute-shield span {
.compute-shield .invisible {
visibility: hidden; }
.compute-shield .filler {
margin-top: 25%; }
.compute-shield .title {
color: whitesmoke;
display: block;
font-size: 2em;
margin: auto;
padding-top: 25%;
text-align: center; }
.compute-shield .subtitle {
color: whitesmoke;
display: block;
font-size: 1.5em;
margin: auto;
text-align: center; }
.compute-shield .lds-ripple {
margin: auto;
display: block;
position: relative;
width: 80px;
height: 80px; }
.compute-shield .lds-ripple div {
position: absolute;
border: 4px solid #fff;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite; }
.compute-shield .lds-ripple div:nth-child(2) {
animation-delay: -0.5s; }
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1; }
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0; } }
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,62 @@
z-index: 100;
}

.compute-shield span {
color: whitesmoke;
display: block;
font-size: 2em;
margin: auto;
padding-top: 25%;
text-align: center;
.compute-shield {
.hidden {
display: none;
}

.filler {
margin-top: 25%;
}

.title {
color: whitesmoke;
display: block;
font-size: 2em;
margin: auto;
text-align: center;
}

.subtitle {
color: whitesmoke;
display: block;
font-size: 1.5em;
margin: auto;
text-align: center;
}

.lds-ripple {
margin: auto;
display: block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ripple div {
position: absolute;
border: 4px solid #fff;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0;
}
}
}
11 changes: 2 additions & 9 deletions src/visualizers/widgets/TensorPlotter/TensorPlotterWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,6 @@ define([
this._logger.debug('ctor finished');
}

async createInteractiveSession(computeId, config) {
const session = await Session.new(computeId, config);
this.initSession(session);
this.artifactLoader.session = session;
return session;
}

async getAuthenticationConfig (dataInfo) {
const {backend} = dataInfo;
const metadata = Storage.getStorageMetadata(backend);
Expand All @@ -87,8 +80,8 @@ define([
}
}

async initSession (session) {
await session.whenConnected();
async onComputeInitialized (session) {
this.artifactLoader.session = session;
const initCode = await this.getInitializationCode();
await session.addFile('utils/init.py', initCode);
await session.addFile('utils/explorer_helpers.py', HELPERS_PY);
Expand Down

0 comments on commit 93c474c

Please sign in to comment.