0

I am using @Retryable like following where I am retrying a db save function in a service class. I want to tract the retrying. But the retry functions are always invoked in the listener.

PeopleService.java

package com.test.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class PeopleService {
    
    @Autowired
    private PeopleRepository peopleRepository;

    @Retryable(value = {Exception.class}, maxAttempts = 3, 
        backoff = @Backoff(delay = 200), listeners = {"retryListener"})
    public void addPeople() throws Exception{
        People p = new People(20, "James", "bond");
        peopleRepository.save(p);
    }
    
}

I have a listener like this which was passed to listener in @Retryable.

RetryListener.java

package com.test.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.listener.RetryListenerSupport;
import org.springframework.stereotype.Component;


@Slf4j
@Component
class RetryListener extends RetryListenerSupport {

    @Override
    public <T, E extends Throwable> void close(RetryContext context,
                                               RetryCallback<T, E> callback, Throwable throwable) {

        System.out.println("Unable to recover from  Exception");
        System.out.println("Error ");
        super.close(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, 
        RetryCallback<T, E> callback, Throwable throwable) {
            
        System.out.println("Exception Occurred, Retry Count " + context.getRetryCount());
        super.onError(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context,
                                                 RetryCallback<T, E> callback) {
        System.out.println("Exception Occurred, Retry Session Started ");
        return super.open(context, callback);
    }
}

Now I am calling the service class method from another component class.

Demo.java

package com.test.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.beans.factory.annotation.Autowired;

@Slf4j
@Component
public class Demo {

    @Autowired
    private PeopleService peopleService;

    @Scheduled(initialDelay = 5000L, fixedDelay = 1000L)
    private void perMinuteStatsTask() {
        try {
            System.out.println("adding people");
            peopleService.addPeople();
        }catch(Exception ex)
        {
            ex.printStackTrace();
        }
    }
    
}

Now the thing is, the onError(), close(), open() is being called even if there is no retry. I am seeing something like this as output for retry case and also for non-retry case.

adding people
Exception Occurred, Retry Session Started
Hibernate:
    insert
    into
        people
        (age, first_name, last_name)
    values
        (?, ?, ?)
Unable to recover from  Exception
Error

some links:

  1. RetryListener doc

  2. full code is here

quidstone
  • 55
  • 1
  • 8

1 Answers1

0

open() and close() are always called.

This is simply wrong:

@Override
public <T, E extends Throwable> boolean open(RetryContext context,
                                             RetryCallback<T, E> callback) {
    System.out.println("Exception Occurred, Retry Session Started ");
    return super.open(context, callback);
}
/**
 * Called before the first attempt in a retry. For instance, implementers can set up
 * state that is needed by the policies in the {@link RetryOperations}. The whole
 * retry can be vetoed by returning false from this method, in which case a
 * {@link TerminatedRetryException} will be thrown.
 * @param <T> the type of object returned by the callback
 * @param <E> the type of exception it declares may be thrown
 * @param context the current {@link RetryContext}.
 * @param callback the current {@link RetryCallback}.
 * @return true if the retry should proceed.
 */

Similarly for close.

System.out.println("Unable to recover from  Exception");

That's not what close() means.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • In the doc, close() is “Called after the final attempt (successful or not)” which included the first attempt as well, so it should be called every time? – quidstone Dec 20 '22 at 21:28
  • 2
    It's called once, at the end of the retry scope; the entire scope is `open -> call target one or more times until successful or retries exhausted -> close`. – Gary Russell Dec 20 '22 at 21:31
  • Okay. I think I understand it now. The whole listener is borrowed from another answer on stack overflow, the comments are not proper which threw me off. I was making different interpretation of the doc as well. – quidstone Dec 20 '22 at 21:35
  • That's not good. To help the community, you should make a comment there to indicate it is wrong, pointing them to this answer. If you don't want to do that point me to answer that led you astray and I will do it. – Gary Russell Dec 20 '22 at 21:56
  • this is the answer I took the retryListener from https://stackoverflow.com/a/64313342 – quidstone Dec 21 '22 at 04:42