Pegasus Enhancement Proposal (PEP)

PEP #: 140

Title: HTTP 1.1 Transfer Encoding Support

Version:  4.3

Created: 26 February 2004

Authors: Brian G Campbell/EMC

Status:  approved
 
 

Version History:
 
Version Date Author Change Description
 1.0 26 February 2004 Brian G Campbell Initial Submission
 2.0 24 March 2004   Brian G Campbell  First review changes 
3.0 16 April 2004
Brian G Campbell Second review changes
4.0
23 July 2004
Brian G Campbell phase 2  changes
4.1
16 August 2004
Brian G Campbell phase 2  changes - address review comments
4.2
17 August 2004
Brian G Campbell phase 2  changes - address more review comments
4.3
20 August 2004
Brian G Campbell phase 2  changes -  final approval



 

Abstract: This document describes enhancing Pegasus to support HTTP 1.1 Transfer Encoding. Not all of the features will be supported (see below).
This PEP is approved in the knowledge that there are some outstanding issues which will be be re-visited during the implementation process.


Definition of the Problem

The standards referenced in this PEP are as follows:
 


(NOTE: as of this revision, specification [2] 1.2c has not been officially released - the link points to the previous version)

1 Non-compliance with Standards

Specification [1] requires that transfer encoding with value="chunked" be supported by any client claiming to be HTTP 1.1 compliant (specification [1] section 3.6.1 at end). Currently, the pegasus client does not do this. However, the pegasus server is currently compliant because HTTP does not require servers to send chunked even if the client requests it to do so.

2 Heavy Server Memory Usage

Large responses can utilize huge amounts of memory. This feature allows the message to be sent over the network in "chunks" as they are sent down by the server application. Each chunk is at a minimum a complete CIM object. After each XML encoded object(s) is sent to the client, the memory is released. This means that a message that would otherwise amount to 100mb (of XML encoding and application data) can be broken up and sent in pieces so that all 100mb are not in memory at once. This realizes a big memory savings since both XML encoded objects and application objects will be released after they are sent to the client as they become available.  See response thresholding below on how this will happen.

3 Limitation of Real Time Server Application Data

Today, a server application cannot control whether the delivered responses go immediately to the client. All the data of the request must be collected first then allowed to be sent to the client. (i.e. ResponseHandler.complete() must be called first before anything reaches the client). There are many cases in which data could be sent as it becomes available to (or processed by) the server application. The parts could in fact be interpreted independently if sent separately (as opposed to sending the entire message back). This proposal would address this problem.

Example:
Client requests EnumerateInstances on all volumes (LUNs) of a Storage Device. The server application asks the embedded device for this data. There exists 10 volumes, but 1 is extremely busy and cannot service the request for many seconds. 9 out of the 10 are communicated to the server application immediately, but the server application needs to wait for the 10th before responding to the client. The 9 are independent (in this case) and could have been sent seconds earlier as they became available. This could also solve potential client timeout problems when in fact some data is ready earlier than the set timeout (but see next note).
 
Important note:
Although the proposed changes to the server allow sending pieces of the application data as they arrive, the pegasus client will NOT realize this faster response because this proposal states that the client will still wait for the entire message to arrive before handing the data to the client application. This section simply states that this change has the potential benefit for the client IF the client side is changed as well.

4 Non Pegasus Server Compatibility

Today, the Pegasus client is incompatible with non-pegasus CIM/HTTP servers that support (and send) transfer encodings with value="chunked" (even after checking for HTTP 1.1 compatibility)

Proposed solution

The broader implication of chunked transfer encoding is that the Pegasus server has to be fundamentally changed in how response data flows from the server application through the server to the client. Today, this data queues up within a ResponseHandler instance when the server application calls "deliver()".  All the response data must be collected before the provider calls "complete()" at which time the ResponseHandler data is transferred to the Response message. This data now gets sent in its entirety to the client. This PEP now proposes that the "deliver()" call sends the data immediately to the client (with some restrictions) without waiting for "complete()". This change goes hand in hand with the idea of the HTTP level chunking process. If this change (at the server level)  was not proposed along side the HTTP chunking change, then there would be no benefit to having HTTP chunking in the server. This is spelled out below in the Behavior of the Server section below. The HTTP  chunking changes will be referred to as "phase 1" changes and  making the deliver() call asynchronous will be referred to as "phase 2" changes. These 2 phases can be done independently. Phase 1 is slated for 2.4 and phase 2 is slated for 2.5. There will be features not supported (see below) for phase 2 that could possibly put in at a later time but is currently not planned. (TBD- phase 3 and beyond)

