Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-872545: Thread.interrupt() does not work #1482

Closed
MohamedKamarudeen opened this issue Jul 20, 2023 · 14 comments
Closed

SNOW-872545: Thread.interrupt() does not work #1482

MohamedKamarudeen opened this issue Jul 20, 2023 · 14 comments
Assignees
Labels
invalid status-triage_done Initial triage done, will be further handled by the driver team

Comments

@MohamedKamarudeen
Copy link

  1. What version of JDBC driver are you using?
    3.13.29, 3.13.33

  2. What operating system and processor architecture are you using?
    MacOS Monterey and Intel Core i7

  3. What version of Java are you using?
    Java 8

  4. What did you do?
    Tried to interrupt the thread (using Thread.interrupt()) while performing JDBC calls, but the thread did not interrupt properly. However, thread.isInterrupted() returns true.

import java.sql.*;

class SnowflakeJDBCExample implements Runnable {
    static String SNOWFLAKE_URL = "jdbc:snowflake:**********";
    static String USERNAME = "**********";
    static String PASS = "**********";
    @Override
    public void run() {
        try {
            Connection connection = DriverManager.getConnection(SNOWFLAKE_URL, USERNAME, PASS);
            System.out.println("Connection Established");

            ResultSet resultSet;
            while(true) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }
        } catch (Exception e) {
            System.out.println("Exception occurred while performing IO operations: "+ e);

            StackTraceElement[] stackTrace = e.getStackTrace();

            System.out.println("---------------------------------");
            if (stackTrace != null) {
                for (StackTraceElement element : stackTrace) {
                    System.out.println(element.toString());
                }
            }
            System.out.println("---------------------------------");
        } finally {
            System.out.println("Executing finally block");
            Test.snowflakeJDBCExample = null;
        }
    }
}


public class Test {

    static volatile SnowflakeJDBCExample snowflakeJDBCExample = new SnowflakeJDBCExample();
    public static void main(String[] args) throws SQLException, InterruptedException {
        try {

            Thread thread = new Thread(snowflakeJDBCExample);

            thread.start();
            System.out.println("Calling thread.isInterrupted() before make interrupt call: "+thread.isInterrupted());

            Thread.sleep(10000);

            System.out.println("Calling thread.isInterrupted() before make interrupt call: "+thread.isInterrupted());

            thread.interrupt();

            System.out.println("Calling thread.isAlive() after made interrupt call "+thread.isAlive());
            System.out.println("Calling thread.isInterrupted() after made interrupt call"+thread.isInterrupted());

            while(!thread.isInterrupted()) {
                thread.interrupt();
                System.out.println();
                Thread.sleep(60000);

                StackTraceElement[] stackTrace = thread.getStackTrace();
                if (stackTrace != null) {
                    for (StackTraceElement element : stackTrace) {
                        System.out.println(element.toString());
                    }
                }
            }

            while(snowflakeJDBCExample!=null);

            //Printing the thread call stack trace
            StackTraceElement[] stackTrace = thread.getStackTrace();
            if (stackTrace != null) {
                for (StackTraceElement element : stackTrace) {
                    System.out.println(element.toString());
                }
            }

            System.out.println("Calling thread.isAlive() after made interrupt call "+thread.isAlive());
            System.out.println("Calling thread.isInterrupted() after made interrupt call"+thread.isInterrupted());
        } catch (Error | Exception e) {
            System.out.println("Exception occurred in main thread: "+e);
        }

    }
}
  1. What did you expect to see?
    Expected the interrupt to happen properly, but it did not.

I tried the same scenario with JDBC version 3.13.30, and it throws a below exception. So, the execution is stopped

net.snowflake.client.jdbc.SnowflakeSQLLoggedException: JDBC driver internal error: Fail to retrieve row count for first arrow chunk: null.

