diff --git a/packages/teraslice-messaging/package.json b/packages/teraslice-messaging/package.json index 12068bab134..9e178e1ff99 100644 --- a/packages/teraslice-messaging/package.json +++ b/packages/teraslice-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@terascope/teraslice-messaging", - "version": "0.1.0", + "version": "0.1.1", "publishConfig": { "access": "public" }, @@ -43,13 +43,11 @@ "@types/lodash": "^4.14.116", "@types/nanoid": "^1.2.0", "@types/node": "^10.9.4", - "@types/node-cache": "^4.1.1", "@types/socket.io": "^1.4.38", "@types/socket.io-client": "^1.4.32", "bluebird": "^3.5.2", "debug": "^4.0.1", "nanoid": "^1.2.3", - "node-cache": "^4.2.0", "porty": "^3.1.1", "rimraf": "^2.0.0", "socket.io": "^1.7.4", diff --git a/packages/teraslice-messaging/src/execution-controller/server.ts b/packages/teraslice-messaging/src/execution-controller/server.ts index 41db84d2551..5b07ed71fb0 100644 --- a/packages/teraslice-messaging/src/execution-controller/server.ts +++ b/packages/teraslice-messaging/src/execution-controller/server.ts @@ -109,11 +109,19 @@ export class Server extends core.Server { } onSliceSuccess(fn: core.ClientEventFn) { - this.on('slice:success', fn); + this.on('slice:success', (workerId, payload) => { + _.defer(() => { + fn(workerId, payload); + }); + }); } onSliceFailure(fn: core.ClientEventFn) { - this.on('slice:failure', fn); + this.on('slice:failure', (workerId, payload) => { + _.defer(() => { + fn(workerId, payload); + }); + }); } sendExecutionFinishedToAll(exId: string) { @@ -135,18 +143,11 @@ export class Server extends core.Server { socket.on('worker:slice:complete', this.handleResponse('worker:slice:complete', (msg) => { const workerResponse = msg.payload; const sliceId = _.get(workerResponse, 'slice.slice_id'); - const alreadyCompleted = this.cache.get(`${sliceId}:complete`); - - if (!alreadyCompleted) { - this.cache.set(`${sliceId}:complete`, true); - - _.defer(() => { - if (workerResponse.error) { - this.emit('slice:failure', workerId, workerResponse); - } else { - this.emit('slice:success', workerId, workerResponse); - } - }); + + if (workerResponse.error) { + this.emit('slice:failure', workerId, workerResponse); + } else { + this.emit('slice:success', workerId, workerResponse); } _.pull(this._activeWorkers, workerId); @@ -155,7 +156,6 @@ export class Server extends core.Server { }); return _.pickBy({ - duplicate: alreadyCompleted, recorded: true, slice_id: sliceId, }); diff --git a/packages/teraslice-messaging/src/messenger/core.ts b/packages/teraslice-messaging/src/messenger/core.ts index e96d05687fc..4b40f6ecfe1 100644 --- a/packages/teraslice-messaging/src/messenger/core.ts +++ b/packages/teraslice-messaging/src/messenger/core.ts @@ -1,6 +1,5 @@ import debugFn from 'debug'; import _ from 'lodash'; -import NodeCache from 'node-cache'; import { EventEmitter } from 'events'; import * as i from './interfaces'; import { newMsgId } from '../utils'; @@ -9,7 +8,6 @@ const debug = debugFn('teraslice-messaging:core'); export class Core extends EventEmitter { public closed: boolean = false; - protected cache: NodeCache; protected networkLatencyBuffer: number; protected actionTimeout: number; @@ -27,21 +25,10 @@ export class Core extends EventEmitter { if (!_.isSafeInteger(this.networkLatencyBuffer)) { throw new Error('Messenger requires a valid networkLatencyBuffer'); } - - this.cache = new NodeCache({ - stdTTL: 30 * 60 * 1000, // 30 minutes - checkperiod: 10 * 60 * 1000, // 10 minutes - useClones: false, - }); - } close() { this.closed = true; - - this.cache.flushAll(); - this.cache.close(); - this.removeAllListeners(); } @@ -105,7 +92,7 @@ export class Core extends EventEmitter { return; } - if (!msg.volatile) { + if (!msg.volatile && !this.isClientReady(message.to)) { const remaining = msg.respondBy - Date.now(); await this.waitForClientReady(message.to, remaining); } diff --git a/packages/teraslice-messaging/src/messenger/interfaces.ts b/packages/teraslice-messaging/src/messenger/interfaces.ts index a0333762b33..e0e65325c46 100644 --- a/packages/teraslice-messaging/src/messenger/interfaces.ts +++ b/packages/teraslice-messaging/src/messenger/interfaces.ts @@ -95,7 +95,7 @@ export interface ConnectedClients { } export interface ClientSendFns { - [clientId: string]: (eventName: string, payload?: Payload, options?: SendOptions) => Promise; + [clientId: string]: (eventName: string, message: Message) => Promise; } export interface ClientEventFn { diff --git a/packages/teraslice-messaging/src/messenger/server.ts b/packages/teraslice-messaging/src/messenger/server.ts index 19c11ecb538..6da1263018d 100644 --- a/packages/teraslice-messaging/src/messenger/server.ts +++ b/packages/teraslice-messaging/src/messenger/server.ts @@ -177,63 +177,51 @@ export class Server extends Core { } get connectedClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState(connectedStates); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState(connectedStates)); } get connectedClientCount(): number { - const clients = this.filterClientsByState(connectedStates); - return _.size(clients); + return this.countClientsByState(connectedStates); } get onlineClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState(onlineStates); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState(onlineStates)); } get onlineClientCount(): number { - const clients = this.filterClientsByState(onlineStates); - return _.size(clients); + return this.countClientsByState(onlineStates); } get disconnectedClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState(disconnectedStates); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState(disconnectedStates)); } get disconectedClientCount(): number { - const clients = this.filterClientsByState(disconnectedStates); - return _.size(clients); + return this.countClientsByState(disconnectedStates); } get offlineClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState([i.ClientState.Offline]); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState([i.ClientState.Offline])); } get offlineClientCount(): number { - const clients = this.filterClientsByState([i.ClientState.Offline]); - return _.size(clients); + return this.countClientsByState([i.ClientState.Offline]); } get availableClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState([i.ClientState.Available]); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState([i.ClientState.Available])); } get availableClientCount(): number { - const clients = this.filterClientsByState([i.ClientState.Available]); - return _.size(clients); + return this.countClientsByState([i.ClientState.Available]); } get unavailableClients(): i.ConnectedClient[] { - const clients = this.filterClientsByState(unavailableStates); - return _.cloneDeep(clients); + return _.clone(this.filterClientsByState(unavailableStates)); } get unavailableClientCount(): number { - const clients = this.filterClientsByState(unavailableStates); - return _.size(clients); + return this.countClientsByState(unavailableStates); } onClientOnline(fn: i.ClientEventFn) { @@ -289,12 +277,38 @@ export class Server extends Core { return Promise.all(promises); } - protected async send(clientId: string, eventName: string, payload: i.Payload = {}, options?: i.SendOptions): Promise { + protected async send(clientId: string, eventName: string, payload: i.Payload = {}, options: i.SendOptions = { response: true }): Promise { if (!_.has(this._clientSendFns, clientId)) { throw new Error(`No client found by that id "${clientId}"`); } - return this._clientSendFns[clientId](eventName, payload, options); + if (this.closed) return null; + + if (this.isShuttingDown) { + options.volatile = true; + } + + if (!options.volatile && !this.isClientReady(clientId)) { + await this.waitForClientReady(clientId); + } + + const response = options.response != null ? options.response : true; + + const message: i.Message = { + id: newMsgId(), + respondBy: Date.now() + this.getTimeout(options.timeout), + eventName, + payload, + to: clientId, + from: this.serverName, + volatile: options.volatile, + response, + }; + + const responseMsg = await this._clientSendFns[clientId](eventName, message); + + if (!responseMsg) return null; + return responseMsg as i.Message; } protected getClientMetadataFromSocket(socket: SocketIO.Socket): i.ClientSocketMetadata { @@ -307,6 +321,16 @@ export class Server extends Core { }); } + private countClientsByState(states: i.ClientState[]): number { + let count = 0; + for (const client of Object.values(this._clients)) { + if (states.includes(client.state)) { + count += 1; + } + } + return count; + } + protected updateClientState(clientId: string, update: i.UpdateClientState): boolean { const client = this._clients[clientId]; if (!client) { @@ -326,15 +350,6 @@ export class Server extends Core { } const updatedAt = new Date(); - const debugObj = _.pickBy({ - payload: update.payload, - error: update.payload, - socketId: update.socketId, - updatedAt, - }); - - debug(`${clientId} is being updated from ${currentState} to ${update.state}`, debugObj); - this._clients[clientId].state = update.state; this._clients[clientId].updatedAt = updatedAt; @@ -384,6 +399,20 @@ export class Server extends Core { } this.emit(`client:${update.state}`, clientId, update.error); + + // cleanup socket and such + const { socketId } = this._clients[clientId]; + + if (this.server.sockets.sockets[socketId]) { + try { + this.server.sockets.sockets[socketId].removeAllListeners(); + this.server.sockets.sockets[socketId].disconnect(true); + } catch (err) { + debug('error cleaning up socket when going offline', err); + } + delete this.server.sockets.sockets[socketId]; + } + delete this._clientSendFns[clientId]; return true; @@ -424,36 +453,10 @@ export class Server extends Core { const client = this.ensureClient(socket); const { clientId } = client; - this._clientSendFns[clientId] = async (eventName, payload = {}, options: i.SendOptions = { response: true }) => { - if (this.closed) return null; - - if (this.isShuttingDown) { - options.volatile = true; - } - - if (!options.volatile) { - await this.waitForClientReady(clientId); - } - - const response = options.response != null ? options.response : true; - - const message: i.Message = { - id: newMsgId(), - respondBy: Date.now() + this.getTimeout(options.timeout), - eventName, - payload, - to: clientId, - from: this.serverName, - volatile: options.volatile, - response, - }; - - const responseMsg = await new Promise((resolve, reject) => { + this._clientSendFns[clientId] = (eventName, message: i.Message) => { + return new Promise((resolve, reject) => { socket.emit(eventName, message, this.handleSendResponse(message, resolve, reject)); }); - - if (!responseMsg) return null; - return responseMsg as i.Message; }; socket.on('error', (err: Error|string) => { diff --git a/packages/teraslice-messaging/src/utils/index.ts b/packages/teraslice-messaging/src/utils/index.ts index 826a2018ec6..510f082b9fc 100644 --- a/packages/teraslice-messaging/src/utils/index.ts +++ b/packages/teraslice-messaging/src/utils/index.ts @@ -1,15 +1,10 @@ import os from 'os'; import _ from 'lodash'; import url from 'url'; -import nanoid from 'nanoid/generate'; +import nanoid from 'nanoid'; -export function newMsgId(lowerCase: boolean = false, length: number = 15): string { - let characters = '-0123456789abcdefghijklmnopqrstuvwxyz'; - if (!lowerCase) { - characters += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - } - const id = _.trim(nanoid(characters, length), '-'); - return _.padEnd(id, length, 'abcdefghijklmnopqrstuvwxyz'); +export function newMsgId(): string { + return nanoid(); } export function formatURL(hostname = os.hostname(), port: number): string { diff --git a/packages/teraslice-messaging/test/execution-controller-spec.ts b/packages/teraslice-messaging/test/execution-controller-spec.ts index a92cb0a70d7..e52ddcae73d 100644 --- a/packages/teraslice-messaging/test/execution-controller-spec.ts +++ b/packages/teraslice-messaging/test/execution-controller-spec.ts @@ -206,47 +206,6 @@ describe('ExecutionController', () => { }); }); }); - - describe('when the slice is double recorded', () => { - it('should respond with a duplicate message', () => { - const onSliceSuccessFn = jest.fn(); - server.onSliceSuccess(onSliceSuccessFn); - const slice = { - slice: { - slicer_order: 0, - slicer_id: 1, - request: {}, - slice_id: 'duplicate-slice-complete', - _created: 'hello' - }, - analytics: { - time: [], - memory: [], - size: [] - }, - }; - - return client.sendSliceComplete(slice) - .then(() => client.sendSliceComplete(slice)) - .then(async (msg) => { - await bluebird.delay(100); - return msg; - }) - .then((msg) => { - if (msg == null) { - expect(msg).not.toBeNull(); - return; - } - expect(msg.payload).toEqual({ - slice_id: 'duplicate-slice-complete', - recorded: true, - duplicate: true, - }); - expect(onSliceSuccessFn).toHaveBeenCalledTimes(1); - expect(server.queue.exists('workerId', workerId)).toBeFalse(); - }); - }); - }); }); describe('when receiving finished', () => { diff --git a/packages/teraslice/cluster-service.js b/packages/teraslice/cluster-service.js index 4f598bc7879..730ab10ea84 100644 --- a/packages/teraslice/cluster-service.js +++ b/packages/teraslice/cluster-service.js @@ -5,8 +5,6 @@ const Promise = require('bluebird'); const get = require('lodash/get'); const { shutdownHandler } = require('./lib/workers/helpers/worker-shutdown'); -const makeClusterMaster = require('./lib/cluster/cluster_master'); -const makeAssetsService = require('./lib/cluster/services/assets'); const makeTerafoundationContext = require('./lib/workers/context/terafoundation-context'); class Service { @@ -27,9 +25,11 @@ class Service { this.logger.trace(`Initializing ${assignment}`); if (assignment === 'cluster_master') { - this.instance = makeClusterMaster(this.context); + // require this here so node doesn't have load extra code into memory + this.instance = require('./lib/cluster/cluster_master')(this.context); } else if (assignment === 'assets_service') { - this.instance = makeAssetsService(this.context); + // require this here so node doesn't have load extra code into memory + this.instance = require('./lib/cluster/services/assets')(this.context); } await this.instance.initialize(); diff --git a/packages/teraslice/lib/processors/delay.js b/packages/teraslice/lib/processors/delay.js new file mode 100644 index 00000000000..277dd88e44e --- /dev/null +++ b/packages/teraslice/lib/processors/delay.js @@ -0,0 +1,25 @@ +'use strict'; + +/* eslint-disable no-unused-vars */ + +const Promise = require('bluebird'); + +function newProcessor(context, opConfig, executionConfig) { + return data => Promise.delay(opConfig.delay).then(() => data); +} + +function schema() { + return { + ms: { + default: 100, + doc: 'Time delay in milliseconds', + format: 'Number' + } + }; +} + + +module.exports = { + newProcessor, + schema +}; diff --git a/packages/teraslice/lib/workers/execution-controller/index.js b/packages/teraslice/lib/workers/execution-controller/index.js index 5bc22b98586..01453671bf9 100644 --- a/packages/teraslice/lib/workers/execution-controller/index.js +++ b/packages/teraslice/lib/workers/execution-controller/index.js @@ -77,6 +77,7 @@ class ExecutionController { this.slicersReady = false; this.slicesEnqueued = 0; this.slicers = []; + this.scheduler = []; this.slicersDoneCount = 0; this.totalSlicers = 0; this.pendingSlices = 0; @@ -175,7 +176,7 @@ class ExecutionController { this.server.onSliceSuccess((workerId, response) => { this.executionAnalytics.increment('processed'); - if (this.collectAnalytics) { + if (this.collectAnalytics && response.analytics) { this.slicerAnalytics.addStats(response.analytics); } @@ -315,6 +316,8 @@ class ExecutionController { await this._waitForExecutionFinished(); + clearInterval(this.processInterval); + if (this.recover) { try { await this.recover.shutdown(); @@ -410,99 +413,88 @@ class ExecutionController { this.client.sendAvailable(), ]); + this.scheduling = false; + this.dispatching = false; + try { - await this._processLoop(); + await new Promise((resolve) => { + this.processInterval = setInterval(() => { + if (this.isExecutionDone || this.isShuttingDown) { + clearInterval(this.processInterval); + resolve(); + return; + } + + if (this.isPaused) return; + + if (!this.isDoneProcessing) { + this._processLoop(); + } else { + clearInterval(this.processInterval); + resolve(); + } + }, 20); + }); } catch (err) { this.logger.error('Error processing slices', err); + } finally { + clearInterval(this.processInterval); } if (this.isDoneProcessing) { + await this._waitForPendingSlices(); this.logger.debug(`execution ${this.exId} is done processing slices`); } } - async _processLoop() { - if (this.isExecutionDone || this.isShuttingDown) { - return null; + _processLoop() { + if (!this.scheduling && this.slicersReady && !this.slicersDone) { + this._scheduleSlices(); } - await Promise.delay(0); - - if (this.isPaused) { - this.logger.debug('execution is paused, wait for resume...'); - const found = await this.client.onceWithTimeout('execution:resume', 1000); - if (found == null) { - return this._processLoop(); - } + if (!this.dispatching && this.slicerQueue.size() > 0) { + this._dispatchSlices(); } - let scheduling = false; - const schedule = async () => { - if (scheduling) return; - if (!this.slicersReady || this.slicersDone) return; - - scheduling = true; - await this._scheduleSlices(); - scheduling = false; - }; - - let dispatching = false; - const dispatch = async () => { - if (dispatching) return; - if (!this.slicerQueue.size()) return; - - dispatching = true; - await this._dispatchSlices(); - dispatching = false; - }; - - await Promise.race([ - schedule(), - dispatch() - ]); - - if (!dispatching && !scheduling && this.slicersDone && !this.slicerQueue.size()) { - await this._waitForPendingSlices(); + if (!this.dispatching && !this.scheduling && this.slicersDone && !this.slicerQueue.size()) { this.isDoneProcessing = true; - return true; } - - return this._processLoop(); } async _scheduleSlices() { - const remaining = this.executionContext.queueLength - this.slicerQueue.size(); - if (remaining <= 0) return; + const needsMoreSlices = this.slicerQueue.size() < this.executionContext.queueLength; + if (!needsMoreSlices) return; - // schedule up to 3 slices at once - const count = _.min([remaining, 3]); + this.scheduling = true; - const schedule = async () => { - const needsMoreSlices = this.slicerQueue.size() < this.executionContext.queueLength; - if (!needsMoreSlices) return; + try { + await Promise.race(this._runScheduler()); + } catch (err) { + this.logger.error('Failure scheduling slice', err); + } - try { - await Promise.map(this.scheduler, slicerFn => slicerFn()); - } catch (err) { - this.logger.error('Failure scheduling slice', err); - } - }; + this.scheduling = false; + } - // this has to be run in series because the slicers can only be run serially - await Promise.each(_.times(count), schedule); + _runScheduler() { + return _.map(this.scheduler, fn => fn()); } async _dispatchSlices() { + this.dispatching = true; + + const reenqueueSlices = []; + const promises = []; + // dispatch only up to 10 at time but if there are less work available const count = _.min([this.server.workerQueueSize, this.slicerQueue.size(), 10]); - const reenqueueSlices = []; - await Promise.all(_.times(count, async () => { - if (!this.slicerQueue.size()) return; - if (!this.server.workerQueueSize) return; + for (let i = 0; i < count; i += 1) { + if (!this.slicerQueue.size()) break; + if (!this.server.workerQueueSize) break; const slice = this.slicerQueue.dequeue(); - if (!slice) return; // this probably won't happen but lets make sure + if (!slice) break; this.pendingSlices += 1; @@ -510,16 +502,11 @@ class ExecutionController { if (!workerId) { reenqueueSlices.push(slice); } else { - const dispatched = await this.server.dispatchSlice(slice, workerId); - - if (dispatched) { - this.logger.debug(`dispatched slice ${slice.slice_id} to worker ${workerId}`); - } else { - reenqueueSlices.push(slice); - this.logger.warn(`worker "${workerId}" is not available to process slice ${slice.slice_id}`); - } + promises.push(this._dispatchSlice(slice, workerId, reenqueueSlices)); } - })); + } + + await Promise.all(promises); if (reenqueueSlices.length > 0) { this.logger.debug(`re-enqueing ${reenqueueSlices.length} slices because they were unable to be dispatched`); @@ -529,7 +516,22 @@ class ExecutionController { }); } - this.executionAnalytics.set('queued', this.slicerQueue.size()); + if (promises.length > 0) { + this.executionAnalytics.set('queued', this.slicerQueue.size()); + } + + this.dispatching = false; + } + + async _dispatchSlice(slice, workerId, reenqueueSlices) { + const dispatched = await this.server.dispatchSlice(slice, workerId); + + if (dispatched) { + this.logger.debug(`dispatched slice ${slice.slice_id} to worker ${workerId}`); + } else { + reenqueueSlices.push(slice); + this.logger.warn(`worker "${workerId}" is not available to process slice ${slice.slice_id}`); + } } async _slicerInit() { @@ -557,7 +559,7 @@ class ExecutionController { ); }, retryOptions); - this.scheduler = await this._registerSlicers(this.slicers); + await this._registerSlicers(this.slicers); } async _registerSlicers(slicers = [], isRecovery = false) { @@ -570,7 +572,8 @@ class ExecutionController { this.totalSlicers += _.size(slicers); this.executionAnalytics.set('slicers', slicers.length); - const scheduler = slicers.map((slicerFn, index) => this._registerSlicerFn(slicerFn, index)); + this.scheduler.length = 0; + this.scheduler = slicers.map((slicerFn, index) => this._registerSlicerFn(slicerFn, index)); // Recovery has it own error listening logic internally if (!isRecovery) { @@ -585,11 +588,9 @@ class ExecutionController { this.logger.debug(`registered ${_.size(slicers)} slicers`); this.slicersReady = true; - - return scheduler; } - async _registerSlicerFn(slicerFn, slicerId) { + _registerSlicerFn(slicerFn, slicerId) { let hasCompleted = false; let isProcessing = false; let slicerOrder = 0; @@ -699,8 +700,7 @@ class ExecutionController { this.slicers = await this.recover.newSlicer(); - this.scheduler = await this._registerSlicers(this.slicers, true); - this.slicersReady = true; + await this._registerSlicers(this.slicers, true); } async _waitForRecovery() { diff --git a/packages/teraslice/package.json b/packages/teraslice/package.json index d3fff7a6a41..873e31bbdb5 100644 --- a/packages/teraslice/package.json +++ b/packages/teraslice/package.json @@ -1,6 +1,6 @@ { "name": "teraslice", - "version": "0.41.2", + "version": "0.41.3", "description": "Slice and dice your Elasticsearch data", "bin": "service.js", "main": "index.js", @@ -38,7 +38,7 @@ "@terascope/error-parser": "^1.0.0", "@terascope/job-components": "^0.4.1", "@terascope/queue": "^1.1.3", - "@terascope/teraslice-messaging": "^0.1.0", + "@terascope/teraslice-messaging": "^0.1.1", "async-mutex": "^0.1.3", "barbe": "^3.0.14", "bluebird": "^3.5.2", diff --git a/packages/teraslice/worker-service.js b/packages/teraslice/worker-service.js index a6927ef650c..8f0e45ab025 100644 --- a/packages/teraslice/worker-service.js +++ b/packages/teraslice/worker-service.js @@ -7,8 +7,6 @@ const _ = require('lodash'); const get = require('lodash/get'); const { shutdownHandler } = require('./lib/workers/helpers/worker-shutdown'); const { safeDecode } = require('./lib/utils/encoding_utils'); -const Worker = require('./lib/workers/worker'); -const ExecutionController = require('./lib/workers/execution-controller'); const ExecutionContext = require('./lib/workers/context/execution-context'); const makeTerafoundationContext = require('./lib/workers/context/terafoundation-context'); @@ -50,8 +48,14 @@ class Service { await this.executionContext.initialize(); if (assignment === 'worker') { + // require this here so node doesn't have load extra code into memory + const Worker = require('./lib/workers/worker'); + this.instance = new Worker(this.context, this.executionContext); } else if (assignment === 'execution_controller') { + // require this here so node doesn't have load extra code into memory + const ExecutionController = require('./lib/workers/execution-controller'); + this.instance = new ExecutionController(this.context, this.executionContext); } diff --git a/yarn.lock b/yarn.lock index 0c25109e40f..c2ec0e4d59b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -617,12 +617,6 @@ dependencies: "@types/node" "*" -"@types/node-cache@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/node-cache/-/node-cache-4.1.1.tgz#c435974571cdb3b44fef91de95565fb6656cc3b6" - dependencies: - "@types/node" "*" - "@types/node@*": version "10.5.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.5.8.tgz#6f14ccecad1d19332f063a6a764f8907801fece0"