Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring async file upload and processing

I'm trying to upload file and then read it, everything is working fine, but not when I put @Async annotation on the handler method.

I don't want the user to keep waiting until it processes the file. But after putting this annotation I get java.lang.IllegalStateException: File has been moved - cannot be read again exception. What happens and how do I fix this? As I understand, Spring could be just clearing the file because request-response ends and it cleans it up. But shouldn't @Async prevent this?

Sample Spring Boot application:

@SpringBootApplication
@EnableSwagger2
@ComponentScan(value = "hello")
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.regex("/api/.*"))
                .build();
    }
}

Upload controller:

@RestController
@RequestMapping(value = "/files")
public class FilesController {

    @Inject
    private Upload upload;

    @RequestMapping(method = RequestMethod.POST)
    public void addSource(@RequestParam MultipartFile file) throws IOException, InterruptedException {
        upload.process(file);
    }
}

Upload service:

@Component
public class Upload {

    @Async
    public void process(MultipartFile file) throws InterruptedException, IOException {
        sleep(2000);
        System.out.println(new String(IOUtils.readFully(file.getInputStream(), -1, false)));
    }
}

And now I get java.io.FileNotFoundException. I'm not sure what I'm missing here. Probably I'm doing something wrong as I couldn't find any error on this and I'd think this is very common use-case.

like image 215
brgs Avatar asked Apr 12 '16 06:04

brgs


1 Answers

You cannot hand over the MultipartFile paramter to your @Async Method in the way you do it.

When the addSource method ends, the MultipartFile runs out of scope and the resource is released. So the access inside your "process" method will fail. You build some kind of a race-condition this way. Springs DispatcherServlet uses StandardServletMultipartResolver.cleanupMultipart to cleanup those files. Put a breakpoint there to see when this method gets called upon retun of addSource(...).

You should do the following: Read the entire file into a Buffer inside the addSource method. Then pass the buffer to the process Method an let addSource return.

What you call "...processes the file..." is not "processing" the file, but reading it.

@Async
public void process(byte[] bs){
    System.out.println(new String(bs));
    //do some long running processing of bs here
}

@RequestMapping(method = RequestMethod.POST)
public void addSource(@RequestParam MultipartFile file) {
    upload.process(IOUtils.toByteArray(file));
}
like image 136
TomB Avatar answered Oct 16 '22 15:10

TomB