net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.setFirstChunkRowCountForArrow(SnowflakeResultSetSerializableV1.java:1109)
net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.create(SnowflakeResultSetSerializableV1.java:588)
net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.create(SnowflakeResultSetSerializableV1.java:489)
net.snowflake.client.core.SFResultSetFactory.getResultSet(SFResultSetFactory.java:29)
net.snowflake.client.core.SFStatement.executeQueryInternal(SFStatement.java:214)
net.snowflake.client.core.SFStatement.executeQuery(SFStatement.java:129)
net.snowflake.client.core.SFStatement.execute(SFStatement.java:742)
net.snowflake.client.core.SFStatement.execute(SFStatement.java:652)
net.snowflake.client.jdbc.SnowflakeStatementV1.executeQueryInternal(SnowflakeStatementV1.java:251)
net.snowflake.client.jdbc.SnowflakeStatementV1.executeQuery(SnowflakeStatementV1.java:136)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.executeAndReturnEmptyResultIfNotFound(SnowflakeDatabaseMetaData.java:3439)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.getTableTypes(SnowflakeDatabaseMetaData.java:1612)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.getTables(SnowflakeDatabaseMetaData.java:1428)
org.example.snowflake.SnowflakeJDBCExample.run(Test.java:17)
java.lang.Thread.run(Thread.java:750)

However, this above exception does not occur in versions 3.13.29 and 3.13.33. As a result, the thread does not get interrupted, and it continues to run continuously without responding to the interruption call.

@github-actions github-actions bot changed the title Thread.interrupt() does not work SNOW-872545: Thread.interrupt() does not work Jul 20, 2023
@sfc-gh-spanaite sfc-gh-spanaite self-assigned this Oct 3, 2023
@sfc-gh-igarish
Copy link
Collaborator

Similar issue: #1461

@sfc-gh-spanaite
Copy link
Contributor

It works for me like this. In this program, after interrupting the thread, I throw an exception.
Look at this example based on yours:

import java.sql.*;

public class ThreadTest {

    static volatile SnowflakeJDBCExample snowflakeJDBCExample = new SnowflakeJDBCExample();
    public static void main(String[] args) throws SQLException, InterruptedException {
	    Thread t1 = new Thread(snowflakeJDBCExample, "thread1");

	    t1.start();

	    try {
		    t1.interrupt();
	    } catch(Exception e) { 
		    System.out.println("Exception handled "+e);
	    }
    }
}

class SnowflakeJDBCExample implements Runnable {
    static String SNOWFLAKE_URL = "jdbc:snowflake://XXXX.snowflakecomputing.com/?&tracing=ALL";
    static String USERNAME = "XXXX";
    static String PASS = "XXXX";
    @Override
    public void run() {
	   try{ 
		   Connection connection = DriverManager.getConnection(SNOWFLAKE_URL, USERNAME, PASS);
		   System.out.println("Connection Established");
		   
		   ResultSet resultSet;
		   
		   System.out.println("Current running thread: " + Thread.currentThread().getName());
		   
		   try {
			   Thread.sleep(10000);
			   resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
			   System.out.println("Meta fetch completed");
		   } catch (InterruptedException e) {
			   throw new RuntimeException("Thread interrupted " + e);
		   }
	   } catch (Exception e) {
		   System.out.println("Exception occurred while performing IO operations: "+ e);
		   StackTraceElement[] stackTrace = e.getStackTrace();
		   System.out.println("---------------------------------");
		   if (stackTrace != null) {
			   for (StackTraceElement element : stackTrace) {
				   System.out.println(element.toString());
			   }
		   }
		   System.out.println("---------------------------------");
	   } finally {
		   System.out.println("Executing finally block");
		   ThreadTest.snowflakeJDBCExample = null;
	   }
    }
}

When I run it:

$ java -cp .:snowflake-jdbc-3.14.2.jar ThreadTest.java
Connection Established
Current running thread: thread1
Exception occurred while performing IO operations: java.lang.RuntimeException: Thread interrupted java.lang.InterruptedException: sleep interrupted
---------------------------------
SnowflakeJDBCExample.run(ThreadTest.java:38)
java.base/java.lang.Thread.run(Thread.java:1589)
---------------------------------
Executing finally block

Keep in mind that interrupt() primarily sets a flag in the Thread object, which you can check with isInterrupted(). It also causes some methods like sleep() to return immediately by throwing an InterruptedException. This is what happens in my case.

