Skip to content

Commit c6e766e

Browse files
authored
Merge pull request #692 from Extra-Chill/fix/issue-691-cleanup-empty-wrapper-convergence
Fix cleanup idle wrapper convergence
2 parents 92c0dfa + 0f21fd5 commit c6e766e

2 files changed

Lines changed: 68 additions & 3 deletions

File tree

inc/Cleanup/DataMachineJobCleanupRunEvidenceStore.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,25 +142,34 @@ private function aggregate_cleanup_child_jobs( array $child_jobs ): array {
142142
),
143143
);
144144

145+
$parent_job_ids = array();
146+
foreach ( $child_jobs as $child ) {
147+
$parent_job_id = (int) ( is_array($child) ? ( $child['parent_job_id'] ?? 0 ) : 0 );
148+
if ( $parent_job_id > 0 ) {
149+
$parent_job_ids[ $parent_job_id ] = true;
150+
}
151+
}
152+
145153
foreach ( $child_jobs as $child ) {
146154
$child_job_id = (int) ( $child['job_id'] ?? 0 );
147155
$status = (string) ( $child['status'] ?? '' );
148156
$engine_data = $this->normalize_engine_data($child['engine_data'] ?? array());
149157
$result = $this->extract_system_task_result($engine_data);
158+
$idle_wrapper = $this->is_idle_cleanup_wrapper_job($child, $engine_data, $result, $parent_job_ids);
150159

151160
++$summary['children']['total'];
152161
if ( $child_job_id > 0 ) {
153162
$summary['children']['job_ids'][] = $child_job_id;
154163
if ( 'pending' === $status ) {
155164
$summary['children']['pending_job_ids'][] = $child_job_id;
156-
} elseif ( 'processing' === $status ) {
165+
} elseif ( 'processing' === $status && ! $idle_wrapper ) {
157166
$summary['children']['processing_job_ids'][] = $child_job_id;
158167
} elseif ( str_starts_with($status, 'failed') ) {
159168
$summary['children']['failed_job_ids'][] = $child_job_id;
160169
}
161170
}
162171

163-
$this->count_cleanup_child_status($summary['children'], $status);
172+
$this->count_cleanup_child_status($summary['children'], $idle_wrapper ? 'skipped - idle_wrapper' : $status);
164173
if ( isset($summary['children']['statuses'][ $status ]) ) {
165174
++$summary['children']['statuses'][ $status ];
166175
} elseif ( '' !== $status ) {
@@ -466,6 +475,31 @@ private function extract_system_task_result( array $engine_data ): array {
466475
return array();
467476
}
468477

478+
/**
479+
* Detect drained wrapper jobs that have no cleanup evidence or descendants.
480+
*
481+
* @param array<string,mixed> $child Child job row.
482+
* @param array<string,mixed> $engine_data Normalized engine data.
483+
* @param array<string,mixed> $result Extracted task result.
484+
* @param array<int,bool> $parent_job_ids Child IDs that have descendants.
485+
* @return bool
486+
*/
487+
private function is_idle_cleanup_wrapper_job( array $child, array $engine_data, array $result, array $parent_job_ids ): bool {
488+
$child_job_id = (int) ( $child['job_id'] ?? 0 );
489+
$status = (string) ( $child['status'] ?? '' );
490+
$source = (string) ( $child['source'] ?? '' );
491+
492+
if ( 'processing' !== $status || 'pipeline_system_task' !== $source || $child_job_id <= 0 || isset($parent_job_ids[ $child_job_id ]) ) {
493+
return false;
494+
}
495+
496+
if ( array() !== $result || isset($engine_data['batch_id']) || 'worktree_cleanup_chunk' === (string) ( $engine_data['task_type'] ?? '' ) ) {
497+
return false;
498+
}
499+
500+
return true;
501+
}
502+
469503
/**
470504
* Count a child job status into stable status buckets.
471505
*

tests/smoke-worktree-cleanup-cli.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,25 @@ public function execute( array $input ): array
965965
'total' => 0,
966966
);
967967
}
968+
if ( 'idle_wrapper' === $scenario ) {
969+
$jobs = 123 === (int) $input['parent_job_id']
970+
? array(
971+
array(
972+
'job_id' => 128,
973+
'parent_job_id' => 123,
974+
'source' => 'pipeline_system_task',
975+
'status' => 'processing',
976+
'engine_data' => array(),
977+
),
978+
)
979+
: array();
980+
981+
return array(
982+
'success' => true,
983+
'jobs' => $jobs,
984+
'total' => count($jobs),
985+
);
986+
}
968987

969988
$children = array(
970989
123 => array(
@@ -1094,7 +1113,7 @@ public function execute( array $input ): array
10941113
'flow_id' => null,
10951114
'pipeline_id' => null,
10961115
'source' => 'system',
1097-
'status' => in_array($scenario, array( 'no_work', 'completed_children' ), true) ? 'processing' : 'completed',
1116+
'status' => in_array($scenario, array( 'no_work', 'completed_children', 'idle_wrapper' ), true) ? 'processing' : 'completed',
10981117
'created_at' => '2026-05-03 00:00:00',
10991118
'completed_at' => '2026-05-03 00:10:00',
11001119
'engine_data' => $engine_data,
@@ -1454,6 +1473,18 @@ public function execute( array $input ): array
14541473
datamachine_code_cleanup_assert(false === (bool) ( $completed_children_status_json['drain']['needed'] ?? true ), 'completed-children cleanup status does not request more drain passes');
14551474
$GLOBALS['datamachine_code_cleanup_status_scenario'] = 'default';
14561475

1476+
WP_CLI::$logs = array();
1477+
WP_CLI::$successes = array();
1478+
$GLOBALS['datamachine_code_cleanup_status_scenario'] = 'idle_wrapper';
1479+
$command->cleanup(array( 'status', 'cleanup-run-123' ), array( 'format' => 'json' ));
1480+
$idle_wrapper_status_json = json_decode(WP_CLI::$logs[0] ?? '', true);
1481+
datamachine_code_cleanup_assert('complete' === ( $idle_wrapper_status_json['state'] ?? '' ), 'cleanup status converges empty processing wrapper jobs instead of waiting forever');
1482+
datamachine_code_cleanup_assert('processing' === ( $idle_wrapper_status_json['parent_status'] ?? '' ), 'idle-wrapper cleanup status preserves raw parent job status separately');
1483+
datamachine_code_cleanup_assert(0 === (int) ( $idle_wrapper_status_json['children']['running'] ?? -1 ), 'idle-wrapper cleanup status does not count empty wrappers as running children');
1484+
datamachine_code_cleanup_assert(array() === ( $idle_wrapper_status_json['children']['processing_job_ids'] ?? array() ), 'idle-wrapper cleanup status omits empty wrappers from active child drain ids');
1485+
datamachine_code_cleanup_assert(false === (bool) ( $idle_wrapper_status_json['drain']['needed'] ?? true ), 'idle-wrapper cleanup status does not request another drain pass');
1486+
$GLOBALS['datamachine_code_cleanup_status_scenario'] = 'default';
1487+
14571488
WP_CLI::$logs = array();
14581489
WP_CLI::$successes = array();
14591490
$command->cleanup(array( 'status', 'cleanup-run-123' ), array());

0 commit comments

Comments
 (0)