int 
MemCacheClient::Combine(
    const char *    aType,
    MemRequest *    aItem, 
    int             aCount
    )
{
    if (aCount < 1) {
        mTrace.Trace(CLDEBUG, "%s: ignoring request for %d items",
            aType, aCount);
        return 0;
    }
    CR_ASSERT(*aType == 'g' || *aType == 'd'); // get, gets, del

    MemRequest * rgpItem[MAX_REQUESTS] = { NULL };
    if (aCount > MAX_REQUESTS) {
        mTrace.Trace(CLDEBUG, "%s: ignoring request for all %d items (too many)", 
            aType, aCount);
        return -1; // invalid args
    }

    // initialize and find all of the servers for these items
    int nItemCount = 0;
    for (int n = 0; n < aCount; ++n) {
        // ensure that the key doesn't have a space in it
        CR_ASSERT(NULL == strchr(aItem[n].mKey.data(), ' '));
        aItem[n].mServer = FindServer(aItem[n].mKey, aItem[n].mService);
        aItem[n].mData.SetEmpty();
        if (aItem[n].mServer) {
            rgpItem[nItemCount++] = &aItem[n];
        }
        else {
            aItem[n].mResult = MCERR_NOSERVER;
        }
    }
    if (nItemCount == 0) {
        mTrace.Trace(CLDEBUG, "%s: ignoring request for all %d items (no servers available)", 
            aType, aCount);
        return 0;
    }

    // sort all requests into server order
    const static MemRequest::Sort sortOnServer = MemRequest::Sort();
    std::sort(&rgpItem[0], &rgpItem[nItemCount], sortOnServer);

    // send all requests
    char szBuf[50];
    int nItem = 0, nNext;
    string_t sRequest, sTemp;
    while (nItem < nItemCount) {
        for (nNext = nItem; nNext < nItemCount; ++nNext) {
            if (rgpItem[nItem]->mServer != rgpItem[nNext]->mServer) break;
            CR_ASSERT(*aType == 'g' || *aType == 'd');
            rgpItem[nNext]->mData.SetEmpty();

            // create get request for all keys on this server
            if (*aType == 'g') {
                if (nNext == nItem) sRequest = "get";
                else sRequest.resize(sRequest.length() - 2);
                sRequest += ' ';
                sRequest += rgpItem[nNext]->mKey;
                sRequest += "\r\n";
                rgpItem[nNext]->mResult = MCERR_NOTFOUND;
            }
            // create del request for all keys on this server
            else if (*aType == 'd') {
                // delete <key> [<time>] [noreply]\r\n
                sRequest += "delete ";
                sRequest += rgpItem[nNext]->mKey;
                sRequest += ' ';
                snprintf(szBuf, sizeof(szBuf), "%ld", (long) rgpItem[nNext]->mExpiry);
                sRequest += szBuf;
                if (rgpItem[nNext]->mResult == MCERR_NOREPLY) {
                    sRequest += " noreply";
                }
                sRequest += "\r\n";
                if (rgpItem[nNext]->mResult != MCERR_NOREPLY) {
                    rgpItem[nNext]->mResult = MCERR_NOTFOUND;
                }
            }
        }

        // send the request. any socket error causes the server connection 
        // to be dropped, so we return errors for all requests using that server.
        try {
            rgpItem[nItem]->mServer->SendBytes(
                sRequest.data(), sRequest.length());
        }
        catch (const Socket::Exception & e) {
            mTrace.Trace(CLINFO, "%s: request error '%s' at %s, marking requests as NOSERVER",
                aType, e.mDetail, rgpItem[nItem]->mServer->GetAddress());
            for (int n = nItem; n < nNext; ++n) {
                rgpItem[n]->mServer = NULL;
                rgpItem[n]->mResult = MCERR_NOSERVER;
            }
        }
        nItem = nNext;
    }

    // receive responses from all servers
    int nResponses = 0;
    for (nItem = 0; nItem < nItemCount; nItem = nNext) {
        // find the end of this server
        if (!rgpItem[nItem]->mServer) { nNext = nItem + 1; continue; }
        for (nNext = nItem + 1; nNext < nItemCount; ++nNext) {
            if (rgpItem[nItem]->mServer != rgpItem[nNext]->mServer) break;
        }

        // receive the responses. any socket error causes the server connection 
        // to be dropped, so we return errors for all requests using that server.
        try {
            if (*aType == 'g') {
                nResponses += HandleGetResponse(
                    rgpItem[nItem]->mServer, 
                    &rgpItem[nItem], &rgpItem[nNext]);
            }
            else if (*aType == 'd') {
                nResponses += HandleDelResponse(
                    rgpItem[nItem]->mServer, 
                    &rgpItem[nItem], &rgpItem[nNext]);
            }
        }
        catch (const Socket::Exception & e) {
            mTrace.Trace(CLINFO, "%s: response error '%s' at %s, marking requests as NOSERVER",
                aType, e.mDetail, rgpItem[nItem]->mServer->GetAddress());
            rgpItem[nItem]->mServer->Disconnect();
            for (int n = nNext - 1; n >= nItem; --n) {
                if (rgpItem[nItem]->mServer != rgpItem[n]->mServer) continue;
                rgpItem[n]->mServer = NULL;
                rgpItem[n]->mResult = MCERR_NOSERVER;
            }
        }
    }

    mTrace.Trace(CLDEBUG, "%s: received %d responses to %d requests",
        aType, nResponses, aCount);
    return nResponses;
}
int 
MemCacheClient::Combine(
    const char *    a_pszType,
    MemRequest *    a_rgItem, 
    int             a_nCount
    )
{
	if (a_nCount < 1) return 0;

    MemRequest * rgpItem[MAX_REQUESTS] = { NULL };
    if (a_nCount > MAX_REQUESTS) return -1; // invalid args

    // initialize and find all of the servers for these items
    int nItemCount = 0;
    for (int n = 0; n < a_nCount; ++n) {
        // ensure that the key doesn't have a space in it
        assert(NULL == strchr(a_rgItem[n].mKey.data(), ' '));
        a_rgItem[n].mServer = FindServer(a_rgItem[n].mKey);
        if (a_rgItem[n].mServer) {
            rgpItem[nItemCount++] = &a_rgItem[n];
        }
        else {
            a_rgItem[n].mResult = MCERR_NOSERVER;
        }
    }
    if (nItemCount == 0) return 0;

    // sort all requests into server order
    const static MemRequest::Sort sortOnServer = MemRequest::Sort();
    std::sort(&rgpItem[0], &rgpItem[nItemCount], sortOnServer);

    // send all requests
    char szBuf[50];
    int nItem = 0, nNext;
    string_t sRequest, sTemp;
    while (nItem < nItemCount) {
        for (nNext = nItem; nNext < nItemCount; ++nNext) {
            if (rgpItem[nItem]->mServer != rgpItem[nNext]->mServer) break;

            // create get request for all keys on this server
            if (*a_pszType == 'g') {
                if (nNext == nItem) sRequest = "get";
                else sRequest.resize(sRequest.length() - 2);
                sRequest += ' ';
                sRequest += rgpItem[nNext]->mKey;
                sRequest += "\r\n";
                rgpItem[nNext]->mResult = MCERR_NOTFOUND;
            }
            // create del request for all keys on this server
            else if (*a_pszType == 'd') {
                // delete <key> [<time>] [noreply]\r\n
                sRequest += "delete ";
                sRequest += rgpItem[nNext]->mKey;
                sRequest += ' ';
                snprintf(szBuf, sizeof(szBuf), "%ld", (long) rgpItem[nNext]->mExpiry);
                sRequest += szBuf;
                if (rgpItem[nNext]->mResult == MCERR_NOREPLY) {
                    sRequest += " noreply";
                }
                sRequest += "\r\n";
                if (rgpItem[nNext]->mResult != MCERR_NOREPLY) {
                    rgpItem[nNext]->mResult = MCERR_NOTFOUND;
                }
            }
        }

        // send the request. any socket error causes the server connection 
        // to be dropped, so we return errors for all requests using that server.
        try {
            rgpItem[nItem]->mServer->SendBytes(
                sRequest.data(), sRequest.length());
        }
        catch (const ServerSocket::Exception &) {
            for (int n = nItem; n < nNext; ++n) {
                rgpItem[n]->mServer = NULL;
                rgpItem[n]->mResult = MCERR_NOSERVER;
            }
        }
        nItem = nNext;
    }

    // receive responses from all servers
    int nResponses = 0;
    for (nItem = 0; nItem < nItemCount; nItem = nNext) {
        // find the end of this server
        if (!rgpItem[nItem]->mServer) { nNext = nItem + 1; continue; }
        for (nNext = nItem + 1; nNext < nItemCount; ++nNext) {
            if (rgpItem[nItem]->mServer != rgpItem[nNext]->mServer) break;
        }

        // receive the responses. any socket error causes the server connection 
        // to be dropped, so we return errors for all requests using that server.
        try {
            if (*a_pszType == 'g') {
                nResponses += HandleGetResponse(
                    rgpItem[nItem]->mServer, 
                    &rgpItem[nItem], &rgpItem[nNext]);
            }
            else if (*a_pszType == 'd') {
                nResponses += HandleDelResponse(
                    rgpItem[nItem]->mServer, 
                    &rgpItem[nItem], &rgpItem[nNext]);
            }
        }
        catch (const ServerSocket::Exception &) {
            rgpItem[nItem]->mServer->Disconnect();
            for (int n = nNext - 1; n >= nItem; --n) {
                if (rgpItem[nItem]->mServer != rgpItem[n]->mServer) continue;
                rgpItem[n]->mServer = NULL;
                rgpItem[n]->mResult = MCERR_NOSERVER;
            }
        }
    }

    return nResponses;
}