In your case, you interrupt the thread but you don't do anything with it, therefore no exception is thrown. You never get into this block while(!thread.isInterrupted()).

cc: @sfc-gh-wfateem FYI for your issue 1461

@MohamedKamarudeen
Copy link
Author

@sfc-gh-spanaite - Your program throws an InterruptedException when your thread is sleeping, not while executing connection.getMetaData().getTables().

In my case, the thread does not interrupt even if I make a Thread.interrupt call while executing connection.getMetaData().getTables().

@sfc-gh-spanaite
Copy link
Contributor

Hi @MohamedKamarudeen that is correct, but with most database drivers, instead of using Thread's interrupt() method, the preferred method would be to invoke cancel() on the Statement that's being executed.
For Snowflake, as far as I can see invoking interrupt() has no effect.

On the other hand, why do you need to use thread interrupt when running JDBC statements?

@MohamedKamarudeen
Copy link
Author

Hi @sfc-gh-spanaite, Let's assume that connection.getMetaData().getTables() takes a long time, and need to stop my process at that point. To stop the process, I interrupt the thread and wait for a minute. If the interruption doesn't complete within a minute, I turn to making a Thread.stop() call. If we make the Thread.stop() call then #1461 issue occurs
It would be good if we fix this problem.

It's important to note that we're not using a Statement object here; we're using connection.getMetaData().getTables(), which means there is no opportunity for using Statement.cancel().

@sfc-gh-spanaite
Copy link
Contributor

If it's only for connection.getMetaData() I would think this would be more of an improvement request rather than a bug.

@MohamedKamarudeen
Copy link
Author

@sfc-gh-spanaite - I feel that connection.getMetaData().getTables() should honor the Thread.interrupt() call. The interrupt call is working fine in JDBC version 3.13.30, and it throws the following exception:


net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.setFirstChunkRowCountForArrow(SnowflakeResultSetSerializableV1.java:1109)
net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.create(SnowflakeResultSetSerializableV1.java:588)
net.snowflake.client.jdbc.SnowflakeResultSetSerializableV1.create(SnowflakeResultSetSerializableV1.java:489)
net.snowflake.client.core.SFResultSetFactory.getResultSet(SFResultSetFactory.java:29)
net.snowflake.client.core.SFStatement.executeQueryInternal(SFStatement.java:214)
net.snowflake.client.core.SFStatement.executeQuery(SFStatement.java:129)
net.snowflake.client.core.SFStatement.execute(SFStatement.java:742)
net.snowflake.client.core.SFStatement.execute(SFStatement.java:652)
net.snowflake.client.jdbc.SnowflakeStatementV1.executeQueryInternal(SnowflakeStatementV1.java:251)
net.snowflake.client.jdbc.SnowflakeStatementV1.executeQuery(SnowflakeStatementV1.java:136)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.executeAndReturnEmptyResultIfNotFound(SnowflakeDatabaseMetaData.java:3439)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.getTableTypes(SnowflakeDatabaseMetaData.java:1612)
net.snowflake.client.jdbc.SnowflakeDatabaseMetaData.getTables(SnowflakeDatabaseMetaData.java:1428)
org.example.snowflake.SnowflakeJDBCExample.run(Test.java:17)
java.lang.Thread.run(Thread.java:750)

This exception does not occur in versions 3.13.29 and 3.13.33. As a result, the thread does not get interrupted, and it continues to run continuously without responding to the interrupt call.

So, I'm considering this a bug.

@sfc-gh-spanaite
Copy link
Contributor

Give me a bit more time to have another look at the differences between 3.13.30 and 3.13.33 to see why an exception would be thrown in 3.13.32. I'll get back with more information.

@sfc-gh-dszmolka
Copy link
Contributor

sorry for leaving this unanswered. Does the same issue occur with latest driver version 3.15.1 ?

@sfc-gh-dszmolka sfc-gh-dszmolka added the status-triage Issue is under initial triage label Apr 26, 2024
@sfc-gh-wfateem sfc-gh-wfateem self-assigned this Apr 27, 2024
@sfc-gh-wfateem sfc-gh-wfateem added invalid status-triage_done Initial triage done, will be further handled by the driver team and removed bug status-triage Issue is under initial triage labels Apr 27, 2024
@sfc-gh-wfateem
Copy link
Collaborator

