From 5e9927cd00fa484826d648007e855b43855ace89 Mon Sep 17 00:00:00 2001 From: yugasun Date: Fri, 3 Apr 2020 12:30:36 +0800 Subject: [PATCH] fix: remove components respect dependencies Close #16 --- serverless.js | 26 +++++++++++++++++-- utils.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/serverless.js b/serverless.js index baf6e49..9be2392 100644 --- a/serverless.js +++ b/serverless.js @@ -7,6 +7,7 @@ const { setDependencies, createGraph, executeGraph, + executeGraphRemove, syncState, getOutputs, createCustomMethodHandler @@ -71,11 +72,32 @@ class Template extends Component { return outputs } - async remove() { + async remove(inputs = {}) { this.context.status('Removing') this.context.debug('Flushing template state and removing all components.') - await syncState({}, this) + + const template = await getTemplate(inputs) + + this.context.debug(`Resolving the template's static variables.`) + + const resolvedTemplate = resolveTemplate(template) + + this.context.debug('Collecting components from the template.') + + const allComponents = await getAllComponents(resolvedTemplate) + + this.context.debug(`Analyzing the template's components dependencies.`) + + const allComponentsWithDependencies = setDependencies(allComponents) + + this.context.debug(`Creating the template's components graph.`) + + const graph = createGraph(allComponentsWithDependencies) + + this.context.debug(`Executing the template's components graph remove.`) + + await executeGraphRemove(allComponentsWithDependencies, graph, this) // todo should we return the removed components outputs here?! return {} diff --git a/utils.js b/utils.js index 791399e..f4ffb8f 100644 --- a/utils.js +++ b/utils.js @@ -5,6 +5,14 @@ const traverse = require('traverse') const { utils } = require('@serverless/core') const newMetric = require('@serverless/component-metrics') +function sleep(ms) { + return new Promise((res) => { + setTimeout(() => { + res(true) + }, ms) + }) +} + const getComponentMetric = async (componentPath, componentMethod = 'default', instance) => { const metric = newMetric() metric.componentMethod(componentMethod) @@ -341,6 +349,66 @@ const syncState = async (allComponents, instance) => { await instance.save() } +const executeGraphRemove = async (allComponents, graph, instance) => { + const leaves = graph.sinks() + if (isEmpty(leaves)) { + return allComponents + } + + const preorderGraph = (gp, res = []) => { + const lvs = gp.sinks() + if (lvs && lvs.length > 0) { + lvs.forEach((lv) => { + if (res.indexOf(lv) === -1) { + res.push(lv) + } + gp.removeNode(lv) + }) + + return preorderGraph(gp, res) + } + return res.reverse() + } + + const componets = preorderGraph(graph) + + for (const alias of componets) { + let loopTime = 5 + const fn = async () => { + const component = await instance.load(instance.state.components[alias], alias) + instance.context.status('Removing', alias) + const metric = await getComponentMetric(instance.state.components[alias], 'remove', instance) + + // try remove, even through remove main node first, + // but the network status may need time to feel it. + // So should try to remove for loop + const tryRemove = async () => { + try { + await component.remove() + } catch (e) { + await metric.publish() + // on error, publish error metric + if (loopTime <= 0) { + metric.componentError(e.message) + throw e + } else { + await sleep(5000) + loopTime-- + await tryRemove() + } + } + } + + await tryRemove() + await metric.publish() + } + + await fn() + } + + await instance.save() +} + const createCustomMethodHandler = (instance, method) => { return async ({ component, template, ...inputs }) => { let components = Array.isArray(component) ? component : [component] @@ -400,6 +468,7 @@ module.exports = { setDependencies, createGraph, executeGraph, + executeGraphRemove, syncState, getOutputs, createCustomMethodHandler