HTTP 1.1 transfer encoding will be implemented as specified in Specification [1] and Specification [2] except for the features excluded below. 

HTTP features not supported

The following HTTP features will NOT be supported. Their syntax will be parsed, but the values will be dropped and an error given if warranted. Note that the terminology "features not supported" simply means that HTTP level chunked encoding does not apply to the feature in question. That feature will still of course work normally. Many of these below bring up a variety of issues. Refer to discussion section as well. These are features not supported in phase 1

1 Transfer encoding values

Only header values of "chunked" and "identity" (i.e. "do nothing") values are supported on a client receive. Any others are not supported. The server will not support parsing of any kind of transfer encodings to the body of an incoming request. Specification [1]  section 3.5 lists all values . This also applies to the statement in Specification [1]  section 14.41 (which allows other values from other specifications):

"Additional information about the encoding parameters MAY be provided by other entity-header fields not defined by this specification."

This statement is NOT supported.

2 Qvalues

Specification [1] section 3.9 describes qvalues (or Quality Values). These parameters apply transfer encoding values in order stated. They reside in the TE header field as extension syntax. Since only a single value of "chunked" or "identity" will be supported, the qvalues for transfer encodings are not needed nor supported.

3 Chunk extensions

Chunk extensions are meta data intended for the end application and have no meaning nor behavior rules at the protocol level. They reside on the chunk length line. Specification [1]  section 3.6.1

4 Trailer

A trailer is a header that may optionally occur after the last chunk and is considered part of the message body. It is detected if the first byte after the last chunk is not the start of the message terminator. In general, the contents of the trailer will be ignored EXCEPT for anything relating to errors. This exception is due to the fact of Specification [2] section 3.3.11 which deals with CIMError embedded into the trailer. The server will ALWAYS send back the "trailer" field in the header as well as the trailer IF and only if there was any server error AND the client requested trailers or chunking. See behavior section if this occurs

5 TE header field

The TE header field will be ignored by the server EXCEPT for values of "chunked" or "trailers".  This exception is due to the fact of Specification [2] section 4.2.19.  The client WILL make use of this field for requests (see behavior)

6 Proxy/Gateway behavior

The specification talks about this in detail in relation to chunked encoding behavior. There are many areas throughout specification [1] that allude to this. Search on "proxy" and "gateway".

CIM features not supported

The following describe CIM features that are not supported. Note that the terminology "features not supported" simply means that immediate asynchronous behavior does not apply to the feature in question. That feature will still of course work normally.

1 Indications

Indications will never be sent with any chunked transfer encodings

2 Operations

CIM operations "InvokeMethod" and "ExecQuery" (and the above mentioned Indications) will not be sent asynchronous. Only because to make these asynchronous would require more work which is not planned at this time. The HTTP header will still have transfer-encoding keyword, but the message will only be sent when these operations complete() method is called by the server application.

Internal features not supported

Note that the terminology "features not supported" simply means that asynchronous behavior from the deliver() calls do not apply to the feature in question. That feature will still of course work normally

1 Interface types

Pegasus users can configure different "interface-types" such as java (JMPI) or C (CMPI) or remote CMPI. The default interface type (C++) is currently the only interface to be officially supported (for phase2) in the interest of time. This of course can be changed in the future by those who need these other interfaces to support asynchronous behavior.

A side note:
Since the code to be changed is common between all interface types, it is likely that the rest will probably work anyway.  The reason that these others are "unsupported" for phase 2 is that the necessary testing time to confirm that they support chunking is not planned at this time..

2. Out-of-process-providers

Out of process providers (see PEP-72) will not be supported in the interest of time. Again, this can be changed in the future for those who need the support.


Behavior

1. HTTP behavior

This section describes the proposed behavior at the HTTP level. This affects client and server behavior. The code modifications will be confined mostly to the HTTPConnection class