The issue here is that you're catching all Exceptions with a very generic catch clause and then you're not handling that exception, so you essentially have an infinite loop in your class implementing Runnable:

        try {
            Connection connection = DriverManager.getConnection(SNOWFLAKE_URL, USERNAME, PASS);
            System.out.println("Connection Established");

            ResultSet resultSet;
            while(true) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }
        } catch (Exception e) {
            System.out.println("Exception occurred while performing IO operations: "+ e);

You should be checking if the thread is interrupted from within the thread itself. So for example, changing the while loop in the SnowflakeJDBCExample class would react to the interrupt call you're making from the main method:

class SnowflakeJDBCExample implements Runnable {
...
            while(!Thread.currentThread().isInterrupted()) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }

Running the application code will result in the following:

Calling snowflakeThread.isInterrupted() before make interrupt call: false
Connection Established
Meta fetch completed
....
Meta fetch completed
Calling snowflakeThread.isInterrupted() before make interrupt call: false
Calling snowflakeThread.isAlive() after made interrupt call true
Calling snowflakeThread.isInterrupted() after made interrupt calltrue
Meta fetch completed
Executing finally block

I'm going to close off this issue, if you still have any questions or concerns then please feel free to open it again.

@MohamedKamarudeen
Copy link
Author

@sfc-gh-wfateem - Thanks for taking look at this. You need to interrupt the thread when connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"}) is in processing state then only this issue will reproduce. I'm re-opening this ticket.

@sfc-gh-wfateem sfc-gh-wfateem reopened this May 3, 2024
@sfc-gh-wfateem
Copy link
Collaborator

sfc-gh-wfateem commented May 3, 2024

@MohamedKamarudeen I'm not sure I fully understand, so I'm going to walk you through my understanding of what you were trying to demonstrate as the problem based on the code snippet you provided.

This is your problem statement:

Tried to interrupt the thread (using Thread.interrupt()) while performing JDBC calls, but the thread did not interrupt properly. However, thread.isInterrupted() returns true.

The thread in question that you're trying to interrupt is implemented here:

class SnowflakeJDBCExample implements Runnable {
    static String SNOWFLAKE_URL = "jdbc:snowflake:**********";
    static String USERNAME = "**********";
    static String PASS = "**********";
    @Override
    public void run() {
        try {
            Connection connection = DriverManager.getConnection(SNOWFLAKE_URL, USERNAME, PASS);
            System.out.println("Connection Established");

            ResultSet resultSet;
            while(true) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }
        } catch (Exception e) {
            System.out.println("Exception occurred while performing IO operations: "+ e);

            StackTraceElement[] stackTrace = e.getStackTrace();

            System.out.println("---------------------------------");
            if (stackTrace != null) {
                for (StackTraceElement element : stackTrace) {
                    System.out.println(element.toString());
                }
            }
            System.out.println("---------------------------------");
        } finally {
            System.out.println("Executing finally block");
            Test.snowflakeJDBCExample = null;
        }
    }
}

In your main method in the Test class, you're attempting to stop this thread by doing this:

            Thread thread = new Thread(snowflakeJDBCExample);

            thread.start();
            System.out.println("Calling thread.isInterrupted() before make interrupt call: "+thread.isInterrupted());

            Thread.sleep(10000);

            System.out.println("Calling thread.isInterrupted() before make interrupt call: "+thread.isInterrupted());

            thread.interrupt();

After you call thread.start() your snowflakeJDBCExample thread is continuously running in the background invoking this line of code in an infinite-loop:

resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});

Your code then puts the main thread to sleep by calling Thread.sleep(10000);

I'm assuming the objective of that is to just ensure that you're calling the connection.getMetaData() method several times before trying to interrupt it.

Once your main method wakes up again, you attempt to interrupt your snowflakeJDBCExample thread by calling thread.interrupt()

