Thanks Dale for the detailed responses. I don't disagree with your suggestion as we making plans to migrate our platform to a React architecture, but our current application is robust and eliminating all ExecuteAndWait actions would be a monumental task - one that doesn't make sense in this scenario. That said, I should have some time to investigate this issue further over the next few days and will report back.

------ Original Message ------
From "Dale Newfield" <d...@newfield.org>
To "Dale Newfield" <d...@newfield.org>
Cc "Struts Users Mailing List" <user@struts.apache.org>
Date 9/24/2023 12:32:08 PM
Subject Re: Issue with ExecuteAndWait when using Spring Session (Redis)

Depending on your deployment architecture, this exec and wait may no longer be the right 
strategy.  Yes, by managing the executor pool you can bound how many non-http-serving 
threads (jobs) are executing concurrently on your web server's host, but it is all 
premised on using your own hardware.  If running "in the cloud", beware that 
this doesn't lead you to overprovison  (overspend on) always-on infrastructure...and thus 
limit your ability to scale dynamically for variable load requirements.

These days I'd capture the data describing the pending work items somewhere 
(db/s3/kinesis/lots of other possibilities) and ensure some resilient scheme 
translates that into a broadcast message for each.  Probably subscribe a queue 
to that broadcast and use that to trigger the work in parallel.  If queue 
listeners are executing on ec2/ecs/eks, perhaps scale as that queue size grows. 
 Or most likely execute the steps with serverless lambdas, potentially with 
massive concurrency and low cost. Today there are lots of alternatives, 
depending on the workload: step functions, etc.   Usually you can find a way to 
ensure at-least-once execution of each task, so this other side of the coin is 
to make the work idempotent so duplicative execution cannot introduce issues.  
Perhaps a DLQ for problematic jobs.  Make sure something monitors that DLQ.

When exec-and-wait was originally implemented we lived in an only intermittently 
connected world and owned all the hardware.  I used it to sync up work done by a 
disconnected instance with "the real, system" upon reconnection.  (For me this 
meant photographers at parties, building engagement after the party by virtualizing the 
real world connections.  It displayed photos live during the event, and allowed 
assistants trailing behind the photographers to tag event-goers in those photos (by 
scanning barcodes on their wristbands/entering account ids/collecting email addresses on 
the spot to create accounts).  This allowed us to email event photos to the subjects in 
them by the next morning, and gave participants an easy way to connect with each other 
afterwards just by having a photographer take their picture together.  At the time, 
facial recognition was just becoming possible and was also leveraged.  But back to the 
technical side this meant I ran either ran a full instance (db/struts web server/etc) at 
the front door managing barcode assignments to match prepaid ticket holders, and 
supporting the whole event via wifi (I'd plug in repeaters as necessary thru the venue 
and/or stick battery-powered ones in the back pockets of security guards) or that I ran a 
full instance on the device carried around by the photographer's assistant.

This was all before tablets/smartphones/social networks/etc. Today we live in a 
very different world--always connected, cloud based, etc.  Some 
tools/techniques still apply.  Many now have much better solutions (more 
reliable/resilient/scalable/simpler; less expensive to 
build/launch/operate/etc.).  YMMV.

-Dale

 On Sep 23, 2023, at 9:52 PM, Dale Newfield <d...@newfield.org> wrote:

 If it runs again then look at the logic where it's gotta decide whether this is the 
initial call or if the job is already running and this  p my try just a status check.  Is 
it looking at the request to figure that out?  If so, and the expected info isn't found, 
walk the data path to figure out where it is getting lost (sounds like the session filter 
might be a good place to start).  Is it looking in a database/shared memory/file system?  
Perhaps these are not in sync?  Perhaps the long running job is doing everything in a 
single transaction, not committed until the very end, and the "I'm running!" 
flag set within the transaction is not visible unless the query is READ_UNCOMMITTED?

 This run/not decision is critical, so it should be determined atomically.  Perhaps one 
transaction for the decision (which either concludes "report status" or ensures 
its record is visible to any other query) and others for each separable unit of work 
within the long running job?

 Oh, yeah, you said Redis.  I've somehow managed to avoid that, so I don't know what persistent 
data check/update can be done atomically, but that's where you're hanging your hat.  Maybe the 
response to your "is anyone else already running this job?" query is being cached, and so 
the first "no" response is being returned every time?

 So many places this decision could go wrong in a distributed platform.  Every possible 
race condition will eventually happen.  Can you confidently say that two concurrent 
requests on distinct hosts will always result in a single "winner"?

 (Then later on there's a "liveness" detector you'll need to figure out in case 
the winner fails.  Perhaps it should periodically update that record to show when it was 
last known to be running? (How far into the job it is right then would also be great to 
snapshot.)  if that time stamp is older than X, is it safe to assume it failed and to 
elect another leader to take its place?  Make sure time zone differences on different 
hosts can't impact the timeout checks.  Is it safe, even in the face of a network 
disconnection, so eventually the evicted leader can reappear?  Would one of them detect 
the issue and kill itself?)

 -Dale

 On Sep 23, 2023, at 4:23 PM, Burton Rhodes <burtonrho...@gmail.com> wrote:

 I am attempting to implement a centralized session store to an existing application 
using Spring Session (Redis) and Struts. I have everything working on a basic level, but 
I am running into an issue when a Struts Action uses ExecuteAndWait.  The refresh 
attempts don't seem to pick up the running action using the provided Struts Token (at 
least I think this is the case). So on each refresh, the action seems to run again as if 
it was the first time duplicating database inserts, etc.  It becomes an infinite process, 
and Struts never returns the final "SUCCESS" result JSP page to the browser.  
It's worth noting that if I disable the Spring Session filter, everything works fine. 
Anyone have a clue as to what might be going on or how I might begin to troubleshoot?

 I've included a thread dump so you can see the filters involved. The 
