payload: {"action":"closed","issue":{"url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons/issues/653","repository_url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons","labels_url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons/issues/653/labels{/name}","comments_url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons/issues/653/comments","events_url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons/issues/653/events","html_url":"https://github.com/spring-cloud/spring-cloud-commons/issues/653","id":535551882,"node_id":"MDU6SXNzdWU1MzU1NTE4ODI=","number":653,"title":"@Autowired @RequestScope bean identity changed randomly in the middle of a loop within an @Async method","user":{"login":"jamestrandung","id":3972929,"node_id":"MDQ6VXNlcjM5NzI5Mjk=","avatar_url":"https://avatars1.githubusercontent.com/u/3972929?v=4","gravatar_id":"","url":"https://api.github.com/users/jamestrandung","html_url":"https://github.com/jamestrandung","followers_url":"https://api.github.com/users/jamestrandung/followers","following_url":"https://api.github.com/users/jamestrandung/following{/other_user}","gists_url":"https://api.github.com/users/jamestrandung/gists{/gist_id}","starred_url":"https://api.github.com/users/jamestrandung/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jamestrandung/subscriptions","organizations_url":"https://api.github.com/users/jamestrandung/orgs","repos_url":"https://api.github.com/users/jamestrandung/repos","events_url":"https://api.github.com/users/jamestrandung/events{/privacy}","received_events_url":"https://api.github.com/users/jamestrandung/received_events","type":"User","site_admin":false},"labels":[{"id":1076040731,"node_id":"MDU6TGFiZWwxMDc2MDQwNzMx","url":"https://api.github.com/repos/spring-cloud/spring-cloud-commons/labels/feedback-provided","name":"feedback-provided","color":"ededed","default":false,"description":null}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":4,"created_at":"2019-12-10T07:41:29Z","updated_at":"2019-12-11T02:30:49Z","closed_at":"2019-12-11T02:30:48Z","author_association":"NONE","body":"Versions: Spring Boot 2.2.2, Spring Cloud Hoxton.RELEASE\r\nRunnable demo: https://drive.google.com/open?id=1ThjcCoJToHKAhWYhpOIyGzi9xIoYtxRY\r\n\r\nIn my application, I have the following `@Configuration` to add support for `@RequestScope` bean within `@Async` methods.\r\n\r\n    @Configuration\r\n    @EnableAsync\r\n    public class AsyncExecutionConfiguration extends AsyncConfigurerSupport {`\r\n        public static final String ASYNC_THREAD_NAME_PREFIX = \"async-worker-\";\r\n\r\n        @Override\r\n        @Primary\r\n        @Bean(\"taskExecutorWithClonedRequestContext\")\r\n        public Executor getAsyncExecutor() {\r\n            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();\r\n            // Copy the current RequestContext to each and every new @Async task\r\n            executor.setTaskDecorator(new RequestContextCloningDecorator());\r\n            executor.setCorePoolSize(5);\r\n            executor.setMaxPoolSize(10);\r\n            executor.setQueueCapacity(20);\r\n            executor.setThreadNamePrefix(ASYNC_THREAD_NAME_PREFIX);\r\n            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());\r\n            executor.initialize();\r\n\r\n            return executor;\r\n        }\r\n    }\r\n\r\nI'm using the following `RequestContextCloningDecorator` to clone the request context on every `@Async` call.\r\n\r\n    public class RequestContextCloningDecorator implements TaskDecorator {\r\n        @Nonnull\r\n        @Override\r\n        public Runnable decorate(@Nonnull Runnable runnable) {\r\n            RequestAttributes requestContext = this.cloneRequestAttributes(RequestContextHolder.currentRequestAttributes());\r\n    \r\n            return () -> {\r\n                try {\r\n                    RequestContextHolder.setRequestAttributes(requestContext);\r\n                    runnable.run();\r\n    \r\n                } finally {\r\n                    RequestContextHolder.resetRequestAttributes();\r\n    \r\n                }\r\n            };\r\n        }\r\n    \r\n        private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes) {\r\n            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;\r\n    \r\n            try {\r\n                RequestAttributes clonedRequestAttribute = new ServletRequestAttributes(servletRequestAttributes.getRequest(), servletRequestAttributes.getResponse());\r\n    \r\n                // Since we're not using session scope, we only need to copy request scope attributes\r\n                String[] scopeAttributes = requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST);\r\n                if (scopeAttributes.length > 0) {\r\n                    for (String attrName : scopeAttributes)\r\n                        clonedRequestAttribute.setAttribute(attrName,\r\n                                                            requestAttributes.getAttribute(attrName, RequestAttributes.SCOPE_REQUEST),\r\n                                                            RequestAttributes.SCOPE_REQUEST);\r\n                }\r\n    \r\n                return clonedRequestAttribute;\r\n    \r\n            } catch (Exception e) {\r\n                return requestAttributes;\r\n    \r\n            }\r\n        }\r\n    }\r\n\r\nEverything has worked fine so far until I have a new feature which involves an `@Async` method with a loop inside of it. I can reproduce the bug consistently with 3 simple beans.\r\n\r\nFirst, this is the `RequestScopeBean` which does nothing but holding an `int` value, default to 99.\r\n\r\n    @RequestScope\r\n    @Component\r\n    public class RequestScopeBean {\r\n        private int value = 99;\r\n    \r\n        public void updateValue(int newValue) {\r\n            value = newValue;\r\n        }\r\n    \r\n        public int getCurrentValue() {\r\n            return value;\r\n        }\r\n    }\r\n\r\nThis is the `@Service` which contains an `@Async` method with a loop inside.\r\n\r\n    @Service\r\n    public class AsyncService {\r\n        @Autowired\r\n        private RequestScopeBean mrBean;\r\n    \r\n        @Async\r\n        public void loopAsync() {\r\n            for (int i = 0 ; i < 1000 ; i++) {\r\n                if  (mrBean.getCurrentValue() != 1234) {\r\n                    System.out.println(\"=================\");\r\n                    System.out.println(\"SERVICE BEAN: \" + mrBean);\r\n                    System.out.println(\"INDEX: \" + i + \" - CUR VALUE: \" + mrBean.getCurrentValue());\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\nFinally, this is our entry point.\r\n\r\n    @RestController\r\n    @RequestMapping(\"/async\")\r\n    public class AsyncController {\r\n        @Autowired\r\n        private AsyncService asyncService;\r\n    \r\n        @Autowired\r\n        private RequestScopeBean mrBean;\r\n    \r\n        @GetMapping(\"/test\")\r\n        public void test() {\r\n            mrBean.updateValue(1234);\r\n    \r\n            System.out.println(\"=================\");\r\n            System.out.println(\"CONTROLLER BEAN: \" + mrBean);\r\n            System.out.println(\"CUR VALUE: \" + mrBean.getCurrentValue());\r\n    \r\n            asyncService.loopAsync();\r\n        }\r\n    }\r\n\r\nWhen I call this endpoint, I'll see something like this.\r\n\r\n    =================\r\n    CONTROLLER BEAN: com.ft.demo.helper.RequestScopeBean@2278b137\r\n    CUR VALUE: 1234\r\n    =================\r\n    SERVICE BEAN: com.ft.demo.helper.RequestScopeBean@1f1b5a8c\r\n    INDEX: 865 - CUR VALUE: 99\r\n    =================\r\n    SERVICE BEAN: com.ft.demo.helper.RequestScopeBean@1f1b5a8c\r\n    INDEX: 866 - CUR VALUE: 99\r\n    =================\r\n    SERVICE BEAN: com.ft.demo.helper.RequestScopeBean@1f1b5a8c\r\n    INDEX: 867 - CUR VALUE: 99\r\n    ......\r\n    =================\r\n    SERVICE BEAN: com.ft.demo.helper.RequestScopeBean@1f1b5a8c\r\n    INDEX: 999 - CUR VALUE: 99\r\n\r\nWithin the loop, at any random point of time, the `@RequestScope` bean will suddenly be swapped with a brand new instance, which carries the default value instead of the updated value. \r\n\r\nWhen I wrote my code, I mainly referred to the solutions suggested in this [StackOverflow question](https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor). If there's a better way to do this, please let me know. Otherwise, I think we should have a fix for this buggy behaviour? :)\r\n\r\n"}}
other: {"actor":{"display_login":"spencergibb"}}