1 Client receiving
The Pegasus client will be modified to detect the transfer-encoding with value="chunked" in the header and the subsequent chunks in the body. All chunks will be read until the final chunk arrives (except for errors - see below) and any optional trailers. The chunk lengths, chunk extensions, chunk terminators and trailer will be stripped out and the message content length will be set according to the data that arrived (minus chunk meta data). The resulting message now appears the same as if no transfer encodings were applied. If a trailer was received with an error, any data received will be discarded and the client application will receive the CIM error. If there was any parsing error during the receiving of the message, the client application will receive the appropriate error as if the server had sent it directly. No client code execution path needs to be changed after this to preserve the behavior today. The client will support the
receiving of transfer encodings regardless of whether the client requested the response in this form. This has to be done to accommodate non-pegasus servers that send chunked transfer encodings according to the looser rules in Specification [1]  (Specification [2] requires the transfer encodings to be requested in order for them to be sent back in that form).
2 Client sending
The client will not send transfer encodings in the body but will ALWAYS send both "chunked" and "trailers" with its request in the "TE" header field. Old servers that do not understand these values should be ignoring them.
3 Server receiving
The server will look for the "TE" header field. If the values of "chunked" or "trailers" occur, then chunked transfer encoding WILL be sent back. Other values in the TE header field are ignored.
4 Server sending
The server will be modified to allow transfer encodings of type "chunked" to be sent asynchronously to the client by the server application before the entire response is ready. However, this will only occur if the client request has values of "chunked" or "trailers" in the "TE" header field. (specification [2] requirement). Even though the HTTP protocol allows any number of bytes representing any state of any kind of data, the minimum chunk will contain (at least) one complete CIM object. This will eliminate any unnecessary complication on the client side for future asynchronous client receiving. 
5 Non-supported transfer encoding values
Any detected non-supported transfer encoding values received in the client (see above) or duplicated supported values,  will throw out the entire HTTP message even if chunked is one in the list. This comes from Specification [1] section 3.6. (see error conditions). The server will not support any transfer encoding in the request body.
6 Error Conditions
The server (or client) may run into conditions which would generate the following HTTP standard errors while processing a response or request.
 


If the client detected these errors, then these are returned to the client application as if the server itself sent this error.  Any data that was received will be discarded. Also, if there was a CIMError or CIMStatusCode in the trailer, the same thing will happen as just described but using the numbers and data in these fields. Except for  "413 Request Entity Too Large" (when there was too much data in the response or request), all other errors and behavior are new to this PEP.
 
Conditions causing above errors:

The server will send back CIMError, CIMStatusCode in the trailer IF a CIM error occurred AND a trailer or chunk was requested by the client.  The server will also place these keywords in the trailer header line whether there is an error or not (if client requested so).  This behavior is required by specification [2]. A CIM error does not necessarily mean an HTTP level error (listed above). A CIM error is typically generated by the server application.  If the operation requested generated many threads to handle each implied class in the request, then each thread has the potential for error. This error may occur in mid stream of any given thread. The current solution is to stop the flow of data of that one thread but allow the rest to continue.

Notes:

- The HTTP level CIMError is not to be confused with the DMTF CIM_Error class that is transmitted in the XML payload.

- There are known open issues with error conditions. See discussion below.

- Users of the client side pegasus code should see no behavior change (also see error handling in discussion section).  Cuurently, there are no NEW requirements for the client to have to reconnect after a HTTP level failure (which now includes chunked transfer-encoding parsing)

2 Internal Server behavior

This section describes the new proposed behavior in the server that occurs above the HTTP level. This behavior is not dictated by any standard. These changes are necessary to take advantage of the chunking changes being added to the HTTP level. In summary, the modifications being proposed below change the deliver() method call in the ResponseHandler class hierarchy (that is called from within the server application code) from being synchronous to asynchronous.  More API/class detail can be found at http://cvs.opengroup.org/pegasus-doc

Summary of functional areas:

It is important to understand the data flow today before the new modifications can be understood. Today the flow of an incoming request (operation) and subsequent response is as follows on the server side:


1 HTTPConnection
class:          HTTPConnection
dir:              Common
file(s):         HTTPConnection.cpp HTTPConnection.h

proposal:  (phase 1 changes - see HTTP behavior above)

2 CIMOperationRequestDispatcher
class:            CIMOperationRequestDispatcher, OperationAggregate
dir:                Server
file(s):           CIMOperationRequestDispatcher.cpp CIMOperationRequestDispatcher.h

