0

Currently I am working on an Report generator based application as part of which when ever a user logs in and tries to download a report he will end up in calling below method as part of flow

which executes list of queries to fetch required data and return result.

Note : some queries can take 40-60 mins to execute.

Nowadays I did come up across a strange issue where I could see some times the report never resumes after triggering post below code [ Observation was found via logs].

So I suspect does Thread.yield() can result into such scenario's. Since all reports invoking this code flow having same thread priority.

I am not sure the use of Thread.yield in this flow as this is an legacy app, So can anyone guide me if such code can result in infinite waiting state for an thread?

Below is the basic Pseudo code representation of flow, Kindly let me know if any additional information is required. Thanks in advance

Class A {

void m1(reportName) {
 try {

// an unique request id gets created for each report generation request
       Callable<ReportDownload>  = new Download(request, requestid);
       FutureTask<ReportDownload> futureTask = new FutureTask<>(download);
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.execute(futureTask);
        downloadResponse = futureTask.get();
         executorService.shutdown();download
     } catch (Throwable drt) {
        // logs 
    } finally {
         // does terminal operations
   }
 }
Class Download {

  genrate(reortName) {
    List sqllist = // fecth all 3 queries related to connector and store in lis
    runQueries(sqllist)
  }
 
 //  the list sqllist will always have 3 queries and these 3 queries will differ for each and every report
 
 List runQueries(sqllist) {
 
 
 List list = new ArrayList() // used to store result of each query
 for (int i = 0; i != sqllist.size(); ++i) {
       Thread.yield();
       DBConnection dbconnection = null;
       try {
       dbconnection = // creates db connection
       String query = sqllist.get(i);
       /* uses DB connection pool and exectes query and store result
        * someting like as shown below 
       */ And there is no issue with query or dbconnection
       DBResult DBResult= dbconnection.retrieve(sql);
       // proces returnes result and store in list.
       list.add(DBResult);
       }
       catch (SQLException e)  {
       // log sql exception
       }
       
    }  
    
    return list;
    
}
       
              ```
srikanth r
  • 302
  • 3
  • 20
  • Yes @Abra it is implementing Callable I have just written a kind of pseudo code to give overall representation of code flow. – srikanth r Jan 17 '22 at 11:44
  • okay sure @Abra Have added it as pseudo code in description. Here as my prime suspect of code is "Thread.yield();" So just added code relevant to it rather than adding complete class – srikanth r Jan 17 '22 at 11:57
  • 1
    Related: https://stackoverflow.com/questions/6979796/what-are-the-main-uses-of-yield-and-how-does-it-differ-from-join-and-interr – tevemadar Jan 17 '22 at 12:07
  • @tevemadar Thank you the reference was very much valuable. – srikanth r Jan 17 '22 at 12:36

1 Answers1

1

So I suspect does Thread.yield() can result into such scenario's. Since all reports invoking this code flow having same thread priority.

The short answer is no. You are going in the wrong direction. See below if you are really stuck on thinking that Thread.yield() is the issue.

In looking at your pseudo code, I'm really confused by the following which just may be how it is represented in your post. If the code is really like the following then there are bugs. I've annotated each line with my thoughts:

Callable<ReportDownload>  = new Download(request, requestid);
// the only time you need `FutureTask` is if you want a `Runnable` to
// return something.  Wrapping a Callable in a `FutureTask` seems like a mistake
FutureTask<ReportDownload> futureTask = new FutureTask<>(download);
// why do this at all?  Why not call in the current thread?
ExecutorService executorService = Executors.newFixedThreadPool(1);
// I just about never use execute because there is no way to catch exceptions
executorService.execute(futureTask);
// if your job throws this will hang, prolly the source of your bug
downloadResponse = futureTask.get();
executorService.shutdown();

I think your use of FutureTask here is flawed. You should just submit your Callable to the executorService and then use the Future returned. I almost never call ExecutorService.execute() especially because if your query throws an exception, you won't be able to see it. I bet that is your problem. If your task throws an exception (maybe a timeout exception) then the FutureTask.get() call will never return.

// maybe this should be newCachedThreadPool and run all queries?
ExecutorService executorService = Executors.newFixedThreadPool(1);
Callable<ReportDownload> download = new Download(request, requestid);
Future<ReportDownload> future = executorService.submit(download);
// get() waits for the job to complete, if you are submitting multiple queries
// then put the futures in a list and call get on them after you call shutdown()
ReportDownload downloadResponse = future.get();
executorService.shutdown();

Also, why not just call this in the calling thread? Why do you need a thread pool? If you are trying to fork multiple queries then you should use one thread pool and submit a bunch of Download objects to it but I only see one thread being created here.

Yeah the following code hangs. I suspect that you super long running query is throwing an exception causing your program to hang.

// you should not use FutureTask to wrap a Callable
FutureTask<Void> futureTask = new FutureTask<Void>(new Callable<Void>() {
    public Void call() throws Exception {
        throw new Exception();
    }
});
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(futureTask);
// bug: this hangs here because the task never completes
futureTask.get();
executorService.shutdown();

If you really think it has something to do with the yield, take a look at the Thread.yield() javadocs mention:

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint... It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions.

What yield does depends highly on the underlying native thread implementation and operating system but I suspect that it does very little in this situation. I suspect that removing it from your program will not change the frequency of hangs that you have seeing.

Gray
  • 115,027
  • 24
  • 293
  • 354