Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions src-electron/db/query-session-notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,8 @@ async function searchNotificationByMessageAndDelete(db, sessionId, message) {
[sessionId, message]
)
if (rows && rows.length > 0) {
let ids = rows.map((row) => row.NOTICE_ID)
let deleteResponses = []
for (let id of ids) {
let response = await deleteNotification(db, id)
deleteResponses.push(response)
}
return deleteResponses
const ids = rows.map((row) => row.NOTICE_ID)
return Promise.all(ids.map((id) => deleteNotification(db, id)))
}
return false
}
Expand Down Expand Up @@ -212,9 +207,11 @@ async function setNotificationOnFeatureChange(db, sessionId, result) {
outdatedWarningPatterns
} = result
if (disableChange) {
for (let message of warningMessage) {
await setWarningIfMessageNotExists(db, sessionId, message)
}
await Promise.all(
warningMessage.map((message) =>
setWarningIfMessageNotExists(db, sessionId, message)
)
)
Comment on lines +210 to +214
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Parallelizing setWarningIfMessageNotExists introduces a race condition. Since this function performs a "check-then-insert" operation without a database lock or transaction, concurrent calls for the same message (or duplicate messages in the array) can result in multiple identical notifications being created. It is safer to process these sequentially to maintain the uniqueness check.

    for (const message of warningMessage) {
      await setWarningIfMessageNotExists(db, sessionId, message)
    }

} else {
await deleteNotificationWithPatterns(db, sessionId, outdatedWarningPatterns)
if (displayWarning) {
Expand Down Expand Up @@ -288,13 +285,8 @@ async function deleteNotificationWithPatterns(db, sessionId, patterns) {

let rows = await dbApi.dbAll(db, query, params)
if (rows && rows.length > 0) {
let ids = rows.map((row) => row.NOTICE_ID)
let deleteResponses = []
for (let id of ids) {
let response = await deleteNotification(db, id)
deleteResponses.push(response)
}
return deleteResponses
const ids = rows.map((row) => row.NOTICE_ID)
return Promise.all(ids.map((id) => deleteNotification(db, id)))
}
return false
}
Expand Down
56 changes: 32 additions & 24 deletions src-electron/generator/generation-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -596,9 +596,13 @@ async function loadTemplates(
templateData: []
}
if (genTemplatesJsonArray != null && genTemplatesJsonArray.length > 0) {
for (let jsonFile of genTemplatesJsonArray) {
if (jsonFile == null || jsonFile == '') continue
let ctx = await loadGenTemplatesJsonFile(db, jsonFile)
const filesToLoad = genTemplatesJsonArray.filter(
(f) => f != null && f !== ''
)
const loadResults = await Promise.all(
filesToLoad.map((jsonFile) => loadGenTemplatesJsonFile(db, jsonFile))
)
Comment on lines +602 to +604
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While Promise.all is used here to parallelize template loading, the underlying loadGenTemplatesJsonFile function is constrained by a global database transaction lock (dbApi.dbBeginTransaction). This results in serialized execution at the database level, with concurrent calls polling the lock every 100ms, which negates the performance benefits of parallelization for the database-heavy portions of the task.

for (const ctx of loadResults) {
if (ctx.error) {
if (options.failOnLoadingError) globalCtx.error = ctx.error
} else {
Expand Down Expand Up @@ -761,28 +765,32 @@ async function generateAllTemplates(
hb: hb
}

for (let pkg of packages) {
let outputOptions = await queryPackage.selectAllOptionsValues(
genResult.db,
pkg.id,
dbEnum.packageOptionCategory.outputOptions
)
outputOptions.forEach((opt) => {
if (opt.optionCode == 'iterator') {
pkg.iterator = opt.optionLabel
}
})
let generatorOptions = await queryPackage.selectAllOptionsValues(
genResult.db,
pkg.id,
dbEnum.packageOptionCategory.generator
)
generatorOptions.forEach((opt) => {
if (opt.optionCode == 'static') {
pkg.static = opt.optionLabel
}
await Promise.all(
packages.map(async (pkg) => {
const [outputOptions, generatorOptions] = await Promise.all([
queryPackage.selectAllOptionsValues(
genResult.db,
pkg.id,
dbEnum.packageOptionCategory.outputOptions
),
queryPackage.selectAllOptionsValues(
genResult.db,
pkg.id,
dbEnum.packageOptionCategory.generator
)
])
outputOptions.forEach((opt) => {
if (opt.optionCode == 'iterator') {
pkg.iterator = opt.optionLabel
}
})
generatorOptions.forEach((opt) => {
if (opt.optionCode == 'static') {
pkg.static = opt.optionLabel
}
})
})
}
)
// First extract overridePath if one exists, as we need to
// pass it to the generation.
packages.forEach((singlePkg) => {
Expand Down
26 changes: 17 additions & 9 deletions src-electron/generator/helper-endpointconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -953,22 +953,30 @@ async function collectAttributes(

c.attributes.sort(zclUtil.attributeComparator)

// Resolve all attribute default values in parallel, then process in order.
const attributeDefaultValues = await Promise.all(
c.attributes.map((a) =>
determineAttributeDefaultValue(
a.defaultValue,
a.type,
a.typeSize,
a.isNullable,
db,
sessionId
)
)
)

// Go over all the attributes in the endpoint and add them to the list.
for (let a of c.attributes) {
for (let attrIdx = 0; attrIdx < c.attributes.length; attrIdx++) {
let a = c.attributes[attrIdx]
let attributeDefaultValue = attributeDefaultValues[attrIdx]
// typeSize is the size of a buffer needed to hold the attribute, if
// that's known.
let typeSize = a.typeSize
// defaultSize is the size of the attribute in the readonly defaults
// store.
let defaultSize = typeSize
let attributeDefaultValue = await determineAttributeDefaultValue(
a.defaultValue,
a.type,
typeSize,
a.isNullable,
db,
sessionId
)
// Various types store the length of the actual content in bytes.
// For those, we can size the default storage to be just big enough for
// the actual default value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,21 @@ async function loadEndpoints() {

const endpoints = await queryEndpoint.selectAllEndpoints(db, sessionId);

// Selection is one by one since existing API does not seem to provide
// linkage between cluster and what endpoint it belongs to.
//
// TODO: there should be a better way
for (const endpoint of endpoints) {
const endpointClusters =
await queryEndpointType.selectAllClustersDetailsFromEndpointTypes(db, [
{ endpointTypeId: endpoint.endpointTypeRef }
]);
result.push({
...endpoint,
clusters: endpointClusters.filter((c) => c.enabled == 1)
});
}
// Fetch cluster details per endpoint in parallel (API does not provide
// linkage between cluster and endpoint; one query per endpoint type).
const endpointResults = await Promise.all(
endpoints.map(async (endpoint) => {
const endpointClusters =
await queryEndpointType.selectAllClustersDetailsFromEndpointTypes(db, [
{ endpointTypeId: endpoint.endpointTypeRef }
]);
return {
...endpoint,
clusters: endpointClusters.filter((c) => c.enabled == 1)
};
})
);
result.push(...endpointResults);

return result;
}
Expand Down
44 changes: 24 additions & 20 deletions src-electron/generator/template-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,25 @@ async function produceIterativeContent(
db,
sessionId
)
let res = []
for (let it of iterationArray) {
options.overrideKey = util.patternFormat(singleTemplatePkg.category, it)
options.initialContext = it
let r = await produceContent(
hb,
metaInfo,
db,
sessionId,
singleTemplatePkg,
genTemplateJsonPackage,
options
)
res.push(...r)
}
return res
let resultArrays = await Promise.all(
iterationArray.map((it) => {
const iterOptions = {
...options,
overrideKey: util.patternFormat(singleTemplatePkg.category, it),
initialContext: it
}
return produceContent(
hb,
metaInfo,
db,
sessionId,
singleTemplatePkg,
genTemplateJsonPackage,
iterOptions
)
})
)
return resultArrays.flat()
}

/**
Expand Down Expand Up @@ -209,10 +212,11 @@ async function produceContent(
)
)
])
// Render deferred blocks
for (let deferredBlock of context.global.deferredBlocks) {
content += await deferredBlock(context)
}
// Render deferred blocks (parallel; order preserved by Promise.all)
const deferredParts = await Promise.all(
context.global.deferredBlocks.map((block) => block(context))
)
content += deferredParts.join('')
} catch (error) {
// Log the error and throw it
notification.setNotification(
Expand Down
21 changes: 11 additions & 10 deletions src-electron/main-process/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,21 +907,22 @@ async function generateSingleFile(
if (usedTemplatePackageIds.length === 0) {
usedTemplatePackageIds = [templatePackageId]
}
let genResults = []
for (let i = 0; i < usedTemplatePackageIds.length; i++) {
let genResult = await generatorEngine.generateAndWriteFiles(
db,
sessionId,
usedTemplatePackageIds[i],
output,
options
const genResults = await Promise.all(
usedTemplatePackageIds.map((templatePackageId) =>
generatorEngine.generateAndWriteFiles(
db,
sessionId,
templatePackageId,
output,
options
)
)

)
for (const genResult of genResults) {
if (genResult.hasErrors) {
console.log(JSON.stringify(genResult.errors))
throw new Error(`Generation failed: ${zapFile}`)
}
genResults.push(genResult)
}

if (options.upgradeZapFile && isZapFileUpgradeNeeded) {
Expand Down
Loading