proposal
:

OperationAggregate:

The response functionality that comes back to this class via a callback from ProviderManagerService and/or repository  (see CIMOperationRequestDispatcher::_forwardForAggregationCallback()) will funnel through a modified OperationAggregate class. The function of the OperationAggregate class now becomes a sequencer rather than an aggregator. This is because each message coming from ProviderManagerService/repository are object(s) that need to be passed immediately down to CIMOperationResponseEncoder and then to the client. It will not wait for the completed message from all launched threads for that request. This has ramifications for error conditions. (see below).  Each CIMResponseMessage will contain information about the chunk (the index/count and a complete flag). This means the resequence function will restamp the chunks according to its own chunk count which is a summation of all chunks (objects) that came in from any thread (including synchronous repository calls) at this point for this request.  It will also restamp the complete flag when the last complete of the last thread and/or repository response is received. OperationAggregate will have an additional member tracking completed responses. For every thread that sends a chunk with the complete flag on, this is counted as that thread having finished. At this time, repository responses will always be complete as they are synchronous in nature to start with. It is very important that all other threads (for this request) are locked out while resequence executes. Once the response is resequenced, it is now ready to be sent down to the encoder and back out to the client. It is also important to note that we still need to be locked for sequential execution of the resquenced response because the client MUST get the first chunk first because it contains the opening XML tags and the client MUST get the last chunk last as it contains the closing XML tags. What happens in between could in theory be sent in parallel, but for the sake of simpler code each chunk will be sent to the client sequentially after resquencing.

Note:
To reiterate for clarification, the thresholding works on a per thread within a request basis. Right now, there is no concept of a "global" per request threshold.  If this did exist, it would enable the entire request to threshold the number of objects before sending to the client. This of course can be proposed for later phases.

The algorithm will look something like:

void OperationAggregate::resequenceResponse(CIMResponseMessage& response)
{
    // lock out all other threads so we can restamp the current chunk of the current thread.
    _mutex.lock(pegasus_thread_self());

    response.setIndex(_totalReceived++);
    if (response.isComplete() == true)
    {
        _totalReceivedComplete++;

        // only one of many threaded messages are complete, set to incomplete
        // until ALL completed messages have come in
       
        if (_totalReceivedComplete < _totalIssued)
            response.setComplete(false);
      
    }
    _mutex.unlock();
}


enqueuing:

There needs to be a method to synchronously send the response in this service. The code may look like:


/*
 * send the given response synchronously using the given aggregation object.
 * return whether the sent message was complete or not. The parameters are
 * pointer references because they can be come invalid from external deletes
 * if the message is complete after queuing. They can be zeroed in this
 * function preventing the caller from referencing deleted pointers.
 */

Boolean
CIMOperationRequestDispatcher::_enqueueResponse(OperationAggregate *&poA,
                                                                                                CIMResponseMessage *&response)
{
    lock();

    Boolean isComplete = false;

    try
    {
        poA->resequenceResponse(*response);
        isComplete = response->isComplete();

        // reset the response destination id to what was set in the request
        response->dest = poA->_dest;

        Uint32 type = poA->getRequestType();
        //request->getType()
       
        switch(type)
        {
            case CIM_ASSOCIATORS_REQUEST_MESSAGE :
            case CIM_REFERENCES_REQUEST_MESSAGE :
            case CIM_REFERENCE_NAMES_REQUEST_MESSAGE :
            case CIM_ASSOCIATOR_NAMES_REQUEST_MESSAGE :
                // execute today's code that exists and sets host and namespace if missing in handle*ResponseAggregation where * is the operation

            default :
                PEGASUS_ASSERT(0);
                break;
            
        } // switch
       
         // this is a synchronous call today that passes response to encoder
        lookup(response->dest)->enqueue(response);
        } // try

        catch(...){}

        if (isComplete == true)
        {
            // also deletes the copied request attached to it
            delete poA;
            poA = 0;
        }

        // after sending, the response has been deleted externally
        response = 0;

        unlock();
        return isComplete;
}



callbacks:

