0

I am working on a library where I make Http call to my service and if my service machine doesn't respond back (there is a socket timeout or connect timeout), I add them to my local blockList and if machine is blocked 5 times then I don't make call to them.

So let's say if machineA is not responding (throwing RestClientException), I will call onFailure method everytime and keep incrementing the counter and then while making call to machineA again, I check isBlocked method by passing machineA as hostname and 5 as threshold so if machineA has been blocked 5 times then I don't make call to them at all. My library is multithreaded so that's why I am using volatile here as I want all threads to see same value.

Below is what I have in DataMapping class:

public static volatile ConcurrentHashMap<String, AtomicInteger> blockedHosts =
      new ConcurrentHashMap<String, AtomicInteger>();

boolean isBlocked(String hostname, int threshold) {
    AtomicInteger count = blockedHosts.get(hostname);
    return count != null && count.get() >= threshold;
}

void onFailure(String hostname) {
    AtomicInteger newValue = new AtomicInteger();
    AtomicInteger val = blockedHosts.putIfAbsent(hostname, newValue);
    // no need to care about over-reaching 5 here
    (val == null ? newValue : val).incrementAndGet();
}

void onSuccess(String hostname) {
    blockedHosts.remove(hostname);
}

Problem Statement:-

Now I want to add one more feature which is - If machineA is blocked (since its blocking count is >= 5) then I want to keep it block for x interval. I will be having another parameter (key.getInterval()) which will tell us for how long I want to keep this machine blocked and after that interval is elapsed, then only I will start making call to them. I am not able to understand how to add this feature?

Below is my main thread code where I am using DataMapping methods to check whether hostname is blocked or not and also to block hostnames.

@Override
public DataResponse call() {
    ResponseEntity<String> response = null;

    List<String> hostnames = some_code_here;

    for (String hostname : hostnames) {
        // If hostname is in block list, skip sending request to this host
        if (DataMapping.isBlocked(hostname)) {
            continue;
        }
        try {
            String url = createURL(hostname);
            response = restTemplate.exchange(url, HttpMethod.GET, key.getEntity(), String.class);
            DataMapping.onSuccess(hostname);

            // some code here to return the response if successful
        } catch (RestClientException ex) {
            // adding to block list
            DataMapping.onFailure(hostname);
        }
    }

    return new DataResponse(DataErrorEnum.SERVER_UNAVAILABLE, DataStatusEnum.ERROR);        
}

How can I block a particular machine for a particular period of time and as soon as that interval is elapsed then only start making calls to them?

john
  • 11,311
  • 40
  • 131
  • 251
  • You need to keep track of when it became blocked in order to tell if the interval has elapsed, not just whether it is blocked. – David Conrad May 15 '16 at 23:56

1 Answers1

1

You can use a ScheduledExecutorService and schedule the reset of the counter after a certain timeout.

You can declare this within your DataMapping class:

private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // or perhaps the thread pool version ?

And within your onFailure() method you can decide whether you want to reset or just decrement the counter after a certain timeout:

void onFailure(String hostname) {
    // you can use `computeIfAbsent` in java8
    AtomicInteger val = blockedHosts.computeIfAbsent(hostname, key -> new AtomicInteger());
    int count = val.incrementAndGet();
    // the test here is `==` to make sure the task is scheduled only once
    if (count == threshold) {
        scheduler.schedule(() -> blockedHosts.remove(hostname), 5L, TimeUnit.MINUTES);  // or you may choose to just decrement the counter
    }
}

As a side note, there's no reason to make blockedHosts volatile. That reference never changes; it should be final instead; and probably private.


In java7, the above code would look like this:

void onFailure(String hostname) {
    AtomicInteger newValue = new AtomicInteger();
    AtomicInteger val = blockedHosts.putIfAbsent(hostname, newValue);
    int count = (val == null ? newValue : val).incrementAndGet();
    // the test here is `==` to make sure the task is scheduled only once
    if (count == threshold) {
        scheduler.schedule(new Runnable() {
            @Override public void run() {
                blockedHosts.remove(hostname);  // or you may choose to just decrement the counter
            }
        }, 5L, TimeUnit.MINUTES);
    }
}
Costi Ciudatu
  • 37,042
  • 7
  • 56
  • 92
  • Unfortunately I am on Java 7 yet and I cannot move to Java 8. How will this look like with Java 7? – john May 16 '16 at 00:15
  • Pretty much the same, except for `computeIfAbsent` and the neat lambda syntax. You'll have to instantiate a `Callable` (or `Runnable`) and submit it to the executor service. – Costi Ciudatu May 16 '16 at 00:17
  • Can you update this with Java 7 suggestion as well. I am still trying to understand what does above code do. I will have few questions once I see in Java 7 just to make sure I understand. – john May 16 '16 at 00:18
  • Done. I hope it compiles :) – Costi Ciudatu May 16 '16 at 00:27
  • Yeah I think it will compile for sure. We have hardcoded the `5` minute window so instead of that if I pass in the method, then that should also work right? – john May 16 '16 at 00:28
  • That was just an example, you can compute the timeout in any way you see fit. I'm not sure I understand what "pass in the method" means though... – Costi Ciudatu May 16 '16 at 00:30
  • Also this if check we have here `if (count == threshold) {`. Does this should be `>=` instead of `==` since we have multiple threads here? – john May 18 '16 at 19:42
  • You can use `>=`, but you'll need to make sure that you don't schedule the same counter removal multiple times. With `==`, that will only happen once so you don't need to worry. You can count on one (and only one) thread getting that exact value eventually -- unless that `AtomicInteger` is changed from outside this class. – Costi Ciudatu May 21 '16 at 14:37
  • I have a new question which is exactly based on this question, only difference is there is a slight change in the requirement. I have got it working, wanted to make sure what I am doing is right or not. Here is my [question](http://stackoverflow.com/questions/41925494/how-to-keep-retrying-block-machine-every-x-interval-until-url-is-executed-succes). – john Feb 02 '17 at 20:18