0

I have a Java SE project that accepts will take some command line arguments are perform processing in a separate thread for specified argument. I am using the following weld microprofile config dependencies for injection

<dependency>
  <groupId>org.jboss.weld.se</groupId>
  <artifactId>weld-se-core</artifactId>
  <version>3.1.0.Final</version>
</dependency>
<dependency>
  <groupId>org.wildfly</groupId>
  <artifactId>wildfly-microprofile-config-implementation</artifactId>
  <version>1.2.1</version>
</dependency>

Here is my beans.xml

<?xml version="1.0"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
version="1.1" bean-discovery-mode="all">
  <decorators>  
    <class>org.jboss.weld.environment.se.threading.RunnableDecorator</class>  
  </decorators>
</beans>    

My project starts with the following main Class

@ApplicationScoped
public class Main {
  @Inject
  private Scheduler scheduler;

  public void process(List<String> types) throws InterruptedException {
    scheduler.schedule(types);
  }

  public static void main(String[] args) throws InterruptedException {
    SeContainerInitializer initializer = SeContainerInitializer.newInstance();
    try (SeContainer container = initializer.initialize()) {
      Main main = container.select(Main.class).get();
      List<String> argsList = Arrays.asList(args);
      final List<String> types = parseArguments(argsList);
      main.process(types);
    }
  }
}

Here is the code for my Scheduler class

@ApplicationScoped
public class Scheduler {
  private static final Duration DEFAULT_WAIT_TIME = Duration.ofSeconds(30);

  @Inject 
  @ConfigProperty(name = "POOL_SIZE", defaultValue = "10")
  @Getter
  private int poolSize = 5;

  @Inject 
  @ConfigProperty(name = "WAIT_DURATION", defaultValue = "PT30S")
  @Getter
  private String durationStr;

  @Getter
  private Duration waitDuration;

  private ThreadPoolExecutor executor;

  @Inject
  private Instance<ExportRunner> exports;

  @PostConstruct
  public void init() {
    executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(poolSize);

    try {
      waitDuration = Duration.parse(durationStr);
    } catch (DateTimeException | NullPointerException e) {
      waitDuration = DEFAULT_WAIT_TIME;
    }
  }

  public void schedule(Collection<String> types) throws InterruptedException {
    if (types != null && !types.isEmpty()) {
      //Spawn a new thread for each type
      for(String type : types) {
        ExportRunner runner = exports.get();
        runner.setType(type);
        executor.submit(runner);
      }
    } else {
      throw new IllegalArgumentException("No FileTypes provided. Not performing export");
    }

    //Wait until every thread has completed
    while(getTotalThreads() > 0) {
      Thread.sleep(waitDuration.toMillis());
    }

    //shutdown executor which effectively ends the program
    executor.shutdownNow();  
  }

  public int getTotalThreads() {
    return getActiveCount() + getQueueSize();
  }

  public int getActiveCount() {
    return executor.getActiveCount();
  }

  public int getQueueSize() {
    return executor.getQueue().size();
  }
}

Here is the skeleton of the Runnable

@Dependent
public class ExportRunner implements Runnable {
  @Setter
  private FileType type;

  //Has a custom producer and disposer
  @Inject
  @SEDataSource
  private EntityManager em;

  //Has a custom producer and disposer
  @Inject
  @SEDataSource
  AlertService alertService;

  //Has a custom producer and disposer
  @Inject
  @SEDataSource
  HistoryService historyService;

  @PostConstruct
  private void init() {
    //Set to same entity manager so that 
    //everythings happen inside single transaction
    alertService.setEm(em);
    historyService.setEm(em);
  }

  @PreDestroy
  public void cleanup() {
    log.info("ExporterRunner @PreDestroy was called");
  }

  public void run() {
    try {
      //do processing
    } finally {
      log.info("Processing Complete");
    }
  }
}

The issue that I am running into is that the injected Objects (the Runnable and the contained services and EntityManager) and never getting released until all of the threads have completed and the executor.shutdown() command is executed.

I believe that since the Runner is marked as @Dependent, it is using the Scope of the Object that injected it; which would make it @ApplicationScoped. I tried to mark the class with the @ThreadScoped (org.jboss.weld.environment.se.contexts.ThreadScoped) as follows