Any callback that is assigned on the request side will be enhanced to call the resequencer (above) and then send the response sequentially to the encoder. The callbacks to be modified are _forwardForAggregationCallback and  _forwardRequestCallback. The aggregation callback is used when there are multiple threads servicing the one request and hence has to "aggregate" (in the original context) all the parallel responses coming back. The other callback is used when only a single (but still asynchronous) response is anticipated. Even still, the returned response must be resequenced with potential repository responses. The aggregation callback may look like:

void CIMOperationRequestDispatcher::_forwardForAggregationCallback(
    AsyncOpNode *op,
    MessageQueue *q,
    void *userParameter)
{
      // today's code ... here

        //new code
        Boolean isComplete = response->isComplete();
        if (isComplete == false)
        {
            // put back the async request because there are more chunks to come.
            op->put_request(asyncRequest);
        }
        else
        {
            // these are per thread instantiations
            delete asyncRequest;
            delete asyncReply;
            op->release();
            service->return_op(op);
        }

        // After resequencing, this flag represents the completion status of
        // the ENTIRE response to the request.

       
        isComplete = service->_enqueueResponse(poA, response);
}


The other callback will have similar behavior

3 SimpleResponseHandler
class(es):       SimpleResponseHandler SimpleInstanceResponseHandler SimpleObjectPathResponseHandler SimpleMethodResultResponseHandler SimpleIndicationResponseHandler    SimpleObjectResponseHandler SimpleInstance2ObjectResponseHandler SimpleValueResponseHandler SimpleClassResponseHandler
dir:                 ProviderManager2
file(s):            SimpleResponseHandler.cpp SimpleResponseHandler.h


proposal:

NOTE: this proposal does NOT change any interfaces!

The base class SimpleResponseHandler, is one of the classes in the multiple inheritance hierarchy of the derived operation response classes (see below).  the remaining classes (listed above) all inherit the base class. The following protected virtual functions will be added to  this base class and all derived classes:

// returns the number of objects in this handler
virtual Uint32 size() const { return 0;    }

// clear any objects in this handler
virtual void clear() {}


Also,  a new function will be defined in the base class that all deliver() calls will call underneath to determine whether to send the data asynchronously (i.e immediately) or not. This function will be called for both the deliver() and complete() virtual function calls and will be protected. The deliver() call will always call send() with isComplete = false and complete() will always call send() with IsComplete = true.

// send (deliver) asynchronously with restrictions
protected: virtual void send(Boolean isComplete);

The complete call will now just call the send method (see OperationResponseHandler below for operation behavior)

virtual void complete(void)
{
    send(true);
}


The body of the code will live in SimpleResponseHandler.cpp and be as follows:

#include "OperationResponseHandler.h"
void SimpleResponseHandler::send(Boolean isComplete)
{
    // If this was NOT instantiated as a derived OperationResponseHandler class,
    // then this will be null but is NOT an error. In this case, there is no
    // response attached, hence no data,so there is nothing to send. else we have
    // a valid "cross-cast" to the operation side

    OperationResponseHandler *operation =
        dynamic_cast<OperationResponseHandler*>(this);

    if (operation)
        operation->send(isComplete);
}

The comments note that the base class and the derived simple classes may not be a derived operation class. If this is the case, then there is no data to send asynchronously and the deliver() and complete() calls default to today's standard synchronous mode. However, if the instantiated class is an OperationResponseHandler class (see below) or a derived  OperationResponseHandler class, then this function will call the operation send function.  The operation class header file will only be included in the this source file and not the header.

Notes:

- The ResponseHandler class hierarchy is very involved with 2 levels of multiple inheritance. It is important to have an understanding of the hierarchy to understand this proposal.

- The new functions cannot be made abstract in the base class because this class has already been defined to be concrete. The new functions in this base class will effectively be a "do nothing" function whereas the derived classes will then redefine these functions

- SimpleMethodResultResponseHandler size() function represents the parameter out list, not the return value. The clear() function clears the out parameters and the return value.

- Some of the derived simple classes (listed above) are used in operation derived classes that were stated as not supporting asynchronous delivers (such as indications), however the new functions are still needed for correctness and consistency
4 OperationResponseHandler
class(es):       OperationResponseHandler GetInstanceResponseHandler  EnumerateInstancesResponseHandler EnumerateInstanceNamesResponseHandler  CreateInstanceResponseHandler GetPropertyResponseHandler SetPropertyResponseHandler ExecQueryResponseHandler AssociatorsResponseHandler AssociatorNamesResponseHandler ReferencesResponseHandler  ReferenceNamesResponseHandler InvokeMethodResponseHandler
dir:                 ProviderManager2
file(s):            OperationResponseHandler.cpp OperationResponseHandler.h


