0

I've created a simple batch using the Spring Batch JSR-352 implementation. The batch uses a PartitionMapper to inject a separate property for each chunk. According to the JSR-352 specifications (when partitionsOverride = False) when one of the partition chunks fail and the batch is restarted only the failed partition chunks should restart.

For example if we have 3 partitions: partition0, partition1 and partition2. If partition1 and partition2 fail, the batch should only restart partition1 and partion2 with their own batch properties.

However I've noticed that when using the Spring Batch JSR-352 implementation (latest version 3.0.3.Release) restarting the batch will restart partition0 and partition1 instead of partition1 and partition2. Thus it correctly detects that two partitions have failed but it incorrectly restarts the first (two) partitions instead of the failed partitions as it should.

Is this a bug in the Spring Batch implementation or am I missing something?

See JSR-352 docs section 10.8.5: http://download.oracle.com/otn-pub/jcp/batch-1_0_revA-mrel-spec/JSR_352-v1.0_Rev_a-Maintenance_Release.pdf

Here is the code I've used:

/META-INF/batch-jobs/sampleBatch.xml

<?xml version="1.0" encoding="UTF-8"?>
<job id="sampleBatch" version="1.0" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">

<step id="sampleStep">
    <chunk item-count="100">
        <reader ref="com.springapp.batch.SampleReader">
            <properties>
                <property name="sample" value="#{partitionPlan['sample']}"/>
            </properties>
        </reader>
        <writer ref="com.springapp.batch.SampleWriter">
            <properties>
                <property name="sample" value="#{partitionPlan['sample']}"/>
            </properties>
        </writer>
    </chunk>
    <partition>
        <mapper ref="com.springapp.batch.SamplePartitionMapper"/>
    </partition>
</step>
</job>

com.springapp.batch.SamplePartitionMapper:

package com.springapp.batch;

import java.util.Properties;
import javax.batch.api.partition.PartitionMapper;
import javax.batch.api.partition.PartitionPlan;
import javax.batch.api.partition.PartitionPlanImpl;

public class SamplePartitionMapper implements PartitionMapper {

@Override
public PartitionPlan mapPartitions() throws Exception {
    final PartitionPlan partitionPlan = new PartitionPlanImpl();

    int size = 3;
    Properties[] partitionProps = new Properties[size];

    for (int i=0; i<size; i++) {
        final Properties properties = new Properties();
        properties.put("sample", ""+i);
        partitionProps[i] = properties;
        System.out.println("mapPartitions: " + i);
    }

    partitionPlan.setThreads(1);
    partitionPlan.setPartitions(partitionProps.length);
    partitionPlan.setPartitionProperties(partitionProps);

    return partitionPlan;
}
}

com.springapp.batch.SampleReader:

public class SampleReader extends AbstractItemReader {

@Inject
@BatchProperty
private String sample;

Iterator<Integer> iter;

@Override
public void open(Serializable checkpoint) throws Exception {
    System.out.println("open for reading sample: " + sample);
    ArrayList list = new ArrayList<Integer>();
    for(int i=0; i<Integer.parseInt(sample); i++) {
        list.add(new Integer(i));
    }
    iter = list.iterator();
}

@Override
public Integer readItem() throws Exception {
    if(iter.hasNext())
        return iter.next();
    else
        return null;
}
}

com.springapp.batch.SampleWriter:

public class SampleWriter extends AbstractItemWriter {

@Inject
@BatchProperty
private String sample;

@Override
public void writeItems(List<Object> items) throws Exception {
    System.out.println("writeItems sample: " + sample);

    if(sample.equals("1")) {
        throw new Exception("FAIL PARTITION 1");
    }
    if(sample.equals("2")) {
        throw new Exception("FAIL PARTITION 2");
    }

    for (Object itemObj : items) {
        Integer item = (Integer) itemObj;
        System.out.println(item);
    }
}
}

TestJob testrunner:

package com.springapp.batch;

import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;
import javax.batch.runtime.JobExecution;
import javax.inject.Inject;

import junit.framework.Assert;
import org.junit.Test;

//@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:spring-config.xml")
public class AppTests {

@Test
public void testJob() throws Exception {
    JobOperator jobOperator = BatchRuntime.getJobOperator();
    long jobExecution = jobOperator.start("sampleBatch", new Properties());

    int attempt = 0;
    while (true) {
        JobExecution execution = jobOperator.getJobExecution(jobExecution);
        if (execution.getEndTime() != null) {
            //check status
            if( "FAILED".equals(execution.getExitStatus()) && attempt < 3 ) {
                attempt++;
                System.out.println("Batch failed, trying to restart (attempt " + attempt + ")..");
                jobExecution = jobOperator.restart(jobExecution,  new Properties());
                continue;
            }
            System.out.println("Batch ended with status: " + execution.getExitStatus());
            break;
        }
    }
    Assert.assertEquals("COMPLETED", jobOperator.getJobExecution(jobExecution).getExitStatus());
}
}
Wesley
  • 3
  • 3

1 Answers1

0

Looking at this, it looks like a bug. I've logged Jira issue BATCH-2364 to track it.

Michael Minella
  • 20,843
  • 4
  • 55
  • 67