Overview and Purpose
The Problem
In certain environments and certain situations, long running SOAP requests cause problems.
- certain environments means: this problem has been observed, if either client (priint:comet ID desktop, server priint:renderer) or server (priint:publishing server) run on a Windows 2008 (or newer) System or Windows 8 (or newer).
- certain situations means, that problems will more likely occur, if the server is under heavy load.
- long running in this context means: one or several hours
- problems finally means, that the network connection is closed either by client or server respective any network component or network communication implemention layer, although timeouts hvae been set to a very high value (or disabled at all) on both client and server side.
More information can be found here:
SynAttackProtect not suggested on Win 2008 /Win 2008 R2 According to this article link http://msdn.microsoft.com/en-us/library/ee377058(BTS.10).aspx The SynAttackProtect, TcpMaxHalfOpen, and TcpMaxHalfOpenRetried registry entries are no longer used with Windows Vista and Windows Server 2008. The TCP/IP protocol suite implementation in Windows Vista and Windows Server 2008 was redesigned to provide improved performance and does not require manual modification of these registry entries.Our proposal to workaround these problems is:
- try to reduce complexity (and duration) of requests, that take more than approx. 20 minutes.
- try to fix the problems following the instructions from Microsoft
- try to workaround the problems using asynchronous SOAP requests like described in this document
Status and Outlook
Currently, asynchronous requests are in testing and evaluation phase. There is some handwork required to enable asynchronous calls for placeholders or build scripts, also there are some limitations, which will disallow using asynchronous requests in certain situations.
On the server side, we use the EJB 3.1 @Asynchronous features to enable asynchronous requests.
On the client side, there is no native support for asynchronous requests (the SOAP library linked with priint:comet ID and priint:renderer does not support asynchronous communication), so we have to manually implement a polling mechanism to check if the result for a request is ready.
In future versions there may be cscript functions to execute asynchronous requests or even configuration properties on the ISON side to switch placeholders to asynchronous communication.
Requirements
You need
- a recent 4.0.5 or 4.1 priint:publishing server installation (please use the latest updates available from Werk II)
- recent 4.0.5 or 4.1 priint:comet plug-ins resp. priint:renderer.
- in addition, you need a priint:publishing server Java plug-in not included in the standard delivery. This plug-in can be requested from Werk II (please contact support@priint.com) and is called AsynchronousPubServerPlugin.This plug-in must be deployed (e.g. using autodeploy or Glassfish administration tools).
Asynchronous Calls Implementation
The AsynchronousPubServerPlugin provides a couple of Java / cscripts functions, which allow to implement asynchronous requests in cscript.
The basic functionality is:
- send a SOAP request using a cscript function provided by the AsynchronousPubServerPlugin. This method returns a ticket Id
- repeatedly ask the Server, if the result for this ticket Id is ready. Wait between calls for an appropriate amount of time using the cscript function system::sleep(int timeSeconds)
- if the request is ready, fetch the result using one of the functions provided by the AsynchronousPubServerPlugin (depending on the result type).
Example Cscript
#pragma var "search//Search" #pragma var "replace//Replace" #include "[pubserver]/plugin/com.priint.pubserver.comet.bridge.async.CometAsyncSoapRequest.c" int main() { int ready = 0; int i = 0; StringList result; char * ticketId = 0; char * command = alloc (4096); // build command // // the original load statement for this placeholder was // #pragma var "search//Search"\ // // #pragma var "replace//Replace"\ // call stringlist plugin(globalName='DataProviderManager',methodName='get')\ // [<Session.Id>,'BucketLabel',<Model.Id>,<Entity.Id>,\ // 'product',<Record.Id>,<Record.GroupId>,'','','','','', // <xml.publication>,'','',<xml.documentId>,\ // {'<Variable.search>' => <search>,'<Variable.replace>' => <replace>}, \ // <Java.ResultClass>]:placeholder#268435763#get\ strcpy(command, "plugin(globalName='DataproviderManager',methodName='get')"); strcpy(command, "[<Session.Id>,'BucketLabel',<Model.Id>,'"); // the record Ids must be inserted from the global cscript variables // -> mind the 'quotes'! strcat(command, pPSEntityId); strcat(command, "','product','"); strcat(command, gPSRecordId); strcat(command, "','"); strcat(command, gPSGroupId); strcat(command, "','','','','','',<xml.publication>,'','',<xml.documentId>,"); // for function variables, the global cscript variables must be used also: // (again: 'quoted'!) strcat(command, "'{'<Variable.search>' => '";); strcat(command, search); strcat(command, ','<Variable.replace>' => '";); strcat(command, replace); strcat(command, "'},<Java.ResultClass>]:placeholder#268435763#get"); // start request ticketId = asyncCall(command); // poll for ready state for (i = 0; i < 10000; ++i) { ready = isResultReady(ticketId); wlog ("", "Result ready? %d\n", ready); if (ready) break; system::sleep(1); } if (!ready) return 1; // fetch result result = getResultAsStrings(ticketId); wlog ("", "Result size: %d\n", stringlist::length(result)); for (i = 0; i < stringlist::length(result); ++i) { showmessage("Result %d: %s", i, stringlist::get(result, i)); } // clean up if (command != 0) release(command); if (result != 0) stringlist::release(result); if (ticketId != 0) release(ticketId); return 0; }
Example Explanation
This is an exmaple for a simple placeholder call, which will be executed asynchronous (the result is just shown in a message box, it could as well be inserted in the document using textmodel::replace or similar functions).
The statement sent with the asyncCall method is derived from the original placeholder statement, which looks like this:
#pragma var "search//Search" #pragma var "replace//Replace" call stringlist plugin(globalName='DataProviderManager',methodName='get')[<Session.Id>,'BucketLabel',<Model.Id>,<Entity.Id>,'product',<Record.Id>,<Record.GroupId>,'','','','','',<xml.publication>,'','',<xml.documentId>,{'<Variable.search>' => <search>,'<Variable.replace>' => <replace>}, <Java.ResultClass>]:placeholder#268435763#getEmbedding this statement in a cscript requires some adaption:
- the #pragma var section does not have to be copied. This part is generated by the CometBridge, no matter, if the statement is a "direct" SOAP statement or a cscript
- the actual statement consists of three terms:
- Statement type: call or getlist
-> depending on the type, you will use the asyncCall or asyncGetList function provided by the AsynchronousPubServerPlugin - Result type in this case stringlist
-> can be omitted, because the result type is determined by the getResultAs... call at the end. - the Statement interpreted by priint:publishing server, in most cases starting with the keyword plugin
-> This term has to be provided as argument for the asyncCall resp. asyncGetList function call.
- Statement type: call or getlist
Once again the original statement with comments
The second step is waiting for the request to be ready. In this case, we use a for loop and wait for one second, at maxiumum for 1000 times - which effectively means a timeout of 16 minutes and 40 seconds.#pragma var "search//Search" #pragma var "replace//Replace"// omit call // call=use asyncCall, getlist=use asyncGetListstringlist// omit plugin(globalName='DataProviderManager',methodName='get')[<Session.Id>,'BucketLabel',<Model.Id>,<Entity.Id>,'product',<Record.Id>,<Record.GroupId>,'','','','','',<xml.publication>,'','',<xml.documentId>,{'<Variable.search>' => <search>,'<Variable.replace>' => <replace>}, <Java.ResultClass>]:placeholder#268435763#get // process like shown above and use // as argument for the async... function call
At the time, there is no way to abort a call. If we give up waiting in the cscript, the server still continues processing and will preserve the result, until it is requested by the client.
Finally, if the server reported success, we fetch the result using one of the fetch... methods provided by the AsynchronousPubServerPlugin.
Step by Step: from Sync to Async
The general procedure to change from synchronous to asynchronous requests is:
- isolate the SOAP request, which causes timeout problems (by analyzing the log files written by InDesign plug-in and / or Glassfish server logs).
- get the original statement schema this request was derived from. Depending on the call situation / environment, this can be achieved in several ways:
- for Placeholders: define a placeholder of the affected type in a InDesign document, open the Placeholder values panel and click on the edit button next to the load statement. This should show up a script dialog, which contains the original statement like shown above (similar procedure for Table insert scripts)
- for Panel scripts, such as build scripts etc.: in some cases, the statement is hard coded incscript and can just be copied from this script. If you use built in functions - like publication::get_document_product_selection, publication::get_document_product_planning or similar - you have to cpoy the statement from the log files and replace the concrete values used for this request by appropriate tags.
Most of the required information can be derived from the cscript documentation, if you need further information request for support at support@priint.com. - for Find statements, Panel statements or Preview statements: load the findstatements.xml resp. panelstatements.xml or previewstatements.xml file using the Script / file ID section in the Placeholder values panel and copy the statement from the XML document. Be aware to decode XML entities (such as <, > or &), otherwise the statement wont work in cscript.
-
create / edit the cscript, which should use asynchronous requests. Include the client stub code to call the AsynchronousPubServerPlugin methods:
#include "[pubserver]/plugin/com.priint.pubserver.comet.bridge.async.CometAsyncSoapRequest.c"
- Depending on the result type, send this statement using either asyncCall or asyncGetList. Use asyncCall for simple data types, usually placeholder load statements, and asyncGetList for complex data, such as product lists etc.
char * ticketId = asyncCall("statement"); // or char * ticketId = asyncGetList("statement");
- implement busy waiting following the pattern shown above. Choose appropriate number of iterations / sleep time
for (i = 0; i < 10000; ++i) { ready = isResultReady(ticketId); wlog ("", "Result ready? %d\n", ready); if (ready) break; system::sleep(1); }
- if request is ready, fetch the result using one of the fetch... functions provided by the AsynchronousPubServerPlugin (fetchResultAsStrings, fetchResultAsRecordIds, fetchResultAsProducts)
StringList result = getResultAsStrings(ticketId); // or ProductList result = getResultAsProducts(ticketId); // or IDTypeList result = getResultAsRecordIds(ticketId);
- Note: isResultReady also returns true (1), if the request fails. In this case, the error is reported, when you try to fetch the result.
API / Function Overview
asyncCall
char* asyncCall(char* statement);Submit a asynchronous call request. Use this function for simple return data types, such as placeholder load statements (which typically return a StringList) etc.
On success, the function returns a ticketId, which can be used for subsequent isResultReady and fetch... calls.
- Parameter: statement - SOAP statement as described above
- returns ticketId or null on failure.
asyncGetList
char* asyncGetList(char* statement);Submit a asynchronous getList request. Use this function for complex return data types, such as product lists (e.g. findstatements) or RecordIds (e.g. table insert scripts).
On success, the function returns a ticketId, which can be used for subsequent isResultReady and fetch... calls.
- Parameter: statement - SOAP statement as described above
- returns ticketId or null on failure.
fetchResultAsRecordIds
IDTypeList fetchResultAsRecordIds(char* ticketId);
Fetch the result of a request as a list of RecordIds (cscript type idtype). This function must not be called, before the isResultReady call for this ticketId returns true.- Parameter: ticketId - ID for the request returned by a previous asyncCall or asyncGetList request.
- returns IDTypeList or null on failure.
fetchResultAsStrings
StringList fetchResultAsStrings(char* ticketId);
Fetch the result of a request as a list of Strings (cscript type string). This function must not be called, before the isResultReady call for this ticketId returns true.- Parameter: ticketId - ID for the request returned by a previous asyncCall or asyncGetList request.
- returns StringList or null on failure.
fetchResultAsProducts
ProductList fetchResultAsProducts(char* ticketId);
Fetch the result of a request as a list of Products (cscript type product). This function must not be called, before the isResultReady call for this ticketId returns true.- Parameter: ticketId - ID for the request returned by a previous asyncCall or asyncGetList request.
- returns ProductList or null on failure.
isResultReady
int isResultReady(char* ticketId);
Check, if a request is ready. Note, that the function also returns true (1), if the result finished with an error.
- Parameter: ticketId - ID for the request returned by a previous asyncCall or asyncGetList request.
- returns 1, if the request finished, 0 otherwise.