proposal:

NOTE: this proposal does NOT change any interfaces!

The OperationResponseHandler class is one of the inherited classes of the other classes listed above. These classes are the operation based response handler classes.  Any handler instantiated as one of the above derived classes has the potential to send data back asynchronously from a deliver() call. (although some are not going to be supported - see not supported section). Keep in mind, although OperationResponseHandler is a base class to the others listed above, it is also a concrete class. This means this base class may be instantiated as well, but then there is no data expected back on the operation response because the SimpleResponseHandler hierarchy actually contains any data returned.

The following protected virtual functions will be added to this base class and all derived classes:

        // transfer any objects from handler to response. this does not clear() the handler
        virtual void transfer() {}

        // validate whatever is necessary before the transfer
        virtual void validate() {}

These functions are needed for a single send() call to do virtual tasks. The transfer() takes the data stored in the SimpleResponseHandler side of the hierarchy (if this is a derived operation instantiation) and transfers it to the response on the OperationResponseHandler side of the hierarchy. The validate() function is used to validate the data before a transfer is executed. Typically this is used to validate the minimum number of responses being sent. The base class is a "do nothing" because it is a concrete class as well as there is no data to transfer or validate. Because these new methods exist, the derived complete() call can now be eliminated. It will just exist as a single virtual in SimpleResponseHandler

The following private members will be added:


        Uint32 _responseObjectTotal;
        Uint32 _responseMessageTotal;
        Uint32 _responseObjectThreshold;
   

The _responseObjectTotal represents how many total objects (up to any point in time) have been sent asynchronously.

The _responseMessageTotal is how many responses (up to any point in time) have been sent asynchronously. Note that each response may have 0 to N objects! The message total is roughly analogous to HTTP level chunks EXCEPT for responses that ONLY represent opening or closing XML but NOT any data.

The _responseObjectThreshold represents the threshold used for this handler to represent the maximum object count needed before a response is sent asynchronously (except if a response is marked as complete, in which case it is sent regardless). For operations that do not have any expected data (e.g.  CreateSubscription), responses are not sent until marked complete. After testing different thresholds for performance, a default value of 100 has been given if not set. This is not an ordained number, but one that was seen through many different testing scenarios as being somewhere between time/performance friendly and memory friendly for many applications. Of course any particular environment may be different.  If a particular application has need to change this number, then another container can be added to the OperationContext class in which the application can call SetContext on it. This container does not exist as of yet, but can be added in future phases if deemed necessary.  Note that thresholding is on a per instance handler. One request may generate many handlers and this does not apply to control providers nor the repository service.

Also, the threshold is a compile time define. If server applications cannot deal with the proposed default threshold, then the OperationResponseHandler.cpp file can be recompiled with a different number passed in. Note that 0 will be treated as ~0 which is the same as having no threshold (message will be sent when complete() is called).

The code will be as follows:

#ifndef PEGASUS_RESPONSE_OBJECT_COUNT_THRESHOLD
#define PEGASUS_RESPONSE_OBJECT_COUNT_THRESHOLD 100
#elif PEGASUS_RESPONSE_OBJECT_COUNT_THRESHOLD  == 0
#undef PEGASUS_RESPONSE_OBJECT_COUNT_THRESHOLD
#define PEGASUS_RESPONSE_OBJECT_COUNT_THRESHOLD  ~0
#endif


This send method will be called from SimpleResponseHandler.send() . The send algorithm will look similar to:

