meng-tian / async-soap-guzzle

An asynchronous SOAP client build on top of Guzzle.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Concurrent Async Calls

MRZMUH001 opened this issue · comments

Hi Meng-Tian,

Have you managed to do multiple async calls at the same time? Currently, in most examples - you call wait on the promise which makes it synchronous.

I have tried few things but to no avail.

The above(atleast the first option) makes the requests in a non-blocking way, but it's the order and timing of the responses not making sense.

Suppose I have 3 similar requests all initiated at the same time calling the SoapClient async function below.

echo Request: #
$response = (yield $this->client->sendAsync($request, $requestOptions));
echo Response: #

The order goes like this for a http call of 5 seconds

Request 1, Request 2, Request 3 at T
First response received at T + 5
Second response received at T + 5 + 5
Third response received at T + 5 + 5 + 5

Note: I use all 4 of your libraries to send soap requests.

Hi @MRZMUH001 Thanks for raising the issue. I don't really understand the issue here but yes this library is capable of performing multiple async calls at the same time.

  1. Why you need use all 4 libraries to send SOAP requests? Shouldn't this library expose enough functionalities for you?
  2. Could you please give me a concrete example to show the issue?

From the code snippet you provided, it looks like you are using coroutine as I noticed you use yield. Using coroutine and yield will give you synchronised (but not blocking) result. In your example, the next request will be issued after the previous request received its response.

Thanks for your reply.

  1. Actually meant, I use your async guzzle library which uses the other 3 hence all 4.
  2. I did manage to get it working using event loops.

But basically, for multiple async calls or even a single non-blocking async call, you can go about (using your readme examples):

use GuzzleHttp\Client;
use Meng\AsyncSoap\Guzzle\Factory;
use GuzzleHttp\Promise;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\CurlMultiHandler;

$curl = new \GuzzleHttp\Handler\CurlMultiHandler();
$handler = \GuzzleHttp\HandlerStack::create($curl);

$factory = new Factory();
$client = $factory->create(new Client(['handler' => $handler]), 'http://www.webservicex.net/Statistics.asmx?WSDL'); // Note: we pass a curl handler to the guzzle client here

// See call_async function below. For each call, we return a promise with a callback for what to return for when the promise is eventually resolved.
$promises = [];
$promises[] = call_async();
$promises[] = call_async();

// At this point, you can do anything - call DB or whatever as we have fired a request and are just waiting for the response.

// Basically instead of waiting (which is another magic method of executing the promise) we continuously tick the curl handler passed to the guzzle client while it is in its pending state)
foreach($promises as $promise) {
   while($promise->getState() === "pending") {
      $curl->tick();
   }
}
// Once the promise is no longer in a pending state (fulfilled or cancelled), the promise will be resolved and will reach this point.

// To wait on all of the requests to complete. Throws a ConnectException if any of the requests fail
$results = Promise\unwrap($promises);

/* OR */

// To wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();


function call_async($client){

 // callAsync here is the method in the SoapClient (async-soap-guzzle library with coroutine and yields)
 return $client->callAsync('GetStatistics', [['X' => [1,2,3]]])->then(
    function(Request $response) { // Promise success callback (fulfilled)
       return $response;
    },
    function (RequestException $e) { // Promise rejected callback (cancelled)
        throw new \Exception(str($e));
    }
  );
}

My tests showed that all requests are fired almost at the same time in a non-blocking manner and responses come back at the same time as well (given they take same time to complete). Not sure, if it's the best way but it's proven to be effective so far.

Re: Coroutines & yield, they are actually part of the callAsync function in the SoapClient file.

Hi @MRZMUH001 , thanks for your explanation. Now I understand your questions. Your approach looks good to me. Using CurlMultiHandler is the right choice as that's the only out of box Guzzle handler that support asynchronous HTTP transport. You can either tick the handler to progress the HTTP transport like what you showed in the example or hook the tick in an event loop like this one https://github.com/reactphp/event-loop.

Under the hood, sending HTTP messages asynchronously requires keep polling the status of HTTP messages. That's why tick and event loop is usually needed.

Looks like you have found the way to resolve your issue, so I will close this issue.