Skip to content

Commit

Permalink
[dashboard] Start at most one workspace after prebuild finished
Browse files Browse the repository at this point in the history
Under certain circumstances - if a workspace failure appeared before the ideURL got set - we would never leave the HeadlessLogView but start a new workspace over and over again. As we do not handle this error well, it would only get caught by timeouts (1h), leading to users being effectively blocked because they reached their parallel workspace limit
  • Loading branch information
geropl authored and roboquat committed Jul 20, 2021
1 parent a00db91 commit 03459a1
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 10 deletions.
25 changes: 19 additions & 6 deletions components/dashboard/src/start/CreateWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,32 @@ function RunningPrebuildView(props: RunningPrebuildViewProps) {
const logsEmitter = new EventEmitter();
const service = getGitpodService();
let pollTimeout: NodeJS.Timeout | undefined;
let prebuildDoneTriggered: boolean = false;

useEffect(() => {
const pollIsPrebuildDone = async () => {
clearTimeout(pollTimeout!);
const available = await service.server.isPrebuildDone(props.runningPrebuild.prebuildID);
if (available) {
const checkIsPrebuildDone = async (): Promise<boolean> => {
if (prebuildDoneTriggered) {
console.debug("prebuild done already triggered, doing nothing");
return true;
}

const done = await service.server.isPrebuildDone(props.runningPrebuild.prebuildID);
if (done) {
// note: this treats "done" as "available" which is not equivalent.
// This works because the backend ignores prebuilds which are not "available", and happily starts a workspace as if there was no prebuild at all.
prebuildDoneTriggered = true;
props.onPrebuildSucceeded();
return;
return true;
}
return false;
};
const pollIsPrebuildDone = async () => {
clearTimeout(pollTimeout!);
await checkIsPrebuildDone();
pollTimeout = setTimeout(pollIsPrebuildDone, 10000);
};

const disposables = watchHeadlessLogs(service.server, props.runningPrebuild.instanceID, (chunk) => logsEmitter.emit('logs', chunk), pollIsPrebuildDone);
const disposables = watchHeadlessLogs(service.server, props.runningPrebuild.instanceID, (chunk) => logsEmitter.emit('logs', chunk), checkIsPrebuildDone);
return function cleanup() {
clearTimeout(pollTimeout!);
disposables.dispose();
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/start/StartWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ function HeadlessWorkspaceView(props: { instanceId: string }) {

useEffect(() => {
const service = getGitpodService();
const disposables = watchHeadlessLogs(service.server, props.instanceId, (chunk) => logsEmitter.emit('logs', chunk), () => {});
const disposables = watchHeadlessLogs(service.server, props.instanceId, (chunk) => logsEmitter.emit('logs', chunk), async () => { return false; });
return function cleanup() {
disposables.dispose();
};
Expand Down
10 changes: 7 additions & 3 deletions components/dashboard/src/start/WorkspaceLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ export default class WorkspaceLogs extends React.Component<WorkspaceLogsProps, W
}
}

export function watchHeadlessLogs(server: GitpodServer, instanceId: string, onLog: (chunk: string) => void, checkIsDone: () => Promise<void> | void): DisposableCollection {
export function watchHeadlessLogs(server: GitpodServer, instanceId: string, onLog: (chunk: string) => void, checkIsDone: () => Promise<boolean>): DisposableCollection {
const disposables = new DisposableCollection();

const startWatchingLogs = async () => {
await checkIsDone();
if (await checkIsDone()) {
return;
}

const retry = async (reason: string, err?: Error) => {
console.debug("re-trying headless-logs because: " + reason, err);
Expand Down Expand Up @@ -155,7 +157,9 @@ export function watchHeadlessLogs(server: GitpodServer, instanceId: string, onLo
}
reader.cancel()

await checkIsDone();
if (await checkIsDone()) {
return;
}
} catch(err) {
reader?.cancel().catch(console.debug);
await retry("error while listening to stream", err);
Expand Down

0 comments on commit 03459a1

Please sign in to comment.