@ThreadScoped
public class ExportRunner implements Runnable {
  ...
}

But that causes the following exception

org.jboss.weld.contexts.ContextNotActiveException: WELD-001303: No active contexts for scope type org.jboss.weld.environment.se.contexts.ThreadScoped

I feel like I need to use the @ActivateThreadScope (org.jboss.weld.environment.se.contexts.activators.ActivateThreadScope) annotation, but I haven't found any examples of how to use it. Does anyone know how I make my Runnable not @ApplicationScoped?

Mike
  • 820
  • 7
  • 19
  • What do you mean with "released"? – LppEdd Mar 12 '19 at 21:21
  • `@ActivateThreadScope` is an interceptor. Simply place it above a method and it will activate the scope before the method and shut it down afterwards. You can glance [in the docs](http://docs.jboss.org/weld/reference/latest/en-US/html_single/#_thread_context). – Siliarus Mar 13 '19 at 08:33
  • I tried wrapping the call to spawn the threads in a local method and added the @ActivateThreadScope annotation. When I attempt to set the type in the Runnable, it get the context not created error. If I remove that call and submit it to the executor, the Thread simply doesn't run. The executor accepts the Runnable but it never actually runs. – Mike Mar 13 '19 at 14:29

1 Answers1

0

So it appears that I have been using the @ThreadScoped annotation incorrectly. It does not go on the Runnable class, but on the producers for the injected beans. With my updated code, the injected EntityManager and services are getting diposed, but the Runnable itself is not getting disposed until the Weld container shuts down.

Here is my producers

public class ProjectProducer {

  //Producer needs to have the @ThreadScopes
  @Produces
  @SEDataSource
  @ThreadScoped
  EntityManager openEntityManager() {
    log.info("Creating Entity manager");
    ...
  }

  //Note that @ThreadScoped is not required for Disposer
  public void closeEntityManager(@Disposes @SEDataSource EntityManager em) {
     log.info("Disposing of EntityManager");
  }

  //Producer needs to have the @ThreadScopes
  @Produces
  @ThreadScoped
  @SEDataSource
  public AlertService createAlertService() {
    log.info("Creating Alert Service");
    ...
  }

  //Note that @ThreadScoped is not required for Disposer
  public void disposeAlertService(@Disposes @SEDataSource AlertService alertService) {
    log.info("Disposing AlertService");
  }

  //Producer needs to have the @ThreadScopes
  @Produces
  @ThreadScoped
  @SEDataSource
  public FileHistoryService createFileHistoryService() {
    log.info("Creating History Service");
  }

  //Note that @ThreadScoped is not required for Disposer
  public void disposeFileHistoryService(@Disposes @SEDataSource FileHistoryService service) {
    log.info("Disposing FileHistoryService");
  }
}

There Runnable class does not change much but I am showing the CDI related parts

public class ExportRunner implements Runnable {

  //The injection point remained the same
  @Inject
  @SEDataSource
  private EntityManager em;

  @Inject
  @SEDataSource
  AlertService alertService;

  @Inject
  @SEDataSource
  FileHistoryService historyService;

  //This should be an @PostConstruct, but because ThreadScoped context does
  //not exist until after the run method is called it must be called inside it
  public void init() {
    alertService.setEm(em);
    historyService.setEm(em);
  }

  //actual PostConstruct to show when Runnable was created
  @PostConstruct
  public void postConstruct() {
    log.info("Creating ExportRunner");
  }

  //PreDestory method to show that the Thread was being disposed
  @PreDestroy
  public void preDestory() {
    log.info("ExporterRunner @PreDestroy was called");
  }

  public void run() {
    try {
      log.info("Starting run method");
      //Force initialization since we can't use @PostConstruct
      init();
      //do processing
    } finally {
      log.info("Processing Complete");
    }
  }
}

Below is the log output to show the order of injections and cleanup

Creating ExportRunner
Starting run method
Creating Alert Service
Creating History Service
Processing Complete
Disposing FileHistoryService
Disposing AlertService
Disposing of EntityManager
ExporterRunner @PreDestroy was called
Mike
  • 820
  • 7
  • 19