2

I have a function like this:

public boolean doLogin() {
    try {
        somemethodForLogin();
        return true;
    }  catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

In this case I want to return false or throw an exception if somemethodForLogin() takes more time than expected, say 8 seconds. How can I do this?

I added something like this just before somemethodForLogin():

new java.util.Timer().schedule(
    new java.util.TimerTask() {
        @Override
        public void run() {
            System.out.println("Returning after 8 seconds wait");
        }
    }, 8000);

But it comes into this always, even if the call is success.

Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56
Jerry
  • 987
  • 4
  • 16
  • 46
  • 1
    I think we need to know what `somemethodForLogin()` is actually doing. If it's making a REST/web call of some kind, using something like `HttpURLConnection`, then you could add a timeout to that call. – Tim Biegeleisen May 24 '18 at 05:38
  • 2
    Call your someMethodForLogin in a separate thread ,stop the current thread for 8 seconds if someMethodForLogin has not ended then kill the thread and return false, else return true; – bit-shashank May 24 '18 at 05:40
  • @javafan while that solution sounds logical, it doesn't actually work. – Kayaman May 24 '18 at 05:41
  • @TimBiegeleisen somemethodForLogin() is not a separate function for me, I will be connecting to mysql comparing the password there. Sometimes if no connections available it will be taking more time than expected in that case i want to throw a proper error – Jerry May 24 '18 at 05:47
  • Can't you set and check for connection timeout ? an exception will be thrown for connection timeout which u can consume to pass on proper message – Amit May 24 '18 at 05:50
  • 1
    It really sounds like you're trying to solve the wrong problem here then. If you're running out of connections, setting a timeout to login isn't exactly a smart move. You could for example use another smaller (or single connection) pool to perform the authentication, ensuring connections are always available. – Kayaman May 24 '18 at 05:50
  • @Kayaman i think my logic is right ,i replied late because i was making a sample program on it. – bit-shashank May 24 '18 at 06:01
  • @javafan no, your logic sounds right, because well...that's how our brain works. It doesn't actually work, because for starters you can't kill a thread. Then you go "well I meant interrupt", and then we go down the same discussion that comes everytime someone suggests that solution. – Kayaman May 24 '18 at 06:02

3 Answers3

2

You can use a completable future to call the login, then retrieve the result with a timeout:

try {
    CompletableFuture.runAsync(() -> somemethodForLogin())
                 .get(8, TimeUnit.SECONDS);
    return true;
}catch(TimeoutException timeoutException) {
    //Timeout handline here
    return false; //or call other function
}

The above lambda is a Supplier rather than a Runnable.

CompletableFuture's get method is documented with the following:

Waits if necessary for at most the given time for this future to complete, and then returns its result, if available.
TimeoutException - if the wait timed out

EDIT:
The above is using CompletableFuture<Void> as the call is taking a Runnable. If somemethodForLogin() returns a value, you can use the same API, but calling supplyAsync:

Object loginResult = CompletableFuture.supplyAsync(() -> somemethodForLogin())
        .get(8, TimeUnit.SECONDS);
//Just change the return types accordingly.
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • I need to return an object from the somemethodForLogin method itself – Jerry May 24 '18 at 06:35
  • @Jerry You can still use the completable future for that. Will edit the answer. – ernest_k May 24 '18 at 06:36
  • speaking of the get and timeoutexception beware of this post https://stackoverflow.com/questions/4350941/why-is-java-future-gettimeout-not-reliable – brat Jul 13 '21 at 15:44
1

So i have just created a sample program to solve your problem .I think that it works as expected but you could also tried it at your side.

I have explained the code in the code comments so it is not a code dump.

class Test
{
    // your do logic method 
    public boolean doLogic()
    {
        try{
            //New Thread for logic();
            Thread t1=new Thread(new Runnable() {
                        public void run() {
                            // calling your logic function in  a new thread
                            try{login();}catch(Exception e){e.printStackTrace();}
                        }
                    });
            t1.start();

            //Making the current thread to sleep for 8 seconds.
            Thread.sleep(8000);
            //check if t1 has ended  within 8 seconds, and then return the required value
            if(!t1.isAlive())
                return true;
            else
                return false;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }

    void login()throws Exception
    {
        Thread.sleep(9000);//tweak this value sleeps the t1 thread for a specific time just to simulate the login function
    }
    // driver method
    public static void main(String args[])
    {
        System.out.println(new Test().doLogic());

    }
}
bit-shashank
  • 887
  • 11
  • 27
1

Both answers allow a possibility for a denial of service attack by repeated login requests. As the executing thread will still continue to execute after 8 seconds, firing repeated login attempts would keep creating threads that would eat away the resources. The CompletableFuture approach would fill up the common pool, which wouldn't keep creating threads, but it would affect every other part of the code that uses the common pool.

What you can do is create a secondary pool with one or just a few connections dedicated to logging in. Set the connection checkout timeout to 8 seconds, and you've got your timeout there right out of the box. Not to mention less competition on the pool that's doing business tasks.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • While this is valid, it's primarily beside the point of the question. It's true that setting connection and a read timeouts on service calls is the first thing to do, but `somemethodForLogin` is effectively a black box in the question, so maybe the timeout is really needed. – ernest_k May 24 '18 at 07:15
  • @ErnestKiwele there is not a reliable way of providing a method timeout in the case of a "black box" situation. This answer is based on the OP's comment about the login method doing DB authentication. If it did socket things we could use a socket timeout. If it performed interruptible things we could call `Thread.interrupt()`. Too many people are suggesting that there **is** a simple solution to this (many dupes suggest using `ExecutorService/Future` approach), but it's not a good idea to think that. It's like thinking you don't need to worry about encoding, because it works on your computer. – Kayaman May 24 '18 at 07:22