void OperationResponseHandler::send(Boolean isComplete)
{
    SimpleResponseHandler *simpleP = dynamic_cast<SimpleResponseHandler*>(this);

    // It is possible to instantiate this class directly (not derived)
    // The caller would do this only if the operation does not have any data to
    // be returned

    if (! simpleP)
    {
        // if there is no data to be returned, then the message should NEVER be
        // incomplete (even on an error)
        if (isComplete == false)
            PEGASUS_ASSERT(false);
        return;
    }

    SimpleResponseHandler &simple = *simpleP;
    PEGASUS_ASSERT(_response);
    Uint32 objectCount = simple.size();

    // have not reached threshold yet
    if (isComplete == false && objectCount < _responseObjectThreshold)
        return;

    CIMResponseMessage *response = _response;

    // for complete responses, just use the one handed down from caller
    // otherwise, create our own that the caller never sees but is
    // utilized for async responses underneath

    if (isComplete == false)
        _response = _request->buildResponse();

    _response->setComplete(isComplete);

       _responseObjectTotal += objectCount;
    Uint32 responseMessageCount = response->getIndex();

    // since we are reusing response for every chunk,keep track of original count
    _response->setIndex(_responseMessageTotal++);

    validate();
    transfer();
    simple.clear();

    _response->operationContext.
        set(ContentLanguageListContainer(simple.getLanguages()));

    // call thru ProviderManager to get externally declared entry point

    if (isComplete == false)
    {
       ProviderManagerService::handleCimResponse(*this);
    }

    // put caller's allocated response back in place
    _response = response;
}


5 ProviderManagerService
class:             ProviderManagerService
dir:                 ProviderManager2
file(s):            ProviderManagerService.cpp ProviderManagerService.h


proposal:

The provider manager service will add a method to handle the asyncronous response. This is a logical home for this new function since the service handles messaging to begin with.

The method will look similar to:

/*static */ void
ProviderManagerService::handleCimResponse(CIMRequestMessage &request,  CIMResponseMessage &response)
{
    CIMStatusCode code = CIM_ERR_SUCCESS;
    String message;

    try
    {
        // only incomplete messages are processed because the caller ends up
        // sending the complete() stage
        PEGASUS_ASSERT(response.isComplete() == false);
       
        AsyncLegacyOperationStart *requestAsync =
            dynamic_cast<AsyncLegacyOperationStart *>(request._async);
        PEGASUS_ASSERT(requestAsync);
        AsyncOpNode *op = requestAsync->op;
        PEGASUS_ASSERT(op);
        PEGASUS_ASSERT(! response._async);
        response._async = new AsyncLegacyOperationResult
            (requestAsync->getKey(), requestAsync->getRouting(), op, &response);
       
        // set the destination
        op->_op_dest = op->_callback_response_q;
       
        MessageQueueService *service =    
            dynamic_cast<MessageQueueService *>(op->_callback_response_q);

        PEGASUS_ASSERT(service);

        // the last chunk MUST be sent last, so use execute the callback
        // not all chunks are going through the dispatcher's chunk
        // resequencer, so this must be a synchronous call here
        // After the call is done, response and asyncResponse are now invalid
        // as they have been sent and deleted externally
       
        op->_async_callback(op, service, op->_callback_ptr);
       
    }

    catch(CIMException &e)
    {
        code = e.getCode();
        message = e.getMessage();
    }
    catch(Exception &e)
    {
        code = CIM_ERR_FAILED;
        message = e.getMessage();
    }
    catch(...)
    {
        code = CIM_ERR_FAILED;
        message = cimStatusCodeToString(code);
    }

    if (code !=  CIM_ERR_SUCCESS)
        response.cimException = PEGASUS_CIM_EXCEPTION(code, message);
}


Note that responses marked as complete do not come through this call. The handler.send() call simply stamps the response and returns out of the provider stack (see OperationResponseHandler.send()) if marked complete. This allows the original response allocated in ProviderManagerService to be used as the final completed response to be sent as it is today and NONE of the ProviderManagerService calling code needs to be modified!

This algorithm is fairly similar to what happens today after the provider has queued up the data and completed its function call. The difference here is that this will call the callback to CIMOperationRequestDispatcher and wait for the complete cycle of the response from the point of deliver() all the way to when HTTPConnection does a tcp.send() to the client. This waiting is important for these reasons:

The overhead to send each response through the mechanism includes a new CIMResponseMessage and 2 async helper instances (AsyncOpNode, AsyncLegacyOperationResult).  It is simply dealing with locks and pointers during the deliver() process. Keep in mind that the amount of data being sent is the same. As the object response threshold is set lower, the overhead becomes higher, but of course the trade-off is not using exorbitant amounts of memory at once.

6 CIMOperationResponseEncoder
class:            CIMOperationResponseEncoder
dir:                Server
file(s):           CIMOperationResponseEncoder.cpp CIMOperationResponseEncoder.h

