Reputation: 628
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
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