"SessionRepositoryFilter" is where the HttpSession is wrapped by Spring (Redis) 
and is before any Struts filters. I've also included a basic action definition that is 
causing the issue (although this is happening on all actions that use the ExecuteAndWait 
logic). Any help is appreciated.

 ** Example Action Definition **

 <action name="ContactBatchCategories_update" 
class="com.afs.web.struts.action.contact.ContactBatchCategoriesAction" method="update">
   <interceptor-ref name="myDefaultStack"/>
   <interceptor-ref name="openSessionExecuteAndWaitInterceptor" >
       <param name="delay">0</param>
   </interceptor-ref>
   <result 
name="input">/struts/search/actions/contactBatchCategories_modal.jsp</result>
   <result name="wait">/struts/common/progressMonitorWait_modal.jsp</result>
   <result>/struts/common/progressMonitorSuccess_modal.jsp</result>
 </action>


 ** Thread Dump **

 java.lang.Thread.State: RUNNABLE
     at 
com.afs.web.struts.action.contact.ContactBatchCategoriesAction.prepare(ContactBatchCategoriesAction.java:62)
     at 
com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171)
     at 
com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.executeConditional(DefaultActionInvocation.java:299)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
     at 
org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:154)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.executeConditional(DefaultActionInvocation.java:299)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
     at 
com.afs.web.config.struts.StrutsAccountInterceptor.intercept(StrutsAccountInterceptor.java:67)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.executeConditional(DefaultActionInvocation.java:299)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
     at 
com.afs.web.config.struts.StrutsSessionInterceptor.intercept(StrutsSessionInterceptor.java:44)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.executeConditional(DefaultActionInvocation.java:299)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
     at 
com.afs.web.common.struts.interceptor.ExceptionInterceptor.intercept(ExceptionInterceptor.java:33)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.executeConditional(DefaultActionInvocation.java:299)
     at 
com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:253)
     at 
org.apache.struts2.factory.StrutsActionProxy.execute(StrutsActionProxy.java:48)
     at 
org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:651)
     at 
org.apache.struts2.dispatcher.ExecuteOperations.executeAction(ExecuteOperations.java:79)
     at 
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.handleRequest(StrutsPrepareAndExecuteFilter.java:157)
     at 
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.tryHandleRequest(StrutsPrepareAndExecuteFilter.java:140)
     at 
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:128)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
com.afs.web.config.filter.MyAppSessionFilter.doFilter(MyAppSessionFilter.java:85)
     at 
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
     at 
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:352)
     at 
org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
     at 
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131)
     at 
org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:110)
     at 
org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:101)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:164)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:151)
     at 
org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:129)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227)
     at 
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:221)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
     at 
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
     at 
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
     at 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:117)
     at 
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
     at 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
     at 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
     at 
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:361)
     at 
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:225)
     at 
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:190)
     at 
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
     at 
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:186)
     at 
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
org.eclipse.jetty.websocket.servlet.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:170)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:202)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142)
     at 
org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82)
     at 
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354)
     at 
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
     at org.eclipse.jetty.servlet.FilterHolder.doFilter(FilterHolder.java:210)
     at 
org.eclipse.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1635)
     at 
org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:527)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:131)
     at 
org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
     at 
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:223)
     at 
org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1570)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:221)
     at 
org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1384)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:176)
     at 
org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:484)
     at 
org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1543)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:174)
     at 
org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1306)
     at 
org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:129)
     at 
org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:149)
     at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:51)
     at 
org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:122)
     at org.eclipse.jetty.server.Server.handle(Server.java:563)
     at 
org.eclipse.jetty.server.HttpChannel.lambda$handle$0(HttpChannel.java:505)
     at 
org.eclipse.jetty.server.HttpChannel$$Lambda$1353.1731450897.dispatch(Unknown 
Source:-1)
     at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:762)
     at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:497)
     at 
org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:282)
     at 
org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:314)
     at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
     at 
org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
     at 
org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:416)
     at 
org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:385)
     at 
org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:272)
     at 
org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:140)
     at 
org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy$$Lambda$1338.510268842.run(Unknown
 Source:-1)
     at 
org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:411)
     at 
org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:969)
     at 
org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.doRunJob(QueuedThreadPool.java:1194)
     at 
org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1149)
     at java.lang.Thread.run(Thread.java:834)

---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscr...@struts.apache.org
For additional commands, e-mail: user-h...@struts.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: user-unsubscr...@struts.apache.org
For additional commands, e-mail: user-h...@struts.apache.org

Reply via email to