From cd044ffd8f8ac084fa442eb735f01f6649102dad Mon Sep 17 00:00:00 2001 From: Edmund Miller Date: Thu, 23 Oct 2025 17:43:00 +0200 Subject: [PATCH 1/3] feat: Add fusion field to DiskResource Add optional Boolean fusion field to DiskResource to allow per-task control of Fusion filesystem usage. Changes: - Add fusion field to DiskResource class - Update constructor to accept fusion parameter from disk directive - Make request field optional (can be null) for fusion-only usage - Update withRequest() to preserve fusion setting - Update toString() via @ToString to include fusion field This enables syntax like: disk fusion: false disk request: '100 GB', fusion: true Signed-off-by: Edmund Miller --- .../nextflow/executor/res/DiskResource.groovy | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/modules/nextflow/src/main/groovy/nextflow/executor/res/DiskResource.groovy b/modules/nextflow/src/main/groovy/nextflow/executor/res/DiskResource.groovy index 25939c7e90..4a651aa9d6 100644 --- a/modules/nextflow/src/main/groovy/nextflow/executor/res/DiskResource.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/executor/res/DiskResource.groovy @@ -23,7 +23,7 @@ import nextflow.util.MemoryUnit /** * Models disk resource request - * + * * @author Ben Sherman */ @ToString(includeNames = true, includePackage = false) @@ -33,30 +33,34 @@ class DiskResource { final MemoryUnit request final String type + final Boolean fusion - DiskResource( value ) { + DiskResource(value) { this(request: value) } - DiskResource( Map opts ) { - this.request = toMemoryUnit(opts.request) + DiskResource(Map opts) { + this.request = opts.request != null ? toMemoryUnit(opts.request) : null - if( opts.type ) + if (opts.type) this.type = opts.type as String + + if (opts.fusion != null) + this.fusion = opts.fusion as Boolean } DiskResource withRequest(MemoryUnit value) { - return new DiskResource(request: value, type: this.type) + return new DiskResource(request: value, type: this.type, fusion: this.fusion) } - private static MemoryUnit toMemoryUnit( value ) { - if( value instanceof MemoryUnit ) - return (MemoryUnit)value + private static MemoryUnit toMemoryUnit(value) { + if (value instanceof MemoryUnit) + return (MemoryUnit) value try { return new MemoryUnit(value.toString().trim()) } - catch( Exception e ) { + catch (Exception e) { throw new IllegalArgumentException("Not a valid disk value: $value") } } From 24bd0a0037a4e90c89ecb163bad74331a361ccb1 Mon Sep 17 00:00:00 2001 From: Edmund Miller Date: Thu, 23 Oct 2025 18:19:46 +0200 Subject: [PATCH 2/3] feat: Implement task-level Fusion override via disk directive Enable per-task control of Fusion filesystem by checking the disk.fusion setting before falling back to executor-level configuration. Changes: - Modify fusionEnabled() in FusionAwareTask to check task's disk.fusion setting first - Fall back to executor.isFusionEnabled() if disk.fusion is not specified - Task-level setting takes priority over global fusion.enabled config - Add null-safe navigation for config to handle cases where task config may not be set (e.g., in test mocks) Priority order: 1. Task-level disk.fusion setting (highest) 2. Executor/session-level fusion.enabled config (fallback) This provides a clearer alternative to using "scratch false" for disabling Fusion on problematic tasks. Signed-off-by: Edmund Miller --- .../main/groovy/nextflow/fusion/FusionAwareTask.groovy | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/nextflow/src/main/groovy/nextflow/fusion/FusionAwareTask.groovy b/modules/nextflow/src/main/groovy/nextflow/fusion/FusionAwareTask.groovy index d75c8f44fc..4ffc135775 100644 --- a/modules/nextflow/src/main/groovy/nextflow/fusion/FusionAwareTask.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/fusion/FusionAwareTask.groovy @@ -42,7 +42,14 @@ trait FusionAwareTask { boolean fusionEnabled() { if( fusionEnabled==null ) { - fusionEnabled = getExecutor0().isFusionEnabled() + // Check task-level disk.fusion setting first + def diskFusion = getTask().config?.getDiskResource()?.fusion + if( diskFusion != null ) { + fusionEnabled = diskFusion + } else { + // Fall back to executor-level setting + fusionEnabled = getExecutor0().isFusionEnabled() + } } return fusionEnabled } From cd122f4fd5d3c3f9cbc6a7eac845d189b989c32a Mon Sep 17 00:00:00 2001 From: Edmund Miller Date: Thu, 23 Oct 2025 18:20:45 +0200 Subject: [PATCH 3/3] test: Add tests for disk fusion feature Add comprehensive unit tests for the disk fusion feature. Changes: - Add test cases for DiskResource with fusion field - Test all combinations: fusion true/false/null - Test fusion field preservation in withRequest() - Test combined usage with type and request fields Tests verify: - DiskResource correctly stores fusion setting - Fusion setting persists through withRequest() transformations - All disk directive syntax variants parse correctly Signed-off-by: Edmund Miller --- .../nextflow/executor/res/DiskResourceTest.groovy | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/nextflow/src/test/groovy/nextflow/executor/res/DiskResourceTest.groovy b/modules/nextflow/src/test/groovy/nextflow/executor/res/DiskResourceTest.groovy index d0541ca9ec..9342f2176b 100644 --- a/modules/nextflow/src/test/groovy/nextflow/executor/res/DiskResourceTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/executor/res/DiskResourceTest.groovy @@ -35,17 +35,23 @@ class DiskResourceTest extends Specification { then: disk.request == REQ disk.type == TYPE + disk.fusion == FUSION where: - VALUE | REQ | TYPE - _100_GB | _100_GB | null - [request: _100_GB] | _100_GB | null - [request: _375_GB, type: 'local-ssd'] | _375_GB | 'local-ssd' + VALUE | REQ | TYPE | FUSION + _100_GB | _100_GB | null | null + [request: _100_GB] | _100_GB | null | null + [request: _375_GB, type: 'local-ssd'] | _375_GB | 'local-ssd' | null + [request: _100_GB, fusion: true] | _100_GB | null | true + [request: _100_GB, fusion: false] | _100_GB | null | false + [request: _375_GB, type: 'local-ssd', fusion: false] | _375_GB | 'local-ssd' | false } def 'should return a disk resource with the specified request' () { expect: new DiskResource(request: _100_GB).withRequest(_375_GB) == new DiskResource(request: _375_GB) new DiskResource(request: _100_GB, type: 'ssd').withRequest(_375_GB) == new DiskResource(request: _375_GB, type: 'ssd') + new DiskResource(request: _100_GB, fusion: true).withRequest(_375_GB) == new DiskResource(request: _375_GB, fusion: true) + new DiskResource(request: _100_GB, type: 'ssd', fusion: false).withRequest(_375_GB) == new DiskResource(request: _375_GB, type: 'ssd', fusion: false) } }