Your expectation is that the snowflakeJDBCExample thread should stop, but it didn't. So running your application as it is results in the following output:

Calling thread.isInterrupted() before make interrupt call: false
Connection Established
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
...

That goes on until 10 seconds, since you put the main thread to sleep for that long, and then after the 10 seconds have elapsed you get the following output:

Calling thread.isInterrupted() before make interrupt call: false
Calling thread.isAlive() after made interrupt call true
Calling thread.isInterrupted() after made interrupt calltrue
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed

That continues on forever, so based on your problem statement, you're saying that this should have stopped especially that the output of your thread.isInterrupted() call was true.

If everything I'm saying here makes sense and I'm understanding your problem correctly, then the problem is mainly here in this block:

            while(true) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }

The JDK docs indicate that calling interrupt() might not always result in an exception being raised. There are certain conditions it outlines that will result in an exception.

In your case, you never catch an exception when you run your code, so calling interrupt() on your thread will simply set a flag to true. This is why you get a value of true when you test whether or not the thread has been interrupted by calling thread.isInterrupted(). Even if an exception was raised, your code is just catching it and you print something in the console, so you're not doing anything to explicitly stop the thread. However, as I mentioned, an exception is not raised so it doesn't matter.

Unless you do something about the isInterrupted flag becoming true, your code is going to run continuously in a loop, so you can't expect the thread to stop at that point. I'm not clear on what your expectations are from the JDBC code. The thread you're interrupting is your application's thread. Therefore, by changing the loop to do this instead:

while(!Thread.currentThread().isInterrupted()) {
                resultSet = connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});
                System.out.println("Meta fetch completed");
            }

The output we get from the new application code is this:

Calling thread.isInterrupted() before make interrupt call: false
Connection Established
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
Meta fetch completed
...

Again, this goes on for 10 seconds until the main thread wakes up and makes an interrupt() call, which then results in:

Meta fetch completed
Calling thread.isInterrupted() before make interrupt call: false
Calling thread.isAlive() after made interrupt call true
Calling thread.isInterrupted() after made interrupt calltrue
Meta fetch completed
Executing finally block
[email protected]/java.util.IdentityHashMap$KeySet.iterator(IdentityHashMap.java:977)
[email protected]/java.util.Collections$SetFromMap.iterator(Collections.java:5513)
[email protected]/jdk.internal.misc.TerminatingThreadLocal.threadTerminated(TerminatingThreadLocal.java:69)
[email protected]/java.lang.Thread.exit(Thread.java:844)
Calling thread.isAlive() after made interrupt call false
Calling thread.isInterrupted() after made interrupt callfalse

If there's something I'm still missing here then can you help clarify that for me, please?

What does the following expectation mean for you?

Expected the interrupt to happen properly, but it did not.

What does a proper interruption mean or look like for you?

@MohamedKamarudeen
Copy link
Author

@sfc-gh-wfateem - My objective with this program is to interrupt the thread during the processing of the method call connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});.

Reproduction Steps:

  1. Establish the Snowflake JDBC connection.
  2. Initiate the thread interrupt call while Snowflake JDBC processes the request. In this case, our request is connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});.
  3. The interrupt call does not terminate; it waits indefinitely.
    NOTE: The Thread.interrupt call is invoked while executing connection.getMetaData().getTables().

The issue is that Snowflake JDBC neither returns nor throws any exceptions when the thread interrupt call is invoked during the processing of connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});.

I expect the thread interrupt call to either complete or throw an exception.

As i mentioned in the comment , this issue(interrupt call waits indefinitely) does not happen in 3.13.30.

@sfc-gh-wfateem
Copy link
Collaborator

@MohamedKamarudeen when you execute the following method in your environment on its own, how long does that take to return?
connection.getMetaData().getTables(null, "%", "%", new String[]{"TABLE", "VIEW", "ALIAS", "SYNONYM"});

@sfc-gh-wfateem sfc-gh-wfateem closed this as not planned Won't fix, can't repro, duplicate, stale Sep 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
invalid status-triage_done Initial triage done, will be further handled by the driver team
Projects
None yet
Development

No branches or pull requests

5 participants