1

I am trying to run the files uploaded through antivirus and want to run each file in a separate thread. I have written method like below

@Transactional( rollbackFor = DataException.class )
    public void throwExceptionIfFilesIsAMalware( MultipartFile[] files ) throws DataException
    {
        try
        {
            NullEmptyUtils.throwExceptionIfInputIsNullOrEmpty(files, GeneralConstants.FILE_NOT_FOUND);
            for( MultipartFile file : files )
            {
                Future<Boolean> future = throwExceptionIfFileIsAMalware(file);
            }
        }
        catch( DataException e )
        {
            log.error(GeneralConstants.ERROR, e);
            throw e;
        }
        catch( Exception e )
        {
            log.error(GeneralConstants.ERROR, e);
            throw new DataException(GeneralConstants.EXCEPTION, GeneralConstants.SOMETHING_WENT_WRONG,
                    HttpStatus.BAD_REQUEST);
        }
    }

    @Async
    public Future<Boolean> throwExceptionIfFileIsAMalware( MultipartFile file ) throws DataException
    {
        try
        {
            NullEmptyUtils.throwExceptionIfInputIsNull(file, GeneralConstants.FILE_NOT_FOUND);
            byte[] scannedResult = clamAVClient.scan(new BufferedInputStream(file.getInputStream()));
            if( !ClamAVClient.isCleanReply(scannedResult) )
            {
                throw new DataException(GeneralConstants.EXCEPTION,
                        GeneralConstants.MALWARE_EXCEPTION + GeneralConstants.SINGLE_SPACE_STRING + file.getName(),
                        HttpStatus.BAD_REQUEST);
            }
            return new AsyncResult<>(true);
        }
        catch( IOException e )
        {
            log.error(GeneralConstants.ERROR, e);
            throw new DataException(GeneralConstants.EXCEPTION, GeneralConstants.FILE_IO_EXCEPTION,
                    HttpStatus.BAD_REQUEST);
        }
        catch( DataException e )
        {
            log.error(GeneralConstants.ERROR, e);
            throw e;
        }
        catch( Exception e )
        {
            log.error(GeneralConstants.ERROR, e);
            throw new DataException(GeneralConstants.EXCEPTION, GeneralConstants.SOMETHING_WENT_WRONG,
                    HttpStatus.BAD_REQUEST);
        }
    }

I want the first method which is calling the second Async method to wait till all the threads are finished.

But if I call future.get() inside the loop it will not parallelly run all the files in a thread. It will start to wait for each file to be processed in a separate thread.

How should I handle this?

Thank you

3
  • 1
    It sounds like what you want is chaining, like future.thenApply. Commented Jul 14, 2020 at 21:16
  • are you sure your @Async will kick in on private method of the same class??? do you use aspectj? Commented Jul 14, 2020 at 22:20
  • It works on the same class Commented Jul 14, 2020 at 22:47

2 Answers 2

2

First of all, you cannot use @Async on private methods. In runtime, it will not able to create a proxy and, therefore, not work.

If I understood what you want to do correctly, you have to separate throwExceptionIfFileIsAMalware to another class and then use Spring proxy with @Async. That way you can catch the exceptions you are expecting at the main method and store it in a variable (or perhaps a list of exceptions). Then, after all executions, you can check your list and throw the exception.

Sign up to request clarification or add additional context in comments.

1 Comment

Got it. But we can not guarantee the thread execution order. By the time the main thread finishes, the other files might still be under process. Are you suggesting we put a while loop and wait until all the files are processed?
1

You can do something like this

CompletableFuture<ResponseClass> cf1 = CompletableFuture.supplyAsync(() -> service.getCall(args));

CompletableFuture<ResponseClass2> cf2 = CompletableFuture.supplyAsync(() -> service2.getCall(args));

CompletableFuture<ReturnTypeOfmakeResponseMethod> response = 
 CompletableFuture.allOf(cf1, cf2).thenApplyAsync(aVoid -> makeResponse(cf1.join(), cf2.join()));


public void ReturnTypeOfmakeResponseMethod makeResponse(ResponseClass, ResponseClass2) {

....

}

You can work around this code. This is based on my requirement.

Hope this will help

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.