screwdriver-queue-service 6.0.1 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/plugins/helper.js
CHANGED
|
@@ -85,12 +85,15 @@ function formatOptions(method, url, token, json, retryStrategyFn) {
|
|
|
85
85
|
* Update build status
|
|
86
86
|
* @method updateBuildStatus
|
|
87
87
|
* @param {Object} updateConfig build config of the job
|
|
88
|
+
* @param {Object} updateConfig.buildConfig Optional buildConfig to avoid Redis lookup
|
|
88
89
|
* @return {Promise}
|
|
89
90
|
*/
|
|
90
91
|
async function updateBuildStatus(updateConfig) {
|
|
91
|
-
const { redisInstance, status, statusMessage, buildId } = updateConfig;
|
|
92
|
+
const { redisInstance, status, statusMessage, buildId, buildConfig: providedConfig } = updateConfig;
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
// Use provided buildConfig if available, otherwise fetch from Redis
|
|
95
|
+
const buildConfig =
|
|
96
|
+
providedConfig || (await redisInstance.hget(`${queuePrefix}buildConfigs`, buildId).then(JSON.parse));
|
|
94
97
|
|
|
95
98
|
if (!buildConfig) return null;
|
|
96
99
|
|
|
@@ -118,11 +121,15 @@ async function updateBuildStatus(updateConfig) {
|
|
|
118
121
|
* @param {String} stepConfig.buildId
|
|
119
122
|
* @param {String} stepConfig.stepName
|
|
120
123
|
* @param {Integer} stepConfig.code
|
|
124
|
+
* @param {Object} stepConfig.buildConfig Optional buildConfig to avoid Redis lookup
|
|
121
125
|
* @return {Promise} response body or error
|
|
122
126
|
*/
|
|
123
127
|
async function updateStepStop(stepConfig) {
|
|
124
|
-
const { redisInstance, buildId, stepName, code } = stepConfig;
|
|
125
|
-
|
|
128
|
+
const { redisInstance, buildId, stepName, code, buildConfig: providedConfig } = stepConfig;
|
|
129
|
+
|
|
130
|
+
// Use provided buildConfig if available, otherwise fetch from Redis
|
|
131
|
+
const buildConfig =
|
|
132
|
+
providedConfig || (await redisInstance.hget(`${queuePrefix}buildConfigs`, buildId).then(JSON.parse));
|
|
126
133
|
|
|
127
134
|
// if buildConfig got deleted already, do not update
|
|
128
135
|
if (!buildConfig) return null;
|
|
@@ -149,11 +156,15 @@ async function updateStepStop(stepConfig) {
|
|
|
149
156
|
* @param {Object} stepConfig
|
|
150
157
|
* @param {Object} stepConfig.redisInstance
|
|
151
158
|
* @param {String} stepConfig.buildId
|
|
159
|
+
* @param {Object} stepConfig.buildConfig Optional buildConfig to avoid Redis lookup
|
|
152
160
|
* @return {Promise} active step or error
|
|
153
161
|
*/
|
|
154
162
|
async function getCurrentStep(stepConfig) {
|
|
155
|
-
const { redisInstance, buildId } = stepConfig;
|
|
156
|
-
|
|
163
|
+
const { redisInstance, buildId, buildConfig: providedConfig } = stepConfig;
|
|
164
|
+
|
|
165
|
+
// Use provided buildConfig if available, otherwise fetch from Redis
|
|
166
|
+
const buildConfig =
|
|
167
|
+
providedConfig || (await redisInstance.hget(`${queuePrefix}buildConfigs`, buildId).then(JSON.parse));
|
|
157
168
|
|
|
158
169
|
// if buildConfig got deleted already, do not update
|
|
159
170
|
if (!buildConfig) return null;
|
|
@@ -134,11 +134,18 @@ class BlockedBy extends NodeResque.Plugin {
|
|
|
134
134
|
*/
|
|
135
135
|
async reEnqueue(buildConfig, decision) {
|
|
136
136
|
const { buildId } = buildConfig;
|
|
137
|
-
const
|
|
137
|
+
const blockedByArray = decision.blockedBy || [];
|
|
138
|
+
const runningBuildId = decision.runningBuildId;
|
|
139
|
+
|
|
140
|
+
const allBlockingBuilds = [...blockedByArray];
|
|
141
|
+
|
|
142
|
+
if (runningBuildId) {
|
|
143
|
+
allBlockingBuilds.push(runningBuildId);
|
|
144
|
+
}
|
|
138
145
|
|
|
139
146
|
let statusMessage = 'Blocked by these running build(s): ';
|
|
140
147
|
|
|
141
|
-
statusMessage +=
|
|
148
|
+
statusMessage += allBlockingBuilds
|
|
142
149
|
.map(blockingBuildId => `<a href="/builds/${blockingBuildId}">${blockingBuildId}</a>`)
|
|
143
150
|
.join(', ');
|
|
144
151
|
|
|
@@ -177,7 +184,8 @@ class BlockedBy extends NodeResque.Plugin {
|
|
|
177
184
|
redisInstance: this.queueObject.connection.redis,
|
|
178
185
|
buildId,
|
|
179
186
|
status: 'COLLAPSED',
|
|
180
|
-
statusMessage: newestBuild ? `Collapsed to build: ${newestBuild}` : 'Collapsed'
|
|
187
|
+
statusMessage: newestBuild ? `Collapsed to build: ${newestBuild}` : 'Collapsed',
|
|
188
|
+
buildConfig
|
|
181
189
|
})
|
|
182
190
|
.catch(err => {
|
|
183
191
|
logger.error(`Failed to update build status to COLLAPSED for build:${buildId}:${err}`);
|
|
@@ -44,6 +44,32 @@ local waitingBuilds = redis.call("LRANGE", waitingKey, 0, -1)
|
|
|
44
44
|
local lastRunningBuildId = redis.call("GET", lastRunningKey)
|
|
45
45
|
local deleteKeyExists = redis.call("EXISTS", deleteKey)
|
|
46
46
|
|
|
47
|
+
-- Early exit: If buildConfig doesn't exist, build was already processed/deleted
|
|
48
|
+
-- Clean up any remaining state and abort
|
|
49
|
+
if not buildConfig then
|
|
50
|
+
redis.log(redis.LOG_WARNING, string.format(
|
|
51
|
+
"[startBuild] BUILD_CONFIG_MISSING: build=%s job=%s - buildConfig deleted externally",
|
|
52
|
+
buildId, jobId
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
redis.call("LREM", waitingKey, 0, buildId)
|
|
56
|
+
redis.call("DEL", deleteKey)
|
|
57
|
+
|
|
58
|
+
-- If this build is somehow marked as running, clean that up too
|
|
59
|
+
if runningBuildId and tostring(runningBuildId) == tostring(buildId) then
|
|
60
|
+
redis.call("DEL", runningKey)
|
|
61
|
+
end
|
|
62
|
+
if lastRunningBuildId and tostring(lastRunningBuildId) == tostring(buildId) then
|
|
63
|
+
redis.call("DEL", lastRunningKey)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return cjson.encode({
|
|
67
|
+
action = "ABORT",
|
|
68
|
+
reason = "BUILD_CONFIG_MISSING",
|
|
69
|
+
buildId = buildId
|
|
70
|
+
})
|
|
71
|
+
end
|
|
72
|
+
|
|
47
73
|
-- Check if build was aborted (deleteKey exists)
|
|
48
74
|
local isAborted = (deleteKeyExists == 1)
|
|
49
75
|
|
|
@@ -58,25 +84,19 @@ end
|
|
|
58
84
|
|
|
59
85
|
-- Check which dependencies are currently running
|
|
60
86
|
local runningBuilds = {}
|
|
87
|
+
local runningJobIds = {}
|
|
61
88
|
for _, depJobId in ipairs(dependencies) do
|
|
62
89
|
local depRunningBuild = redis.call("GET", runningJobsPrefix .. tostring(depJobId))
|
|
63
90
|
if depRunningBuild then
|
|
64
91
|
table.insert(runningBuilds, depRunningBuild)
|
|
92
|
+
table.insert(runningJobIds, depJobId)
|
|
65
93
|
end
|
|
66
94
|
end
|
|
67
95
|
|
|
68
96
|
-- Helper: Check if blocked by dependencies
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
for _, runningId in ipairs(running) do
|
|
73
|
-
if tostring(dep) == tostring(runningId) then
|
|
74
|
-
table.insert(blockedByBuilds, dep)
|
|
75
|
-
break
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
return #blockedByBuilds > 0, blockedByBuilds
|
|
97
|
+
-- Returns: isBlocked (boolean), blockedByBuilds (array of buildIds that are blocking)
|
|
98
|
+
local function isBlockedByDependencies(runningBuildIds)
|
|
99
|
+
return #runningBuildIds > 0, runningBuildIds
|
|
80
100
|
end
|
|
81
101
|
|
|
82
102
|
-- Helper: Check if blocked by same job
|
|
@@ -121,10 +141,17 @@ local function shouldCollapse()
|
|
|
121
141
|
return false, nil, "IS_NEWEST_BUILD"
|
|
122
142
|
end
|
|
123
143
|
|
|
124
|
-
local isBlockedByDeps, blockedByBuilds = isBlockedByDependencies(
|
|
144
|
+
local isBlockedByDeps, blockedByBuilds = isBlockedByDependencies(runningBuilds)
|
|
125
145
|
local isBlockedBySelf = isBlockedBySameJob()
|
|
126
146
|
local shouldCollapseFlag, newestBuild, collapseReason = shouldCollapse()
|
|
127
147
|
|
|
148
|
+
redis.log(redis.LOG_NOTICE, string.format(
|
|
149
|
+
"[startBuild] build=%s job=%s deps=%d blockedByDeps=%s blockedBySelf=%s runningBuildId=%s lastRunning=%s shouldCollapse=%s deleteKeyExists=%s",
|
|
150
|
+
buildId, jobId, #dependencies, tostring(isBlockedByDeps), tostring(isBlockedBySelf),
|
|
151
|
+
tostring(runningBuildId or "nil"), tostring(lastRunningBuildId or "nil"),
|
|
152
|
+
tostring(shouldCollapseFlag), tostring(deleteKeyExists == 1)
|
|
153
|
+
))
|
|
154
|
+
|
|
128
155
|
-- Determine final action (Priority: ABORT > COLLAPSE > BLOCK > START)
|
|
129
156
|
local action, reason, actionData
|
|
130
157
|
|
|
@@ -154,7 +181,11 @@ end
|
|
|
154
181
|
|
|
155
182
|
-- Update Redis state based on decision
|
|
156
183
|
if action == "ABORT" then
|
|
157
|
-
-- Build was aborted
|
|
184
|
+
-- Build was aborted - clean up waiting queue and configs
|
|
185
|
+
redis.call("HDEL", buildConfigKey, buildId)
|
|
186
|
+
redis.call("LREM", waitingKey, 0, buildId)
|
|
187
|
+
redis.call("DEL", deleteKey)
|
|
188
|
+
|
|
158
189
|
return cjson.encode({
|
|
159
190
|
action = "ABORT",
|
|
160
191
|
reason = reason,
|
|
@@ -163,6 +194,10 @@ if action == "ABORT" then
|
|
|
163
194
|
|
|
164
195
|
elseif action == "COLLAPSE" then
|
|
165
196
|
-- Collapse this build - remove from configs and waiting queue
|
|
197
|
+
redis.log(redis.LOG_NOTICE, string.format(
|
|
198
|
+
"[startBuild] COLLAPSE: Deleting buildConfig for build=%s job=%s reason=%s",
|
|
199
|
+
buildId, jobId, reason
|
|
200
|
+
))
|
|
166
201
|
redis.call("HDEL", buildConfigKey, buildId)
|
|
167
202
|
redis.call("LREM", waitingKey, 0, buildId)
|
|
168
203
|
|
|
@@ -187,9 +222,6 @@ elseif action == "BLOCK" then
|
|
|
187
222
|
redis.call("RPUSH", waitingKey, buildId)
|
|
188
223
|
end
|
|
189
224
|
|
|
190
|
-
-- Set deleteKey with timeout (prevents zombie builds)
|
|
191
|
-
redis.call("SET", deleteKey, buildId, "EX", blockTimeout * 60)
|
|
192
|
-
|
|
193
225
|
return cjson.encode({
|
|
194
226
|
action = "BLOCK",
|
|
195
227
|
reason = reason,
|
|
@@ -31,9 +31,10 @@ const hash = `${queuePrefix}timeoutConfigs`;
|
|
|
31
31
|
* @param {String} buildId
|
|
32
32
|
* @param {Object} redis
|
|
33
33
|
* @param {String} workerId
|
|
34
|
+
* @param {Object} buildConfig - Build config fetched before Lua execution
|
|
34
35
|
* @return {Promise}
|
|
35
36
|
*/
|
|
36
|
-
async function executeTimeout(decision, buildId, redis, workerId) {
|
|
37
|
+
async function executeTimeout(decision, buildId, redis, workerId, buildConfig) {
|
|
37
38
|
const { timeoutMinutes } = decision;
|
|
38
39
|
|
|
39
40
|
// Get and update current step
|
|
@@ -42,7 +43,8 @@ async function executeTimeout(decision, buildId, redis, workerId) {
|
|
|
42
43
|
try {
|
|
43
44
|
step = await helper.getCurrentStep({
|
|
44
45
|
redisInstance: redis,
|
|
45
|
-
buildId
|
|
46
|
+
buildId,
|
|
47
|
+
buildConfig
|
|
46
48
|
});
|
|
47
49
|
} catch (err) {
|
|
48
50
|
logger.error(`worker[${workerId}] -> No active step found for ${buildId}`);
|
|
@@ -53,7 +55,8 @@ async function executeTimeout(decision, buildId, redis, workerId) {
|
|
|
53
55
|
redisInstance: redis,
|
|
54
56
|
buildId,
|
|
55
57
|
stepName: step.name,
|
|
56
|
-
code: TIMEOUT_CODE
|
|
58
|
+
code: TIMEOUT_CODE,
|
|
59
|
+
buildConfig
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -61,7 +64,8 @@ async function executeTimeout(decision, buildId, redis, workerId) {
|
|
|
61
64
|
redisInstance: redis,
|
|
62
65
|
buildId,
|
|
63
66
|
status: 'FAILURE',
|
|
64
|
-
statusMessage: `Build failed due to timeout (${timeoutMinutes} minutes)
|
|
67
|
+
statusMessage: `Build failed due to timeout (${timeoutMinutes} minutes)`,
|
|
68
|
+
buildConfig
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
logger.info(`worker[${workerId}] -> Timeout cleanup completed for build ${buildId}`);
|
|
@@ -73,14 +77,15 @@ async function executeTimeout(decision, buildId, redis, workerId) {
|
|
|
73
77
|
* @param {String} buildId - Build ID
|
|
74
78
|
* @param {Object} redis - Redis instance
|
|
75
79
|
* @param {String} workerId - Worker ID
|
|
80
|
+
* @param {Object} buildConfig - Build config fetched before Lua execution
|
|
76
81
|
* @return {Promise}
|
|
77
82
|
*/
|
|
78
|
-
async function handleDecision(decision, buildId, redis, workerId) {
|
|
83
|
+
async function handleDecision(decision, buildId, redis, workerId, buildConfig) {
|
|
79
84
|
switch (decision.action) {
|
|
80
85
|
case 'TIMEOUT':
|
|
81
86
|
// Build has timed out - execute cleanup
|
|
82
87
|
logger.info(`worker[${workerId}] -> Build has timed out ${buildId}`);
|
|
83
|
-
await executeTimeout(decision, buildId, redis, workerId);
|
|
88
|
+
await executeTimeout(decision, buildId, redis, workerId, buildConfig);
|
|
84
89
|
break;
|
|
85
90
|
|
|
86
91
|
case 'CLEANUP':
|
|
@@ -119,6 +124,10 @@ async function process(timeoutConfig, buildId, redis, workerId) {
|
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
try {
|
|
127
|
+
// Fetch buildConfig BEFORE Lua execution (Lua script will delete it if timeout)
|
|
128
|
+
const buildConfigJson = await redis.hget(`${queuePrefix}buildConfigs`, buildId);
|
|
129
|
+
const buildConfig = buildConfigJson ? JSON.parse(buildConfigJson) : null;
|
|
130
|
+
|
|
122
131
|
const loader = getLuaScriptLoader();
|
|
123
132
|
const result = await loader.executeScript(
|
|
124
133
|
'checkTimeout.lua',
|
|
@@ -139,7 +148,7 @@ async function process(timeoutConfig, buildId, redis, workerId) {
|
|
|
139
148
|
|
|
140
149
|
logger.info(`worker[${workerId}] -> Build ${buildId}: action=${decision.action}, reason=${decision.reason}`);
|
|
141
150
|
|
|
142
|
-
await handleDecision(decision, buildId, redis, workerId);
|
|
151
|
+
await handleDecision(decision, buildId, redis, workerId, buildConfig);
|
|
143
152
|
} catch (err) {
|
|
144
153
|
logger.error(`Error in timeout check for build ${buildId}: ${err.message}`);
|
|
145
154
|
logger.error(err.stack);
|
package/plugins/worker/worker.js
CHANGED
|
@@ -94,7 +94,8 @@ async function invoke() {
|
|
|
94
94
|
redisInstance: redis,
|
|
95
95
|
buildId: job.args[0].buildId,
|
|
96
96
|
status: 'FAILURE',
|
|
97
|
-
statusMessage: `${failure}
|
|
97
|
+
statusMessage: `${failure}`,
|
|
98
|
+
buildConfig: job.args[0] // Pass buildConfig to avoid Redis lookup
|
|
98
99
|
})
|
|
99
100
|
.then(() => {
|
|
100
101
|
logger.info(
|