[Code] handle delete workspace exception (#28062)

* [Code] Add a WorkerReservedProgress enum for predefined progress

* [Code] handle delete workspace exception
This commit is contained in:
Mengwei Ding 2019-01-04 10:50:38 -08:00 committed by GitHub
parent 3c6690a4db
commit 4d1bf85ffd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 136 additions and 55 deletions

View file

@ -86,6 +86,13 @@ export interface IndexWorkerResult extends WorkerResult {
stats: IndexStats;
}
export enum WorkerReservedProgress {
INIT = 0,
COMPLETED = 100,
ERROR = -100,
TIMEOUT = -200,
}
export interface WorkerProgress {
// Job payload repository uri.
uri: string;

View file

@ -8,8 +8,9 @@ import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import styled from 'styled-components';
import { RepositoryUtils } from '../../../common/repository_utils';
import { CloneProgress } from '../../../model';
import { CloneProgress, WorkerReservedProgress } from '../../../model';
import { MainRouteParams } from '../../common/types';
import { RootState } from '../../reducers';
import { cloneProgressSelector, progressSelector } from '../../selectors';
@ -42,7 +43,11 @@ interface Props extends RouteComponentProps<MainRouteParams> {
class CodeMain extends React.Component<Props> {
public shouldRenderProgress() {
const { progress, cloneProgress } = this.props;
return !!progress && progress < 100 && !RepositoryUtils.hasFullyCloned(cloneProgress);
return (
!!progress &&
progress < WorkerReservedProgress.COMPLETED &&
!RepositoryUtils.hasFullyCloned(cloneProgress)
);
}
public renderProgress() {

View file

@ -6,6 +6,8 @@
import { Action } from 'redux-actions';
import { put, select, takeEvery } from 'redux-saga/effects';
import { WorkerReservedProgress } from '../../model';
import { Match, routeChange } from '../actions';
import { loadStatusSuccess } from '../actions/status';
import * as ROUTES from '../components/routes';
@ -14,7 +16,8 @@ import { RootState } from '../reducers';
const matchSelector = (state: RootState) => state.route.match;
const pattern = (action: Action<any>) =>
action.type === String(loadStatusSuccess) && action.payload!.status.progress === 100;
action.type === String(loadStatusSuccess) &&
action.payload!.status.progress === WorkerReservedProgress.COMPLETED;
function* handleRepoCloneSuccess() {
const match: Match = yield select(matchSelector);

View file

@ -10,7 +10,8 @@ import Git from 'nodegit';
import path from 'path';
import rimraf from 'rimraf';
import sinon from 'sinon';
import { Repository } from '../../model';
import { Repository, WorkerReservedProgress } from '../../model';
import { AnyObject, EsClient, Esqueue } from '../lib/esqueue';
import { Log } from '../log';
import { CloneWorker } from '../queue';
@ -179,7 +180,10 @@ describe('clone_worker_tests', () => {
);
assert.ok(broadcastCloneProgressSpy.calledOnce);
assert.strictEqual(broadcastCloneProgressSpy.getCall(0).args[1], 100);
assert.strictEqual(
broadcastCloneProgressSpy.getCall(0).args[1],
WorkerReservedProgress.COMPLETED
);
assert.ok(enqueueJobSpy.calledOnce);
// EsClient update got called twice. One for updating default branch and revision
// of a repository. The other for update git clone status.

View file

@ -10,6 +10,8 @@ import Git, { CloneOptions } from 'nodegit';
import path from 'path';
import rimraf from 'rimraf';
import sinon from 'sinon';
import { WorkerReservedProgress } from '../../model';
import { LspIndexer } from '../indexer/lsp_indexer';
import { RepositoryGitStatusReservedField } from '../indexer/schema';
import { AnyObject, EsClient } from '../lib/esqueue';
@ -97,7 +99,7 @@ function setupEsClientSpy() {
_source: {
[RepositoryGitStatusReservedField]: {
uri: 'github.com/Microsoft/TypeScript-Node-Starter',
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
cloneProgress: {
isCloned: true,

View file

@ -18,7 +18,7 @@ import { DetailSymbolInformation } from 'elastic-lsp-extension';
import { RepositoryUtils } from '../../common/repository_utils';
import { parseLspUrl } from '../../common/uri_util';
import { LspRequest } from '../../model';
import { LspRequest, WorkerReservedProgress } from '../../model';
import { getDefaultBranch, GitOperations } from '../git_operations';
import { EsClient } from '../lib/esqueue';
import { Logger } from '../log';
@ -53,7 +53,10 @@ export class WorkspaceHandler {
try {
const gitStatus = await this.objectClient.getRepositoryGitStatus(repositoryUri);
if (!RepositoryUtils.hasFullyCloned(gitStatus.cloneProgress) && gitStatus.progress !== 100) {
if (
!RepositoryUtils.hasFullyCloned(gitStatus.cloneProgress) &&
gitStatus.progress !== WorkerReservedProgress.COMPLETED
) {
throw Boom.internal(`repository has not been fully cloned yet.`);
}
} catch (error) {

View file

@ -5,7 +5,12 @@
*/
import { RepositoryUtils } from '../../common/repository_utils';
import { CloneProgress, CloneWorkerProgress, CloneWorkerResult } from '../../model';
import {
CloneProgress,
CloneWorkerProgress,
CloneWorkerResult,
WorkerReservedProgress,
} from '../../model';
import { getDefaultBranch, getHeadRevision } from '../git_operations';
import { EsClient, Esqueue } from '../lib/esqueue';
import { Log } from '../log';
@ -44,7 +49,7 @@ export abstract class AbstractGitWorker extends AbstractWorker {
try {
return await this.objectClient.updateRepositoryGitStatus(repoUri, {
revision,
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
cloneProgress: {
isCloned: true,
},

View file

@ -6,7 +6,7 @@
import moment from 'moment';
import { WorkerResult } from '../../model';
import { WorkerReservedProgress, WorkerResult } from '../../model';
import {
CancellationToken,
Esqueue,
@ -97,22 +97,22 @@ export abstract class AbstractWorker implements Worker {
public async onJobEnqueued(job: Job) {
this.log.info(`${this.id} job enqueued with details ${JSON.stringify(job)}`);
return await this.updateProgress(job.payload.uri, 0);
return await this.updateProgress(job.payload.uri, WorkerReservedProgress.INIT);
}
public async onJobCompleted(job: Job, res: WorkerResult) {
this.log.info(`${this.id} job completed with result ${JSON.stringify(res)}`);
return await this.updateProgress(res.uri, 100);
return await this.updateProgress(res.uri, WorkerReservedProgress.COMPLETED);
}
public async onJobExecutionError(res: any) {
this.log.info(`${this.id} job execution error ${JSON.stringify(res)}.`);
return await this.updateProgress(res.job.payload.uri, -100);
this.log.error(`${this.id} job execution error ${JSON.stringify(res)}.`);
return await this.updateProgress(res.job.payload.uri, WorkerReservedProgress.ERROR);
}
public async onJobTimeOut(res: any) {
this.log.info(`${this.id} job timed out ${JSON.stringify(res)}`);
return await this.updateProgress(res.job.payload.uri, -200);
this.log.error(`${this.id} job timed out ${JSON.stringify(res)}`);
return await this.updateProgress(res.job.payload.uri, WorkerReservedProgress.TIMEOUT);
}
public async updateProgress(uri: string, progress: number) {

View file

@ -5,7 +5,12 @@
*/
import { RepositoryUtils } from '../../common/repository_utils';
import { CloneProgress, CloneWorkerProgress, CloneWorkerResult } from '../../model';
import {
CloneProgress,
CloneWorkerProgress,
CloneWorkerResult,
WorkerReservedProgress,
} from '../../model';
import { EsClient, Esqueue } from '../lib/esqueue';
import { Log } from '../log';
import { RepositoryServiceFactory } from '../repository_service_factory';
@ -45,7 +50,11 @@ export class CloneWorker extends AbstractGitWorker {
this.log.info(`Clone job done for ${res.repo.uri}`);
if (this.socketService) {
this.socketService.broadcastCloneProgress(res.repo.uri, 100, undefined);
this.socketService.broadcastCloneProgress(
res.repo.uri,
WorkerReservedProgress.COMPLETED,
undefined
);
}
// Throw out a repository index request.
@ -65,7 +74,7 @@ export class CloneWorker extends AbstractGitWorker {
const repo = RepositoryUtils.buildRepository(url);
const progress: CloneWorkerProgress = {
uri: repo.uri,
progress: 0,
progress: WorkerReservedProgress.INIT,
timestamp: new Date(),
};
return await this.objectClient.setRepositoryGitStatus(repo.uri, progress);

View file

@ -7,6 +7,7 @@
import sinon from 'sinon';
import { AnyObject, EsClient, Esqueue } from '../lib/esqueue';
import { WorkerReservedProgress } from '../../model';
import { Log } from '../log';
import { LspService } from '../lsp/lsp_service';
import { RepositoryServiceFactory } from '../repository_service_factory';
@ -91,8 +92,8 @@ test('Execute delete job.', async () => {
});
expect(broadcastDeleteProgressSpy.calledTwice).toBeTruthy();
expect(broadcastDeleteProgressSpy.getCall(0).args[1]).toEqual(0);
expect(broadcastDeleteProgressSpy.getCall(1).args[1]).toEqual(100);
expect(broadcastDeleteProgressSpy.getCall(0).args[1]).toEqual(WorkerReservedProgress.INIT);
expect(broadcastDeleteProgressSpy.getCall(1).args[1]).toEqual(WorkerReservedProgress.COMPLETED);
expect(cancelIndexJobSpy.calledOnce).toBeTruthy();

View file

@ -6,7 +6,7 @@
import moment from 'moment';
import { RepositoryUri } from '../../model';
import { RepositoryUri, WorkerReservedProgress } from '../../model';
import { WorkerProgress, WorkerResult } from '../../model/repository';
import { DocumentIndexName, ReferenceIndexName, SymbolIndexName } from '../indexer/schema';
import { EsClient, Esqueue } from '../lib/esqueue';
@ -40,7 +40,7 @@ export class DeleteWorker extends AbstractWorker {
const { uri, dataPath } = job.payload;
// 1. Notify repo delete start through websocket.
this.socketService.broadcastDeleteProgress(uri, 0);
this.socketService.broadcastDeleteProgress(uri, WorkerReservedProgress.INIT);
// 2. Cancel running workers
// TODO: Add support for clone/update worker.
@ -63,7 +63,11 @@ export class DeleteWorker extends AbstractWorker {
uri
);
const deleteWorkspacePromise = this.lspService.deleteWorkspace(uri);
const deleteWorkspacePromise = this.deletePromiseWrapper(
this.lspService.deleteWorkspace(uri),
'workspace',
uri
);
try {
await Promise.all([
@ -74,7 +78,7 @@ export class DeleteWorker extends AbstractWorker {
]);
// 5. Notify repo delete end through websocket.
this.socketService.broadcastDeleteProgress(uri, 100);
this.socketService.broadcastDeleteProgress(uri, WorkerReservedProgress.COMPLETED);
// 6. Delete the document index and alias where the repository document and all status reside,
// so that you won't be able to import the same repositories until they are
@ -93,7 +97,7 @@ export class DeleteWorker extends AbstractWorker {
this.log.error(`Delete repository ${uri} error.`);
this.log.error(error);
// Notify repo delete failed through websocket.
this.socketService.broadcastDeleteProgress(uri, -100);
this.socketService.broadcastDeleteProgress(uri, WorkerReservedProgress.ERROR);
return {
uri,
res: false,
@ -105,7 +109,7 @@ export class DeleteWorker extends AbstractWorker {
const repoUri = job.payload.uri;
const progress: WorkerProgress = {
uri: repoUri,
progress: 0,
progress: WorkerReservedProgress.INIT,
timestamp: new Date(),
};
return await this.objectClient.setRepositoryDeleteStatus(repoUri, progress);

View file

@ -5,6 +5,8 @@
*/
import sinon from 'sinon';
import { WorkerReservedProgress } from '../../model';
import { IndexerFactory } from '../indexer';
import { AnyObject, CancellationToken, EsClient, Esqueue } from '../lib/esqueue';
import { Log } from '../log';
@ -79,8 +81,8 @@ test('Execute index job.', async () => {
});
expect(broadcastIndexProgressSpy.calledTwice).toBeTruthy();
expect(broadcastIndexProgressSpy.getCall(0).args[1]).toEqual(0);
expect(broadcastIndexProgressSpy.getCall(1).args[1]).toEqual(100);
expect(broadcastIndexProgressSpy.getCall(0).args[1]).toEqual(WorkerReservedProgress.INIT);
expect(broadcastIndexProgressSpy.getCall(1).args[1]).toEqual(WorkerReservedProgress.COMPLETED);
expect(cancelIndexJobSpy.calledOnce).toBeTruthy();
@ -146,8 +148,8 @@ test('Execute index job and then cancel.', async () => {
cToken.cancel();
expect(broadcastIndexProgressSpy.calledTwice).toBeTruthy();
expect(broadcastIndexProgressSpy.getCall(0).args[1]).toEqual(0);
expect(broadcastIndexProgressSpy.getCall(1).args[1]).toEqual(100);
expect(broadcastIndexProgressSpy.getCall(0).args[1]).toEqual(WorkerReservedProgress.INIT);
expect(broadcastIndexProgressSpy.getCall(1).args[1]).toEqual(WorkerReservedProgress.COMPLETED);
expect(cancelIndexJobSpy.calledOnce).toBeTruthy();
@ -212,7 +214,7 @@ test('On index job completed.', async () => {
},
{
uri: 'github.com/elastic/kibana',
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
}
);

View file

@ -6,7 +6,13 @@
import moment from 'moment';
import { IndexStats, IndexWorkerResult, RepositoryUri, WorkerProgress } from '../../model';
import {
IndexStats,
IndexWorkerResult,
RepositoryUri,
WorkerProgress,
WorkerReservedProgress,
} from '../../model';
import { IndexerFactory, IndexProgress } from '../indexer';
import { EsClient, Esqueue } from '../lib/esqueue';
import { Log } from '../log';
@ -38,7 +44,7 @@ export class IndexWorker extends AbstractWorker {
const { payload, cancellationToken } = job;
const { uri, revision } = payload;
this.socketService.broadcastIndexProgress(uri, 0);
this.socketService.broadcastIndexProgress(uri, WorkerReservedProgress.INIT);
const indexerNumber = this.indexerFactories.length;
@ -59,7 +65,7 @@ export class IndexWorker extends AbstractWorker {
);
const stats: IndexStats[] = await Promise.all(indexPromises);
this.socketService.broadcastIndexProgress(uri, 100);
this.socketService.broadcastIndexProgress(uri, WorkerReservedProgress.COMPLETED);
const res: IndexWorkerResult = {
uri,
@ -84,7 +90,7 @@ export class IndexWorker extends AbstractWorker {
public async onJobCompleted(job: Job, res: WorkerProgress) {
const { uri } = job.payload;
await this.objectClient.updateRepositoryLspIndexStatus(uri, {
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
});
return await super.onJobCompleted(job, res);

View file

@ -7,7 +7,13 @@
import moment from 'moment';
import sinon from 'sinon';
import { CloneProgress, CloneWorkerProgress, Repository, WorkerProgress } from '../../model';
import {
CloneProgress,
CloneWorkerProgress,
Repository,
WorkerProgress,
WorkerReservedProgress,
} from '../../model';
import {
RepositoryGitStatusReservedField,
RepositoryLspIndexStatusReservedField,
@ -164,7 +170,7 @@ test('Next job should not execute when repo is still in clone.', done => {
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getStub = createGetStub(50, 100, 'newrevision');
const getStub = createGetStub(50, WorkerReservedProgress.COMPLETED, 'newrevision');
esClient.get = getStub;
const updateSpy = sinon.spy();
esClient.update = updateSpy;
@ -210,7 +216,7 @@ test('Next job should not execute when repo is still in the preivous index job.'
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getStub = createGetStub(100, 50, 'newrevision');
const getStub = createGetStub(WorkerReservedProgress.COMPLETED, 50, 'newrevision');
esClient.get = getStub;
const updateSpy = sinon.spy();
esClient.update = updateSpy;
@ -257,7 +263,11 @@ test('Next job should not execute when repo revision did not change.', done => {
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getStub = createGetStub(100, 100, 'master');
const getStub = createGetStub(
WorkerReservedProgress.COMPLETED,
WorkerReservedProgress.COMPLETED,
'master'
);
esClient.get = getStub;
const updateSpy = sinon.spy();
esClient.update = updateSpy;
@ -304,7 +314,11 @@ test('Next job should execute.', done => {
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getStub = createGetStub(100, 100, 'newrevision');
const getStub = createGetStub(
WorkerReservedProgress.COMPLETED,
WorkerReservedProgress.COMPLETED,
'newrevision'
);
esClient.get = getStub;
const updateSpy = sinon.spy();
esClient.update = updateSpy;

View file

@ -5,7 +5,7 @@
*/
import { RepositoryUtils } from '../../common/repository_utils';
import { Repository } from '../../model';
import { Repository, WorkerReservedProgress } from '../../model';
import { EsClient } from '../lib/esqueue';
import { Log } from '../log';
import { IndexWorker } from '../queue';
@ -42,7 +42,7 @@ export class IndexScheduler extends AbstractScheduler {
const cloneStatus = await this.objectClient.getRepositoryGitStatus(repo.uri);
if (
!RepositoryUtils.hasFullyCloned(cloneStatus.cloneProgress) ||
cloneStatus.progress !== 100
cloneStatus.progress !== WorkerReservedProgress.COMPLETED
) {
this.log.info(`Repo ${repo.uri} has not been fully cloned yet or in update. Skip index.`);
return;
@ -55,9 +55,15 @@ export class IndexScheduler extends AbstractScheduler {
this.log.info(
`Current repo revision: ${repo.revision}, indexed revision ${repoIndexStatus.revision}.`
);
if (repoIndexStatus.progress >= 0 && repoIndexStatus.progress < 100) {
if (
repoIndexStatus.progress >= 0 &&
repoIndexStatus.progress < WorkerReservedProgress.COMPLETED
) {
this.log.info(`Repo is still in indexing. Skip index for ${repo.uri}`);
} else if (repoIndexStatus.progress === 100 && repoIndexStatus.revision === repo.revision) {
} else if (
repoIndexStatus.progress === WorkerReservedProgress.COMPLETED &&
repoIndexStatus.revision === repo.revision
) {
this.log.info(`Repo does not change since last index. Skip index for ${repo.uri}.`);
} else {
const payload = {

View file

@ -7,7 +7,12 @@
import moment from 'moment';
import sinon from 'sinon';
import { CloneProgress, CloneWorkerProgress, Repository } from '../../model';
import {
CloneProgress,
CloneWorkerProgress,
Repository,
WorkerReservedProgress,
} from '../../model';
import { RepositoryGitStatusReservedField, RepositoryReservedField } from '../indexer/schema';
import { AnyObject, EsClient } from '../lib/esqueue';
import { Log } from '../log';
@ -95,7 +100,7 @@ test('Next job should not execute when scheduled update time is not current.', d
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getSpy = createGetSpy(100);
const getSpy = createGetSpy(WorkerReservedProgress.COMPLETED);
esClient.get = getSpy;
const updateSpy = sinon.spy();
esClient.update = updateSpy;
@ -186,7 +191,7 @@ test('Next job should execute.', done => {
esClient.search = searchSpy;
// Set up the update and get spies of esClient
const getSpy = createGetSpy(100);
const getSpy = createGetSpy(WorkerReservedProgress.COMPLETED);
esClient.get = getSpy;
const updateSpy = sinon.spy();
esClient.update = updateSpy;

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Repository } from '../../model';
import { Repository, WorkerReservedProgress } from '../../model';
import {
RepositoryIndexName,
RepositoryReservedField,
@ -52,7 +52,7 @@ export class UpdateScheduler extends AbstractScheduler {
if (
cloneStatus.cloneProgress &&
cloneStatus.cloneProgress.isCloned &&
cloneStatus.progress === 100
cloneStatus.progress === WorkerReservedProgress.COMPLETED
) {
const payload = {
uri: repo.uri,

View file

@ -6,7 +6,12 @@
import sinon from 'sinon';
import { CloneWorkerProgress, Repository, WorkerProgress } from '../../model';
import {
CloneWorkerProgress,
Repository,
WorkerProgress,
WorkerReservedProgress,
} from '../../model';
import {
RepositoryDeleteStatusReservedField,
RepositoryGitStatusReservedField,
@ -144,7 +149,7 @@ test('CRUD of Repository Git Status', async () => {
const indexSpy = sinon.spy(esClient, 'index');
const cObj: CloneWorkerProgress = {
uri: repoUri,
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
};
await repoObjectClient.setRepositoryGitStatus(repoUri, cObj);
@ -207,7 +212,7 @@ test('CRUD of Repository LSP Index Status', async () => {
const indexSpy = sinon.spy(esClient, 'index');
const cObj: WorkerProgress = {
uri: repoUri,
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
};
await repoObjectClient.setRepositoryLspIndexStatus(repoUri, cObj);
@ -270,7 +275,7 @@ test('CRUD of Repository Delete Status', async () => {
const indexSpy = sinon.spy(esClient, 'index');
const cObj: CloneWorkerProgress = {
uri: repoUri,
progress: 100,
progress: WorkerReservedProgress.COMPLETED,
timestamp: new Date(),
};
await repoObjectClient.setRepositoryDeleteStatus(repoUri, cObj);