After adding the Hystrix command am getting the following error -
Error executing HystrixCommand.run(). Proceeding to fallback logic ...: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) at
My application class looks like this -
#SpringBootApplication
#EnableDistributedSession(regionName="DATASERVICES")
#EnableDiscoveryClient
#EnableCircuitBreaker
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
I have seen the error is happening at the line where we are trying to get the session in Service class. My service class where I have implemented the Hystrix looks like this -
#Service
public class TransactionsUSStandardServiceImpl implements TransactionsUSStandardService {
#Autowired
private RestTemplate restTemplate;
#Autowired
HttpServletRequest request;
#Override
#HystrixCommand(commandKey = "getXyzCommand", threadPoolKey = "getXyzThreadPoolKey", fallbackMethod = "xyzFallback")
public List<String> getXyz(String arg1, HttpHeaders headers, String arg2) throws Exception {
HttpSession session = request.getSession(false);
...
...
}
public List<String> xyzFallback(String arg1, HttpHeaders headers, String arg2, Throwable t) throws Exception {
LOGGER.error(".......");
throw new XyzException("Hystrix CircuitBreaker Fallback while executing getXyz() method", t);
}
}
In the SVN we kept our configuration as -
hystrix:
command:
defaultHystrixCommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
getXyzCommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
threadpool:
getXyzThreadPoolKey:
coreSize: 20
maximumSize: 20
maxQueueSize: 5
Am I missing anything in my Hystrix configuration?
Related
I have to build a Spring Batch system. In my project I have to call, inside a batch scheduled, an Api method defined in Controller class of my project.
This is an example of controller
#RestController
public class MyController implements ExampleApi {
// other methods
#Override
public ResponseEntity<ExampleResponse> method(String object){
final ExampleResponse response = // execution
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
I call the method above in my CustomProcessor. This is an example of Batch processor
public class CustomProcessor implements ItemProcessor<List<String>, List<String>> {
#Autowired
private ExampleApi exampleApi;
#Override
public List<String> process(#NonNull List<String> objects) throws Exception {
objects.forEach(object -> exampleApi.method(object));
return objects;
}
}
When batch starts running I can call the api, but when I call inside another api in another class annotated like this:
#Component
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ObjectFactoryImpl implements ObjectFactory {
#Autowired
#Qualifier("anotherService")
private ObjectProvider<AnotherApi> anotherApiObjectProvider;
public Object find(Long objectId) {
Object object;
try {
object = anotherApiObjectProvider.getObject().getObjectById(objectId).getObject();
} catch (Exception e) {
throw new Exception....
}
}
I get this exception:
IllegalStateException: No thread-bound request found: Are you
referring to request attributes outside of an actual web request, or
processing a request outside of the originally receiving thread? If
you are actually operating within a web request and still receive this
message, your code is probably running outside of DispatcherServlet:
In this case, use RequestContextListener or RequestContextFilter to
expose the current request.
I'm quite new to Spring Batch.
I have a simple Spring Boot 2.1 application with a Spring Interceptor and #RestControllerAdvice.
My requirement is to have the Spring Interceptor be called for all situations, including when an exception occurs.
For custom exceptions, the Interceptor handler methods do get called, e.g. preHandle() and afterCompletion(). However, for exceptions handled by ResponseEntityExceptionHandler, the Spring Interceptor does not get called (I need ResponseEntityExceptionHandler's methods to create a custom ResponseBody to send back, however, I also need to trigger Interceptor's afterCompletion() for auditing purposes).
For instance, if a REST request is made with PATCH HTTP method, it only executes PersonControllerExceptionHandler.handleHttpRequestMethodNotSupported() and no PersonInterceptor is invoked.
Exception Handler:
#RestControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonControllerExceptionHandler extends ResponseEntityExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonControllerExceptionHandler.class);
#ExceptionHandler(value = {PersonException.class })
public ResponseEntity<Object> handlePersonException(PersonException exception) {
LOGGER.info("Person exception occurred");
return new ResponseEntity<Object>(new Person("Bad Age", -1),
HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(value = {Exception.class })
public ResponseEntity<Object> handleException(Exception exception) {
LOGGER.info("Exception occurred");
return new ResponseEntity<Object>(new Person("Unknown Age", -100),
HttpStatus.INTERNAL_SERVER_ERROR);
}
#Override
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LOGGER.info("handleHttpRequestMethodNotSupported()...");
return new ResponseEntity<>(new Person("Argh!", 900), HttpStatus.METHOD_NOT_ALLOWED);
}
}
The Interceptor:
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("PersonInterceptor#preHandler()...");
return true;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.info("PersonInterceptor#postHandler()...");
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
LOGGER.info("PersonInterceptor#afterCompletion()...");
if (ex != null) {
LOGGER.error("afterCompletion(): An exception occurred", ex);
}
}
}
Registering the Interceptor:
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PersonInterceptor()).addPathPatterns("/person/*");
}
}
Controller:
#RestController
#RequestMapping("/")
public class PersonController {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
#Autowired
private PersonService personService;
#GetMapping(path = "/person/{age}", produces = MediaType.APPLICATION_JSON_VALUE)
public Person getPerson(#PathVariable("age") Integer age) throws PersonException {
LOGGER.info("Age: {}", age);
return personService.getPerson(age);
}
}
Initially I thought it has something to do with #Ordered but trying various scenarios where I give PersonInterceptor a higher precedence than #RestControllerAdvice yields the same undesirable outcome (or vice versa).
After digging into Spring framework, it seems like if a handler is not found, an exception is thrown back to DispacherServlet#doDispatch() which goes into a catch block, and therefore, it skips interceptor mapping process, including the afterCompletion() (I'm using Spring 5.1. as an example to trace the execution path):
DispacherServlet#doDispatch() is called and attempts is made to get the HandlerExecutionChain
I can see there are several HandlerMapping's; the one that fails is RequestMappingHandlerMapping
In AbstractHandlerMapping#getHandler(), it tries to get the handler via AbstractHandlerMethodMapping#getHandlerInternal()
Eventually, AbstractHandlerMethodMapping#lookupHandlerMethod() is called which fails to find a matching pattern due to the fact that there is no PATCH getPerson(), but rather GET getPerson()
At this point, RequestMappingInfoHandlerMapping#handleNoMatch() throws HttpRequestMethodNotSupportedException
This exception bubbles up to DispatcherServlet#doDispatch() exception clause which then processes by the exception resolver that it finds in
DispatcherServlet#processHandlerException() (of course, this finds an exception resolver and doesn't throw an exception which might trigger DispatcherServlet#triggerAfterCompletion() when an exception is caught in DispacherServlet#doDispatch() exception clause
Is there something I am missing to trigger the interceptor's afterCompletion() in cases when there is no handler match?
Camel has to call REST service for some integration, However, the REST service has one authentication api (POST api) which needs to be called first to get a token and then other subsequent api calls has to be invoked with the token embedded in header of HTTP requests.
Does Spring Restemplate or apache camel has some api to support the same?
Followed #gusto2 approach, Its pretty much working fine.
SO, I created two routes --> First one is a timer based like below, this generates the token, periodically refreshes it(since the route is timer based) and stores the token in a local variable for being reused by some other route.
#Component
public class RestTokenProducerRoute extends RouteBuilder {
private String refreshedToken;
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
from("timer://test?period=1200000") //called every 20 mins
.process(
exchange -> exchange.getIn().setBody(
new UserKeyRequest("apiuser", "password")))
.marshal(userKeyRequestJacksonFormat) //convert it to JSON
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http4://localhost:8085/Service/Token")
.unmarshal(userKeyResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
UserKeyResponse response= exchange.getIn().getBody(
UserKeyResponse.class); //get the response object
System.out.println(response + "========>>>>>>" +
response.getResult());
setRefreshedToken(response.getResult()); //store the token in some object
}
}).log("${body}");
}
public String getRefreshedToken() {
return refreshedToken;
}
public void setRefreshedToken(String refreshedToken) {
this.refreshedToken = refreshedToken;
}
}
And the second route can call subsequent apis which will use the token generated by the first route, it would be something like this. Have to add error handling scenarios, where token might not be valid or expired. But I guess that would be separate concern to solve.
#Component
public class RestTokenUserOnboardRoute extends RouteBuilder {
private JacksonDataFormat OtherDomainUserRequestJacksonFormat = new JacksonDataFormat(
OtherDomainUserRequest.class);
private JacksonDataFormat OtherDomainUserResponseJacksonFormat = new JacksonDataFormat(
OtherDomainUserResponse.class);
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
//This route is subscribed to a Salesforce topic, which gets invoked when there is any new messages in the topic.
from("salesforce:CamelTestTopic?sObjectName=MyUser__c&sObjectClass="+MyUser__c.class.getName()))
.convertBodyTo(OtherDomainUserRequest.class)
.marshal(OtherDomainUserRequestJacksonFormat).log("${body}")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("The token being passed is ==> ${bean:tokenObj?method=getRefreshedToken}")
.setHeader("Authorization", simple("${bean:tokenObj?method=getRefreshedToken}"))
.to("http4://localhost:8085/Service/DomainUser")
.unmarshal(OtherDomainUserResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
OtherDomainUserResponse response = exchange.getIn().getBody(
OtherDomainUserResponse.class);
System.out.println(response + "==================>>>>>> " + response.getStatusCode());
}
}).log("${body}");
}
}
So, here the token is getting consumed from the tokenObj bean (instance of RestTokenProducerRoute which has method getRefreshedToken() defined. It returns the stored token.
Needless to say, you have set the bean in camelcontext registry as follows along with other settings (like component, route etc). In my case it was as follows.
#Autowired
public RestTokenUserOnboardRoute userOnboardRoute;
#Autowired
public RestTokenProducerRoute serviceTokenProducerRoute;
#Autowired
private RestTokenProducerRoute tokenObj;
#Override
protected CamelContext createCamelContext() throws Exception {
SimpleRegistry registry = new SimpleRegistry();
registry.put("tokenObj", tokenObj); //the tokenObj bean,which can be used anywhere in the camelcontext
SpringCamelContext camelContext = new SpringCamelContext();
camelContext.setRegistry(registry); //add the registry
camelContext.setApplicationContext(getApplicationContext());
camelContext.addComponent("salesforce", salesforceComponent());
camelContext.getTypeConverterRegistry().addTypeConverter(DomainUserRequest.class, MyUser__c.class, new MyTypeConverter());
camelContext.addRoutes(route()); //Some other route
camelContext.addRoutes(serviceTokenProducerRoute); //Token producer Route
camelContext.addRoutes(userOnboardRoute); //Subsequent API call route
camelContext.start();
return camelContext;
}
This solves my problem of setting token dynamically where token is getting produced as a result of execution of some other route.
I have a spring application that injects certain beans are injexted based on the request context. In this example it is the Facebook bean.
#RestController
#RequestMapping("facebook")
public class FacebookInjectionController {
#Autowired
private Facebook facebook;
#Autowired
private UserRepository userRepository;
#RequestMapping(method = RequestMethod.GET)
public List<String> blah() {
String firstName = facebook.userOperations().getUserProfile().getFirstName();
return Arrays.asList(firstName);
}
#RequestMapping(method = RequestMethod.GET, value = "complex")
public List<String> blah2() {
UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);
return stream.filter(u -> u.getUid().equals(principal.getUid()))
.map(u ->
facebook.userOperations().getUserProfile().getFirstName()
).collect(Collectors.toList());
}
}
This code will run normally but every so often it will fail with the following error:
2017-02-09 01:39:59.133 ERROR 40802 --- [o-auto-1-exec-2]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'scopedTarget.facebook': Scope 'request' is
not active for the current thread; consider defining a scoped proxy
for this bean if you intend to refer to it from a singleton; nested
exception is java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual
web request, or processing a request outside of the originally
receiving thread? If you are actually operating within a web request
and still receive this message, your code is probably running outside
of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.] with root cause
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy137.userOperations(Unknown Source)
at com.roomsync.FacebookInjectionController.lambda$blah2$5(FacebookInjectionController.java:43)
at com.roomsync.FacebookInjectionController$$Lambda$10/2024009478.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
at java.util.stream.AbstractTask.compute(AbstractTask.java:316)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
I have tried multiple solutions (including Spring MVC: How to use a request-scoped bean inside a spawned thread?) but none have worked.
Is there a way to pass a request scoped bean down to a lambda or another thread?
going of what https://stackoverflow.com/users/1262865/john16384 said i have changed my config to:
#Bean
#Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ConnectionRepository connectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return getUsersConnectionRepository(connectionFactoryLocator).createConnectionRepository(authentication.getName());
}
#Bean
#Scope(value="inheritableThreadScope", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionFactoryLocator connectionFactoryLocator) {
Connection<Facebook> connection = connectionRepository(connectionFactoryLocator).findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ExecutorService fbExecutor () {
return Executors.newSingleThreadExecutor();
}
the controller now looks like:
#RestController
#RequestMapping("facebook")
public class FacebookInjectionController {
#Autowired
private Facebook facebook;
#Autowired
private UserRepository userRepository;
#Autowired
private ExecutorService fbExecutor;
#RequestMapping(method = RequestMethod.GET)
public List<String> blah() {
String firstName = facebook.userOperations().getUserProfile().getFirstName();
return Arrays.asList(firstName);
}
#RequestMapping(method = RequestMethod.GET, value = "complex")
public List<String> blah2() throws ExecutionException, InterruptedException {
UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);
Future<List<String>> submit = fbExecutor.submit(() -> stream.filter(u -> u.getUid().equals(principal.getUid()))
.map(u ->
facebook.userOperations().getUserProfile().getFirstName()
)
.collect(Collectors.toList()));
return submit.get();
}
}
i also have the following config:
#Configuration
public class BeanFactoryConfig implements BeanFactoryAware {
private static final Logger LOGGER = Logger.getLogger(BeanFactoryConfig.class);
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
// logger.info("MainConfig is backed by a ConfigurableBeanFactory");
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
/*Notice:
*org.springframework.beans.factory.config.Scope
* !=
*org.springframework.context.annotation.Scope
*/
org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope() {
#Override
public void registerDestructionCallback(String name, Runnable callback) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.registerDestructionCallback(name, callback, 3);
}
};
cbf.registerScope("inheritableThreadScope", simpleThreadScope);
/*why the following? Because "Spring Social" gets the HTTP request's username from
*SecurityContextHolder.getContext().getAuthentication() ... and this
*by default only has a ThreadLocal strategy...
*also see https://stackoverflow.com/a/3468965/923560
*/
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
else {
// logger.info("MainConfig is not backed by a ConfigurableBeanFactory");
}
}
}
even with this it sometimes get the error:
{
"timestamp": 1486686875535,
"status": 500,
"error": "Internal Server Error",
"exception": "java.util.concurrent.ExecutionException",
"message": "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook' defined in class path resource [com/roomsync/config/SocialConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.facebook.api.Facebook]: Factory method 'facebook' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.connectionRepository': Scope 'inheritableThreadScope' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.",
"path": "/facebook/complex"
}
so it seems that im still missing the piece to activate the scope and copying the thread local context to it
There's two things going on:
1) Java streams use a common Fork/Join pool to execute things in parallel. These threads are not created by the Spring framework (or by you).
2) Request scoped beans are supported by using a ThreadLocal.
This means that if a thread, not created by Spring, tries to access a request scoped bean, it won't be found as the thread does not know about it (it is not in the ThreadLocal).
In order for you to resolve this issue you will need to take control of which threads are used for your streams. Once you achieved that, you can make a copy of the request scoped beans to use for the sub-threads. You'll also need to clean them up again after the thread has finished its task or you risk leaving beans behind that may be seen by the next task being executed on that thread.
To change which threads are used by parallel streams, see: Custom thread pool in Java 8 parallel stream
How to configure Spring properly to propagate request scoped beans to child threads you already found I think.
This is what worked for me to transfer request beans in fork-joined threads. The example is only for illustration.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// org.slf4j:slf4j-api:1.7.30
import org.slf4j.MDC;
// org.springframework:spring-web:5.2.12.RELEASE
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
class Scratch {
public static void main(String[] args) {
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
Map<String, String> contextMap = MDC.getCopyOfContextMap();
List<String> list = new ArrayList<>();
list.parallelStream().map(id -> {
try {
// copy all required for spring beans
RequestContextHolder.setRequestAttributes(context);
MDC.setContextMap(contextMap);
// ************************************
// Spring request beans usage goes here
// ************************************
return 1;
} finally {
// clean all from thread local
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
})
.collect(Collectors.toList());
}
}
Is it required, that the stream is processed in parallel? That causes, that the lambda may be executed in another thread.
Stream stream = StreamSupport.stream(userRepository.findAll().spliterator(), false);
I had the same issue, I was trying to use the parallel stream to fetch job information from Kubernetes REST API since the parallel stream uses new Threads as John16384 explained, my code couldn't get the 'scopedTarget.oauth2ClientContext' because it's scope is request in Spring and the thread created by parallel stream couldn't access it. So I had to change it like below;
old version: items.parallelStream().map(jobItem -> createJobObject(jobItem, createJobTrigger(jobItem))).collect(Collectors.toList());
fixed version: items.stream().map(jobItem -> createJobObject(jobItem, createJobTrigger(jobItem))).collect(Collectors.toList());
and inside the createJobObject method, I was calling a REST service
restTemplate.getForEntity(url, KubernetesJob.class).getBody().getItems();
This question already has an answer here:
Accessing HttpSession outside of the originally receiving thread
(1 answer)
Closed 6 years ago.
I have a fully-annotation-driven Spring Boot 1.3.5 app which has this asynchronous service which needs to autowire another service bean (And in the future it will need to autowire a repository bean, but I'm not there yet) in order to perform some business logic:
#Service
public class AsyncService {
#Autowired
public HelpingService helpingService;
#Async
public Future<String> doFoo(String someArgument)
throws InterruptedException {
Thread.sleep(3000);
System.out.println("about to do Foo "+someArgument);
String result = "";
try {
result = helpingService.getSomeStuff(someArgument);
}
catch (Exception e) {
e.printStackTrace();
}
return new AsyncResult<String>(hello);
}
}
That method above is being called from a #Controller bean, which has other endpoints (Non-async) that work as expected also using this
#Controller
public class MyController extends BaseController {
#Autowired
HelpingService helpingService;
#Autowired
AsyncService asyncService;
#RequestMapping(method=RequestMethod.GET, value={"/rest/threads/getIp/{jobId}"}, produces={"application/json"})
public ResponseEntity<?> getLog(#PathVariable("jobId") String jobId) throws InterruptedException {
asyncService.doFoo(jobId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
And here's helpingService's implementation (It's an interface), calling any method works perfectly fine when I'm not doing it from the #Async method above:
#Service
#Validated
public class HelpingServiceImpl implements HelpingService {
#Autowired
HttpSession httpSession;
#Value(value="${projName}")
private String projName;
public String getServerAddress(){
AuthRegion region = (AuthRegion) httpSession.getAttribute("region");
if (region != null)
return region.getServerAddress();
else
return null;
}
#Override
public String getSomeStuff(String jobId) {
String responseString = "";
String projName = this.projName;
String serverAddress = getServerAddress(); // Code stops here with an exception
// Some code here that works fine outside this thread
return responseString;
}
}
This is the exception being caught:
about to do Foo (267)
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:309)
at org.springframework.web.context.support.WebApplicationContextUtils.access$400(WebApplicationContextUtils.java:64)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:366)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:361)
at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:307)
at com.sun.proxy.$Proxy96.getAttribute(Unknown Source)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getRundeckServerPort(ThreadServiceImpl.java:45)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getJobExecutionOutput(ThreadServiceImpl.java:65)
at corp.fernandopcg.myapp.service.AsyncService.doFoo(AsyncService.java:40)
at corp.fernandopcg.myapp.service.AsyncService$$FastClassBySpringCGLIB$$7e164220.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
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)
I added (With some changes as I couldn't extend AsyncConfigurer at the same time as SpringBootServletInitializer, and I had to catch an exception not mentiones there) the taskExecutor part to my Application main class as follows, guided by this tutorial which does look similar to what I need, in my opinion
#SpringBootApplication
#EnableAsync
#EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class)
public class MyApplication extends SpringBootServletInitializer implements AsyncConfigurer{
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("SomeRandomLookup-");
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// TODO Auto-generated method stub
return null;
}
}
Can I tell my #Async service to be able to use other services of the application? Because if that's not possible, I don't really see the use of these threading mechanism.
This is a great illustration of why request-scope injection can be problematic. Your HelpingServiceImpl has a hidden dependency on the request-specific HttpSession, which looks like a field but is actually a proxy that is resolved by Spring on each call to always refer to the "current" request (using a thread-local variable).
The problem is that by making your call #Async, you're separating the HelpingServiceImpl invocation from the request that triggered it, and there's no longer the implicit connection of being on the same thread that would allow it to pull information from the globalish context.
The most straightforward fix is to make your dependencies explicit--instead of having your HelpingServiceImpl grab the region directly off of the HttpSession, pass the region to it as a method parameter.