Search code examples
javamultithreadinghttpquartz-schedulerjvisualvm

Threads running amuck with AsyncHttpClient and Quartz Job


Here is a simple Quartz scheduler which should run a job once every minute; the job in itself makes an HTTP request using the Sonatype Async Http Client. Using jvisualvm, I was able to detect threads spawning and never closing, e.g. they are stuck in wait. This leads me to believe that either A) I'm misunderstanding how Quartz works with this particular setup, or B) Something else is wrong. Probably A :) The scheduler:

public class QuartzAsyncHttpThreadTest {

    /* TEST */
    @SuppressWarnings("rawtypes")
    private static Class jobToRun = AsyncHttpRequestJob.class;
    private static String cron = "0 0/1 * * * ?";
    /* TEST */


    public static void main(String[] args) throws SchedulerException {

        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
        scheduler.start();

        start(scheduler, jobToRun.getName(), jobToRun);

    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void start(Scheduler scheduler, String name, Class job)
            throws SchedulerException {

        JobKey monitorKey = new JobKey(name + "_job", "jobs");
        JobDetail detail = JobBuilder.newJob(job).withIdentity(monitorKey)
                .build();

        Trigger cronDef = TriggerBuilder.newTrigger()
                .withIdentity(name + "_trigger", "triggers")
                .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();

        scheduler.scheduleJob(detail, cronDef);

    }

}

The job:

public class AsyncHttpRequestJob implements Job {

    public AsyncHttpRequestJob() {

    }

    public void execute(JobExecutionContext context)
            throws JobExecutionException {

        System.out.println("Go..");

        makeRequest();

    }

    public static void makeRequest() {

        try {

            Future<Response> r = new AsyncHttpClient().prepareGet(
                    "http://google.com").execute();

            Response response = r.get();

            System.out.println("Request status: " + response.getStatusCode()); // 301

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

There's really not much to it. If I profile live threads, the encroachment is noticeable after 2 minutes. First 24, than 27, 30...and so on. enter image description here

EDIT:

I was able to verify that it's a combination of the AsyncHttpClient and Quartz, as when I swap out the makeRequest method with a standard request:

    URL conn = new URL("http://google.com");
    URLConnection httpR = conn.openConnection();
    BufferedReader in = new BufferedReader(new InputStreamReader(
            httpR.getInputStream()));
    String inputLine;

    while ((inputLine = in.readLine()) != null)
        System.out.println(inputLine);
    in.close();

All works as expected.


Solution

  • You must close every AsyncHttpClient instance created here:

    Future<Response> r = new AsyncHttpClient().prepareGet(
                    "http://google.com").execute();
    

    using close() method. Each instance of AsyncHttpClient creates some resources (like threads) that have to be cleaned-up).

    However, since AsyncHttpClient is thread-safe, much better approach is to create only one, global instance of AsyncHttpClient and reuse it throughout the lifetime of your application and from multiple threads.

    Finally, since you basically send some request and wait synchronously (blocking) for a response, why not use standard URLConnection (as in your example) or HttpClient? AsyncHttpClient is great when you don't want to wait for a response synchronously, e.g. when you want to initiate hundreds of HTTP requests concurrently without spawning hundreds of threads. AsyncHttpClient will then call your callback code as responses appear.