Fix concurrent modification with job arrays#7332
Conversation
TaskArrayRun.getContainerConfig() appended the executor array-index variable (e.g. SLURM_ARRAY_TASK_ID) to the container env whitelist by mutating the list in place. Because ContainerHelper.parseEnvWhitelist() returns the session config list by reference, that list is shared across every ContainerConfig rebuilt from the session config, so one actor thread mutating it while another iterated it in BashWrapperBuilder/FusionHelper threw a java.util.ConcurrentModificationException (and the whitelist grew unboundedly). Stop mutating the shared container config. The array index variable is already carried on TaskBean, so instead add it to the container environment where the whitelist is applied: BashWrapperBuilder.createContainerBuilder() for the standard container path and FusionHelper.runWithContainer() for the Fusion path (reading it from the launcher's TaskBean). The shared session config is never modified. Signed-off-by: Ben Sherman <ben.sherman@seqera.io> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Ben Sherman <bentshermann@gmail.com>
✅ Deploy Preview for nextflow-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Reviewed this before approving and found a gap that needs to be closed first: the Why
The Fusion path works because Fusion is container-native and wraps the container at array-submit time via Note that the added |
Removing the shared-config mutation (previous commit) left the array index variable (e.g. SLURM_ARRAY_TASK_ID) no longer reaching the container for non-Fusion grid job arrays: for those executors the container is built from each child TaskRun, whose TaskBean.arrayIndexName is null (only a TaskArrayRun populates it), so the whitelist branch added in BashWrapperBuilder never fired. Inject the executor array index name onto the child launcher in GridTaskHandler.createTaskWrapper() when the handler is an array child, so the already-added BashWrapperBuilder branch exposes it in the container env. Guard the `### array:` metadata block on arrayWorkDirs so array children (which carry only the index name, not the work-dirs) don't render a bogus block / NPE. Add GridTaskHandlerTest cases exercising the real child path (a plain child builder with a null arrayIndexName that the handler must populate), which fail without the fix. Signed-off-by: Jorge Ejarque <jorge.ejarque@seqera.io> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: jorgee <jorge.ejarque@seqera.io>
|
Pushed 21aef4f to close the gap. Three parts: 1. Fix — propagate the index name to the child launcher ( The handler is the one place that knows both that it's building an array child ( protected BashWrapperBuilder createTaskWrapper(TaskRun task) {
if( fusionEnabled() )
return fusionLauncher()
final builder = executor.createBashWrapperBuilder(task)
// for a containerised array child, the container is built from the child task bean
// (which does not carry the array index variable); expose the scheduler array-index
// variable (e.g. SLURM_ARRAY_TASK_ID) inside the task container
if( isArrayChild && executor instanceof TaskArrayExecutor )
builder.arrayIndexName = ((TaskArrayExecutor) executor).getArrayIndexName()
return builder
}This reuses the whitelist branch already added in 2. Guard — the Now that array children carry if( bean.arrayIndexName && bean.arrayWorkDirs ) {Only the dispatcher task ( 3. Test — exercise the real child path ( Added I left the existing |
jorgee
left a comment
There was a problem hiding this comment.
I have found an issue in the non-fusion path and pushed a fix and extra tests to cover this case. Ready to merge from my side.
|
Thanks for catching this edge case. However I need to review this PR as a whole again. I'm concerned about polluting the code too much with all of this array logic. Need to see if we can do it in a more isolated manner |
Fix #6108
TaskArrayRun.getContainerConfig()appended the executor array-index variable (e.g.SLURM_ARRAY_TASK_ID) to the container env whitelist by mutating the list in place. BecauseContainerHelper.parseEnvWhitelist()returns the session config list by reference, that list is shared across everyContainerConfigrebuilt from the session config, so one actor thread mutating it while another iterated it inBashWrapperBuilder/FusionHelperthrew aConcurrentModificationExceptionThis PR removes the shared container config mutation. The array index variable is already carried on
TaskBean, so instead add it to the container environment where the whitelist is applied:BashWrapperBuilder.createContainerBuilder()for the standard container pathFusionHelper.runWithContainer()for the Fusion path