Debojit
Debojit

Reputation: 628

Spring Boot Application Stuck on WebLogic

I have a Spring Boot REST API with 2 endpoints, one calls an Async service to start a long-running job, and another is a synchronous API to get the progress of the job. Job status is stored in a table. The APIs work fine when I run on the embedded tomcat, but the async method does not work on WLS, which is my target env. The sync method is working fine. My weblogic.xml file is as below:

<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app         http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">

    <wls:context-root>moph-excel-loader</wls:context-root>
    <wls:container-descriptor>
        <wls:prefer-application-packages>
            <wls:package-name>org.slf4j.*</wls:package-name>
            <wls:package-name>org.springframework.*</wls:package-name>
        </wls:prefer-application-packages>
    </wls:container-descriptor>
</wls:weblogic-web-app>

I'm not getting any error messages from WLS or the application, it just hangs there. The synchronous API continues to work fine. I'm not clear what the issue is here and would appreciate some guidance from the community.

Thanks,

EDIT 1:

I have my long running tasks run via a ThreadPoolTaskExecutor configued as a @Bean:

@Configuration
@EnableAsync
public class AsyncConfig{
    @Bean(name = "asyncFileLoadExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("xlconv-th-");
        executor.initialize();
        return executor;
    }
}

And all my long-running @Service classes have methods annotated as @Async:

@Service
public class FileLoaderService {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(FileLoaderService.class);
    
    //Autowired services
    
    @Async("asyncFileLoadExecutor")
    public CompletableFuture<Void> loadFile(JobDto job, String fileName) throws IOException, OpenXML4JException, SAXException, ParserConfigurationException, ParseException {
        //Long running work.
        
        return CompletableFuture.completedFuture(null);
    }
}

This one gets called from the RESTController class and calls other async services in turn to parallelize independent tasks.

EDIT 2: My main class looks like this:

@SpringBootApplication(scanBasePackages = { "nom.side.xls" })
public class ExcelFileLoaderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExcelFileLoaderApplication.class, args);
    }

}

And this is my only controller class:

@RestController
@RequestMapping("/bulk-loader")
public class BulkLoadController {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(BulkLoadController.class);
    @Autowired
    private FileLoaderService loaderService;
    
    @Autowired
    private JobManagerService jobManagerService;
    
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public JobDto load(@RequestBody Map<String, String> request) {
        JobDto job = jobManagerService.createJob(Paths.get(request.get("fileName")).getFileName().toString());
        LOGGER.info("[" + job.getJobId() + "] Parsing file " + request.get("fileName"));
        
        try {
            loaderService.loadFile(job, request.get("fileName"));
        } catch (IOException | OpenXML4JException | SAXException | ParserConfigurationException
                | ParseException e) {
            e.printStackTrace();
        }
        
        return job;
    }
    
    //This one works just fine
    @GetMapping(path = "/jobs/{job-id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public JobDto progress(@PathVariable(name = "job-id") long jobId) {
        return jobManagerService.getJob(jobId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "ERROR: Invalid Job ID " + jobId + "."));
    }
}

Upvotes: 1

Views: 786

Answers (1)

httPants
httPants

Reputation: 2123

If you're launching a long running task in weblogic from a spring boot app, you should configure a TaskExecutor so weblogic can manage the thread running the task and also doesn't classify it as a stuck thread if it is processing for a long time and can shutdown the thread if the server is shutdown.

You can configure a task executor in your weblogic.xml like this...

<wls:weblogic-web-app xmlns:wls="http://xmlns.oracle.com/weblogic/weblogic-web-app" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/weblogic/weblogic-web-app         http://xmlns.oracle.com/weblogic/weblogic-web-app/1.4/weblogic-web-app.xsd">
  <wls:context-root>moph-excel-loader</wls:context-root>
  <wls:container-descriptor>
    <wls:prefer-application-packages>
      <wls:package-name>org.slf4j.*</wls:package-name>
      <wls:package-name>org.springframework.*</wls:package-name>
    </wls:prefer-application-packages>
  </wls:container-descriptor>

  <work-manager>
    <name>moph-excel-loader-wm</name>
    <max-threads-constraint>
      <name>moph-excel-loader-max-threads</name>
      <count>10</count>
    </max-threads-constraint>
    <ignore-stuck-threads>true</ignore-stuck-threads>
  </work-manager>
  
  <managed-executor-service>
    <name>moph-excel-loader-executor</name>
    <dispatch-policy>moph-excel-loader-wm</dispatch-policy>
    <long-running-priority>10</long-running-priority>
    <max-concurrent-long-running-requests>2</max-concurrent-long-running-requests>
  </managed-executor-service>
  
  <resource-env-description>
    <resource-env-ref-name>concurrent/moph-excel-loader-executor</resource-env-ref-name>
    <resource-link>moph-excel-loader-executor</resource-link>
  </resource-env-description>
  
</wls:weblogic-web-app>

Then in your web.xml you create a reference to it...

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

  <context-param>
    <param-name>spring.config.additional-location</param-name>
    <param-value>file:./config/moph-excel-loader.yml</param-value>
  </context-param>

  <resource-env-ref>
    <resource-env-ref-name>concurrent/moph-excel-loader-executor</resource-env-ref-name>
    <resource-env-ref-type>javax.enterprise.concurrent.ManagedExecutorService</resource-env-ref-type>
  </resource-env-ref>

</web-app>

and then you can create the TaskExecutor bean from a @Configuration class...

@Configuration
public class MophExcelLoaderConfig {

    @Bean
    public DefaultManagedTaskExecutor batchTaskExecutor() {
        DefaultManagedTaskExecutor taskExecutor = new DefaultManagedTaskExecutor();
        taskExecutor.setJndiName("java:comp/env/concurrent/moph-excel-loader-executor");
        return taskExecutor;
    }
}

For the long running task, create a SchedulingAwareRunnable that returns true from the isLongLived method, so weblogic is aware its a long running task and will create a daemon thread to run the task instead of taking a thread out of the thread pool...

public class LongLivedRunnable implements SchedulingAwareRunnable, ManagedTask {

    @Override
    public void run() {
        // do the work here
    }

    @Override
    public boolean isLongLived() {
        return true;
    }

}

Then to run the task, you use the ManagedTaskExecutor...

@Service
public class JobLauncher  {

    @Autowired
    private DefaultManagedTaskExecutor batchTaskExecutor;

    @Override
    public void runLongRunningTask() {
        Runnable task = new LongLivedRunnable ();
        batchTaskExecutor.execute(task);
    }
}

That's the general idea. I've been doing this to run long running spring batch jobs within weblogic.

When deploying a spring boot app in weblogic as a war file, your @SpringBootApplication class should extend the SpringBootServletInitializer. eg.

@SpringBootApplication
public class ExcelFileLoaderApplication  extends SpringBootServletInitializer  {

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder application) {
        return application.sources(ExcelFileLoaderApplication.class);
    }


    public static void main(
            String[] args) throws Exception {
        SpringApplication.run(ExcelFileLoaderApplication.class, args);
    }

}

If you are running tests using spring boot and embedded tomcat, you need to provide a different application class to use for your tests that looks like your original ExcelFileLoaderApplication class. Also if testing using tomcat, you would use a standard ThreadPoolTaskExecutor instead of the DefaultManagedTaskExecutor (you can achieve that by having separate AsyncConfig classes; one for testing using embedded tomcat and one for packing in the war file deployed to weblogic).

You should specify the spring-boot-starter-tomcat dependency as provided so weblogic uses it's servlet classes...

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-tomcat</artifactId>
  <scope>provided</scope>
</dependency>

Upvotes: 1

Related Questions