proposal:


The encoder change simply has to convey to the sendResponse method the message index and if the message is complete or not. This is a phase 1 change.


class:            XmlWriter
dir:                Common
file(s):           XmlWriter.cpp


proposal:

The formatters will be modified to understand first, middle, last. Any combination of all 3 states may occur. "middle" implies actual object data (i.e. "body" argument).  A given response may have all of or just one of these states. This is a phase 1 change.


Array<Sint8> XmlWriter::formatSimpleIMethodRspMessage(
    const CIMName& iMethodName,
    const String& messageId,
    HttpMethod httpMethod,
    const ContentLanguages & httpContentLanguages,   
    const Array<Sint8>& body,
    Uint64 serverResponseTime,
        Boolean isFirst,
        Boolean isLast)
{
    Array<Sint8> out;

        if (isFirst == true)
        {
            // NOTE: temporarily put zero for content length. the http code
            // will later decide to fill in the length or remove it altogether
            appendMethodResponseHeader(out, httpMethod, httpContentLanguages, 0, serverResponseTime);
            _appendMessageElementBegin(out, messageId);
            _appendSimpleRspElementBegin(out);
            _appendIMethodResponseElementBegin(out, iMethodName);
            // output the start of the return tag. Test if there is response data by:
            // 1. there is data on the first chunk OR
            // 2. there is no data on the first chunk but isLast is false implying
            //    there is more non-empty data to come. If all subsequent chunks
            //    are empty, then this generates and empty response.
            if (body.size() != 0 || isLast == false)
                _appendIReturnValueElementBegin(out);
        }

    if (body.size() != 0)
    {
            out << body;
    }

        if (isLast == true)
        {
            if (body.size() != 0 || isFirst == false)
                _appendIReturnValueElementEnd(out);
            _appendIMethodResponseElementEnd(out);
            _appendSimpleRspElementEnd(out);
            _appendMessageElementEnd(out);
        }

    return out;
}
7 Error Conditions

Today, if any of the responding threads contains an error, the data for that thread is discarded (except if all threads have errors, it chooses the first one). To reiterate from above, the thread data will simply be terminated at the point that the error is encountered since some data may have already been sent to the client. (see  discussion on errors below)

Rationale

See "Definition of the Problem" section

Schedule

The targeted code base is 2.4 for phase 1 and 2.5 for phase 2. The date is whenever that is scheduled to be released.

Discussion

Requirements Issues

 

1 Error Conditions

server
Does every one agree on the behavior of the error conditions for both the HTTP/CIM level and asynchronous deliver() errors?.   Again, when an error occurs from the backend (server application/provider), what should the behavior be ? Remember that some data has may have already reached the client.

- stop data of that one thread (currently documented above)
- stop all data (and send terminating chunk to client)
- skip over the one chunk on that one thread and continue to let data flow.

- other error handling  issues ?
client
- If a client gets a chunk decoding parse error OR an HTTP level error (NOT a CIM error) on the response, should the connection be dropped ? The current plan is to log and continue.

2 Server Application Control of transfer encodings

- The default object threshold for asynchronous responses is 100. The pegasus builders may change this number through a compile time define (as stated above in OperationResponseHandler)
 

3. Content-languages

- Today, if there is a content language mismatch between any of the responses, then they are all set to empty.  Since responses may have already been sent, we cannot do this. The ultimate solution is to place the content language in the trailer if all languages agree from all response chunks. This is a known issue and the architecture committee has agreed to deal with the problem after this document gets approved and the first cut of the code is checked in. The intial code will simply set the content languages to EMPTY within each chunked response.

Design Issues


1 Monitor2

NOTE: as of last discussion, the change will ONLY go into monitor1. It is assumed PEP-183 will reconcile monitor1 vs monitor2

2 Internal Server Design

Comment here on the proposed design/methodology for asynchronous deliver() calls. There are many implementation issues and I want to make sure they are covered. Some issues that have been brought up:
 


Copyright (c) 2004 EMC Corporation; Hewlett-Packard Development Company, L.P.; IBM Corp.; The Open Group; VERITAS Software Corporation

Permission is hereby granted, free of charge, to any person obtaining a copy  of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED  "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Template last modified: January 20th 2004 by Martin Kirk
Template version: 1.6