#1
  1. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    43
    Rep Power
    4

    Process won't stop when action window closes


    I have an application where the user sees a table of items (packets to be faxed or emailed). User checks off all of the items they desire to have delivered, then clicks on a button. The action page pops up as a new window, and begins looping through all of the items that were checked.

    So the code looks something like this:

    Code:
    <cfflush interval=10>
    
    <cfoutput>
    Processing items. Please do not close this window until processing is complete.
    <span style="font-weight:bold;">Note - Any work requests with delivery status other than "ready to be sent" will not be sent.</span>
    <br><br>#repeatString(" ", 100000)# <!--- whitespace to force IE8 to flush right here and now --->
    </cfoutput>
    
    <cfset blnSuccess = structNew()>
    
    <cfif NOT qryWRsToSend.recordCount>
    <cfoutput>O records were sent.</cfoutput>
    <cfelse>
    <cfset intNumFailed = 0>
    <cfset lstFailedIDs = ""> 		
    
    <cfoutput>
    <div id="row">
    <div id="cell" class="header">Work Request ID</div>
    <div id="cell" class="header">Delivery Method</div>
    <div id="cell" class="header">Success (Y or N)</div>
    <div id="cell" class="header">Progress</div>
    </div>
    </cfoutput>
    
    <cfoutput query="qryWRsToSend"> <!--- this is the "loop" that seems to continue even after this window is closed--->
    <cftry>
    <cfset blnSuccess['#workRequestID#'] = objWP.sendWelcomePacket(#workRequestID#, '#Delivery_Method#')> <!--- this function does a number of things; grabs attachment documents, checks whether to fax or send, etc. It takes at least a few seconds to complete --->
    <cfcatch>
    <cfset blnSuccess['#workRequestID#'] = false>
    </cfcatch>
    </cftry>
    
    <cfset strColorClass = "gray">
    <cfif currentrow MOD 2 EQ 0>
    <cfset strColorClass = "white">
    </cfif>
    
    
    <!--- this is what gets flushed to the screen, so user can see the loop's progress--->
    <div id="row" <cfif NOT blnSuccess['#workRequestID#']>class="red"</cfif>>
    <div id="cell" class="#strColorClass#">#workRequestID#</div>
    <div id="cell" class="#strColorClass#">#Delivery_Method#</div>
    <div id="cell" class="#strColorClass#"><cfif blnSuccess['#workRequestID#']>Y<cfelse>N</cfif></div>
    <div id="cell" class="#strColorClass#">#currentrow# of #recordcount# processed.</div>
    </div>
    #repeatString(" ", 100000)# <!---this is whitespace - necessary to force IE8 to render this "row" to the screen right here and how--->
    </cfoutput> <!--- end of query "loop" --->
    I have a query with 200 records to loop over, and each iteration should take at least a second or two. So as a test, I start with this query, and close the window after about 15 records have been processed. However, the loop seems to continue running anyway, because I still get all 200 test emails in my inbox.

    Isn't the cfoutput "loop" supposed to stop wherever it if the the window has been closed?

    Thanks,
    Christophe
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    43
    Rep Power
    4
    Okay, I got my answer to the above, which is no -- CF will not stop the cfoutput loop just because the window was closed. I was also told this link is a possible work-around: http://www.bennadel.com/blog/1131-Ask-Ben-Ending-ColdFusion-Session-When-User-Closes-Browser.htm
  4. #3
  5. No Profile Picture
    Moderator

    Join Date
    Jun 2002
    Location
    Raleigh, NC
    Posts
    5,243
    Rep Power
    967
    Correct. Once a request has started processing, what the user does in their browser doesn't affect anything. The request will run until it completes or an error is thrown.
  6. #4
  7. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    43
    Rep Power
    4
    Originally Posted by kiteless
    Correct. Once a request has started processing, what the user does in their browser doesn't affect anything. The request will run until it completes or an error is thrown.
    So maybe I could do something like this?:

    Code:
    <cfoutput query="qry">
        <cfif [some condition the user could trigger that CF would notice]>
           <cfabort> [or cfthrow]
        </cfif>
    
         [Do something...]
    </cfoutput>
  8. #5
  9. No Profile Picture
    Moderator

    Join Date
    Jun 2002
    Location
    Raleigh, NC
    Posts
    5,243
    Rep Power
    967
    Each CF request runs in its own thread, so the only way that would work would be to look at something that is shared across reqeusts, such as a session variable.
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    43
    Rep Power
    4
    Actually, I think I've found another way to do what I want, using AJAX and the session scope. I'll post about it again, after I code and test.
  12. #7
  13. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2012
    Posts
    43
    Rep Power
    4
    Originally Posted by Xophe
    Actually, I think I've found another way to do what I want, using AJAX and the session scope. I'll post about it again, after I code and test.
    Wrote it, tested it, and it works like a charm. So here's what I did:

    On the action page, right before the loop begins, I create a structure in the session scope (if it isn't already there), and assign a UUID to this particular job. (This action page runs inside a popup, so there can actually be several jobs running concurrently).

    Code:
    <cflock name="createSTCBulkJobs" timeout="10">
       <cfif NOT structKeyExists(session, "stcBulkJobs")>
    	<cfset session.stcBulkJobs = structNew()>
       </cfif>
       <cfset strThisBulkJobID = createUUID()>
       <cfset session.stcBulkJobs[strThisBulkJobID] = "continue"></cflock>
    When the loop starts, with each iteration it checks the value of session.stcBulkJobs[strThisBulkJobID]. If it's not "continue", then it cfbreaks out of the loop.

    Code:
    <cfloop query="qryWRsToSend">
       <cfif session.stcBulkJobs[strThisBulkJobID] NEQ "continue">
          <cfbreak>
       </cfif>
    To change the value of session.stcBulkJobs[strThisBulkJobID] in the middle of this request, I use a simple ajax function, which is called in two circumstances - when the user clicks a "stop job" button, or when the popup window closes.

    Code:
    function fncStopJob(n_strJobID) {
       var objReq = new ActiveXObject("Microsoft.XMLHTTP");
       var strURL = "/_ajaxMethods/_abortwpbulksendjob.cfm?jobid=" + n_strJobID;
       objReq.open("GET", strURL, false);
       objReq.send();
       var blnResponse = objReq.responseText;
       return blnResponse;
    }
    And _abortwpbulksendjob.cfm is a very simple page - all it does it set session.stcBulkJobs[url.jobid] to equal "stop".

    Code:
    <cfsetting enablecfoutputonly="Yes">
    
    <cfparam name="url.jobid" type="UUID"> 
    
    <cfif structKeyExists(session, "stcBulkJobs") AND structKeyExists(session.stcBulkJobs, url.jobid)>
    	<cfset session.stcBulkJobs[url.jobid] = "stop">
    </cfif>
    	<cfoutput>stopped</cfoutput>
    the code for the button is what you'd expect,

    Code:
    <input type="button" value="Abort Job" onClick="fncStopJob('<cfoutput>#strThisBulkJobID#</cfoutput>');">
    I had to play around a while to create a good onunload function that works, and works with IE8, and eventually ended up with:

    Code:
    window.onunload = window.onbeforeunload = (function(){
       var blnDidstop = false;
       return function(){
       if (blnDidstop) return;
       blnDidstop = true;
       fncStopJob('<cfoutput>#strThisBulkJobID#</cfoutput>');
       }
    }());
    To keep the session scope clean, I structDelete each job from session.stcBulkJobs once the job has completed or has been stopped (and structDelete stcBulkJobs from session if it's empty).

    Code:
    <cfset structDelete(session.stcBulkJobs, "#strThisBulkJobID#")>
       <cflock name="removestcBulkJobs" timeout="10">
          <cfif structIsEmpty(session.stcBulkJobs)>
             <cfset structDelete(session, "stcBulkJobs")>
          </cfif>
       </cflock>
    And that's all there is to it.

    The only fly in the ointment is what if the app didn't have session management enabled, or if it was in a clustered server environment with no sticky sessions? If that were the case, I'd just use a DB table to store, read, and update the [jobid]-[continue/stop] name-value pairs. That would mean a trip to the DB with every iteration of the loop and slow things down slightly, but no big deal, especially if number of records in the table is kept low. To keep it low, I'd probably just run a nightly DB task that empties out the table.

    Comments on this post

    • kiteless agrees
    • ManiacDan agrees

IMN logo majestic logo threadwatch logo seochat tools logo