Kaarel Purde
Kaarel Purde

Reputation: 1265

Non-public constructor causing problems with Tomcat7?

I have Java app with Spring running on tomcat.

This class is causing a very strange problem for me:

@WebListener
public class ThreadPool extends ThreadPoolExecutor implements ServletContextListener {

  private ThreadPool() {
    super(MIN_ACTIVE_THREADS, MAX_ACTIVE_THREADS, DEACTIVATE_THREADS_AFTER_TIMEPERIOD, TimeUnit.SECONDS, taskQueue);
  }

  private static final ThreadPoolExecutor pool = new ThreadPool();

  public synchronized static void submit(Task task) {
      executingTasks.add(task);
      pool.execute(task);
  }

  @Override
  public synchronized void contextDestroyed(ServletContextEvent arg0) {
    cancelWaitingTasks();
    sendStopSignalsToExecutingTasks();
    pool.shutdown();
  }

  ...

}

If the constructor is private or default I get this exception during runtime (on first HTTP request to the app):

Error configuring application listener of class com.testApp.util.ThreadPool
java.lang.IllegalAccessException: Class org.apache.catalina.core.DefaultInstanceManager can not access a member of class com.testApp.util.ThreadPool with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.Class.newInstance(Class.java:436)
    at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:140)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4888)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5467)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Skipped installing application listeners due to previous error(s)
Error listenerStart
Context [] startup failed due to previous errors

But if i set the constructor public then I get no exceptions and everything works fine. Can anyone tell me why is this default or private constructor causing runtime exceptions?

Upvotes: 0

Views: 293

Answers (4)

Kaarel Purde
Kaarel Purde

Reputation: 1265

I think I accidentally discovered the real reason I was getting exceptions. Currently I am using this class, no exceptions thrown, tested on GlassFish and Tomcat:

public class TrackingThreadPool extends ThreadPoolExecutor {

    private static final int MAX_WAITING_TASKS = 4000;
    private static final int MAX_ACTIVE_THREADS = 20;
    private static final int MIN_ACTIVE_THREADS = 4;
    private static final int DEACTIVATE_THREADS_AFTER_SECONDS = 60;

    private TrackingThreadPool() {
        super(MIN_ACTIVE_THREADS, MAX_ACTIVE_THREADS, DEACTIVATE_THREADS_AFTER_SECONDS,
                TimeUnit.SECONDS, waitingTasks);
    }

    private static final BlockingQueue<Runnable> waitingTasks = new LinkedBlockingQueue<>(MAX_WAITING_TASKS);

    private static final Map<Long, Task> executingTasks = new HashMap<>(MAX_ACTIVE_THREADS * 2);

    private static final TrackingThreadPool instance = new TrackingThreadPool();

    public synchronized static void submitAndTrack(Task task) {
        executingTasks.put(task.getId(), task);
        instance.execute(task);
    }

    public synchronized static void shutdownAndCancelAllTasks() {
        cancelWaitingTasks();
        sendStopSignalToExecutingTasks();
        instance.shutdown();
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (r instanceof Task) {
            executingTasks.remove(((Task) r).getId());
        }
    }

private static void cancelWaitingTasks() {
    List<Runnable> waitingTaskListRunnables = new ArrayList<>(waitingTasks.size() + 10); //+10 to avoid resizing
    waitingTasks.drainTo(waitingTaskListRunnables);
    for (Runnable r : waitingTaskListRunnables) {
        if (r instanceof Task) {
            ((Task) r).sendStopSignal(byShutdownMethod());
        }
    }
}

    private static void sendStopSignalToExecutingTasks() {
        for (long taskId : executingTasks.keySet()) {
            Task executingTask = executingTasks.get(taskId);
            executingTask.sendStopSignal(byShutdownMethod());
        }
    }

    private static String byShutdownMethod() {
        return TrackingThreadPool.class.getSimpleName() + "#shutdownAndCancelAllTasks()";
    }
}

And if I swap the positions of BlockingQueue<Runnable> waitingTasks and TrackingThreadPool instance like this:

private static final TrackingThreadPool instance = new TrackingThreadPool();

private static final Map<Long, Task> executingTasks = new HashMap<>(MAX_ACTIVE_THREADS * 2);

private static final BlockingQueue<Runnable> waitingTasks = new LinkedBlockingQueue<>(MAX_WAITING_TASKS);

I get exceptions again because waitingTasks is not instantiated by the time I make a new TrackingThreadPool instance.

I guess you can have a subclass of ThreadPoolExecutor with a private constructor / singelton pattern.

Upvotes: 0

hagrawal7777
hagrawal7777

Reputation: 14668

Tomcat's org.apache.catalina.core.DefaultInstanceManager is trying to create an object of your ThreadPool which you have configured as context listener. Now, since this is outside of org.apache.catalina.core you have to use a public constructor else org.apache.catalina.core.DefaultInstanceManager will not be able to create its object.

From org.apache.catalina.core.DefaultInstanceManager

private Object newInstance(Object instance, Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException {
    if (!ignoreAnnotations) {
        Map<String, String> injections = injectionMap.get(clazz.getName());
        processAnnotations(instance, injections);
        postConstruct(instance, clazz);
    }
    return instance;
}

Upvotes: 2

wero
wero

Reputation: 32990

Tomcat uses Class.newInstance() to create an instance of your ThreadPool. This method obeys the access rules of Java.

Since your constructor is private it fails with a IllegalAccessException. This is the runtime equivalent of not being allowed to call a function to the compiler error which you see if you would try to write new ThreadPool() outside of ThreadPool,

Upvotes: 4

Pao Im
Pao Im

Reputation: 347

Through the error, it said clearly because it cannot access a member of class.

can not access a member of class com.testApp.util.ThreadPool with modifiers

Upvotes: 1

Related Questions